Quick.Console.pas 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. { ***************************************************************************
  2. Copyright (c) 2016-2017 Kike Pérez
  3. Unit : Quick.Console
  4. Description : Console output with colors and optional file log
  5. Author : Kike Pérez
  6. Version : 1.6
  7. Created : 10/05/2017
  8. Modified : 20/10/2017
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Console;
  22. {$IFDEF CONDITIONALEXPRESSIONS}
  23. {$ifndef VER140}
  24. {$ifndef LINUX}
  25. {$define WITHUXTHEME}
  26. {$endif}
  27. {$endif}
  28. {$IF CompilerVersion >= 17.0}
  29. {$DEFINE INLINES}
  30. {$ENDIF}
  31. {$IF RTLVersion >= 14.0}
  32. {$DEFINE HASERROUTPUT}
  33. {$ENDIF}
  34. {$ENDIF}
  35. interface
  36. uses
  37. Classes,
  38. Windows,
  39. Winapi.Messages,
  40. System.SysUtils,
  41. Quick.Commons,
  42. Quick.Log;
  43. type
  44. //text colors
  45. TConsoleColor = (
  46. ccBlack = 0,
  47. ccBlue = 1,
  48. ccGreen = 2,
  49. ccCyan = 3,
  50. ccRed = 4,
  51. ccMagenta = 5,
  52. ccBrown = 6,
  53. ccLightGray = 7,
  54. ccDarkGray = 8,
  55. ccLightBlue = 9,
  56. ccLightGreen = 10,
  57. ccLightCyan = 11,
  58. ccLightRed = 12,
  59. ccLightMagenta = 13,
  60. ccYellow = 14,
  61. ccWhite = 15);
  62. TConsoleProperties = record
  63. LogVerbose : TLogVerbose;
  64. Log : TQuickLog;
  65. end;
  66. procedure cout(const cMsg : Integer; cEventType : TEventType); overload;
  67. procedure cout(const cMsg : Double; cEventType : TEventType); overload;
  68. procedure cout(const cMsg : string; cEventType : TEventType); overload;
  69. procedure cout(const cMsg : string; cColor : TConsoleColor); overload;
  70. procedure coutXY(x,y : Integer; const s : string; cEventType : TEventType);
  71. procedure coutBL(const s : string; cEventType : TEventType);
  72. procedure coutFmt(const cMsg : string; params : array of const; cEventType : TEventType);
  73. procedure TextColor(Color: TConsoleColor); overload;
  74. procedure TextColor(Color: Byte); overload;
  75. procedure TextBackground(Color: TConsoleColor); overload;
  76. procedure TextBackground(Color: Byte); overload;
  77. procedure ResetColors;
  78. function ClearScreen : Boolean;
  79. procedure ClearLine;
  80. procedure ConsoleWaitForEnterKey;
  81. procedure InitConsole;
  82. var
  83. Console : TConsoleProperties;
  84. CSConsole : TRTLCriticalSection;
  85. LastMode : Word;
  86. DefConsoleColor : Byte;
  87. TextAttr : Byte;
  88. hStdOut: THandle;
  89. hStdErr: THandle;
  90. ConsoleRect: TSmallRect;
  91. ScreenBufInfo : TConsoleScreenBufferInfo;
  92. implementation
  93. procedure cout(const cMsg : Integer; cEventType : TEventType);
  94. var
  95. FmtSets : TFormatSettings;
  96. begin
  97. try
  98. FmtSets := TFormatSettings.Create;
  99. FmtSets.ThousandSeparator := '.';
  100. FmtSets.DecimalSeparator := ',';
  101. cout(FormatFloat('0,',cMsg,FmtSets),cEventType);
  102. except
  103. cout(cMsg.ToString,cEventType);
  104. end;
  105. end;
  106. procedure cout(const cMsg : Double; cEventType : TEventType);
  107. var
  108. FmtSets : TFormatSettings;
  109. begin
  110. try
  111. FmtSets := TFormatSettings.Create;
  112. FmtSets.ThousandSeparator := '.';
  113. FmtSets.DecimalSeparator := ',';
  114. cout(FormatFloat('.0###,',cMsg,FmtSets),cEventType);
  115. except
  116. cout(cMsg.ToString,cEventType);
  117. end;
  118. end;
  119. procedure cout(const cMsg : string; cEventType : TEventType);
  120. begin
  121. if cEventType in Console.LogVerbose then
  122. begin
  123. EnterCriticalSection(CSConsole);
  124. try
  125. if hStdOut <> 0 then
  126. begin
  127. case cEventType of
  128. etError : TextColor(ccLightRed);
  129. etInfo : TextColor(ccWhite);
  130. etSuccess : TextColor(ccLightGreen);
  131. etWarning : TextColor(ccYellow);
  132. etDebug : TextColor(ccLightCyan);
  133. etTrace : TextColor(ccLightMagenta);
  134. else TextColor(ccWhite);
  135. end;
  136. Writeln(cMsg);
  137. TextColor(LastMode);
  138. end;
  139. finally
  140. LeaveCriticalSection(CSConsole);
  141. end;
  142. if Assigned(Console.Log) then Console.Log.Add(cMsg,cEventType);
  143. end;
  144. end;
  145. procedure cout(const cMsg : string; cColor : TConsoleColor);
  146. begin
  147. EnterCriticalSection(CSConsole);
  148. try
  149. if hStdOut <> 0 then
  150. begin
  151. TextColor(cColor);
  152. Writeln(cMsg);
  153. TextColor(LastMode);
  154. end;
  155. finally
  156. LeaveCriticalSection(CSConsole);
  157. end;
  158. end;
  159. function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  160. var
  161. BufferInfo: TConsoleScreenBufferInfo;
  162. begin
  163. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  164. Result := BufferInfo.dwCursorPosition.X;
  165. end;
  166. function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  167. var
  168. BufferInfo: TConsoleScreenBufferInfo;
  169. begin
  170. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  171. Result := BufferInfo.dwCursorPosition.Y;
  172. end;
  173. function GetCursorMaxBottom : Integer;
  174. var
  175. BufferInfo: TConsoleScreenBufferInfo;
  176. begin
  177. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  178. Result := BufferInfo.srWindow.Bottom;
  179. end;
  180. procedure SetCursorPos(NewCoord : TCoord);
  181. begin
  182. SetConsoleCursorPosition(hStdOut, NewCoord);
  183. end;
  184. procedure coutXY(x,y : Integer; const s : string; cEventType : TEventType);
  185. var
  186. NewCoord : TCoord;
  187. LastCoord : TCoord;
  188. begin
  189. if hStdOut = 0 then Exit;
  190. LastCoord.X := GetCursorX;
  191. LastCoord.Y := GetCursorY;
  192. NewCoord.X := x;
  193. NewCoord.Y := y;
  194. ClearLine;
  195. SetCursorPos(NewCoord);
  196. try
  197. cout(s,cEventType);
  198. finally
  199. SetCursorPos(LastCoord);
  200. end;
  201. end;
  202. procedure coutBL(const s : string; cEventType : TEventType);
  203. var
  204. NewCoord : TCoord;
  205. begin
  206. coutXY(0,GetCurSorMaxBottom,s,cEventType);
  207. NewCoord.X := GetCursorX;
  208. NewCoord.Y := GetCurSorMaxBottom - 1;
  209. SetCursorPos(NewCoord);
  210. end;
  211. procedure coutFmt(const cMsg : string; params : array of const; cEventType : TEventType);
  212. begin
  213. cout(Format(cMsg,params),cEventType);
  214. end;
  215. procedure TextColor(Color: TConsoleColor);
  216. begin
  217. TextColor(Integer(Color));
  218. end;
  219. procedure TextColor(Color: Byte);
  220. begin
  221. if hStdOut = 0 then Exit;
  222. LastMode := TextAttr;
  223. TextAttr := (TextAttr and $F0) or (Color and $0F);
  224. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  225. end;
  226. procedure TextBackground(Color: TConsoleColor);
  227. begin
  228. TextBackground(Integer(Color));
  229. end;
  230. procedure TextBackground(Color: Byte);
  231. begin
  232. if hStdOut = 0 then Exit;
  233. LastMode := TextAttr;
  234. TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0);
  235. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  236. end;
  237. procedure ResetColors;
  238. begin
  239. SetConsoleTextAttribute(hStdOut, DefConsoleColor);
  240. TextAttr := DefConsoleColor;
  241. end;
  242. function ClearScreen : Boolean;
  243. const
  244. BUFSIZE = 80*25;
  245. var
  246. Han, Dummy: LongWord;
  247. buf: string;
  248. coord: TCoord;
  249. begin
  250. Result := false;
  251. Han := GetStdHandle(STD_OUTPUT_HANDLE);
  252. if Han <> INVALID_HANDLE_VALUE then
  253. begin
  254. if SetConsoleTextAttribute(han, FOREGROUND_RED or FOREGROUND_GREEN or FOREGROUND_BLUE) then
  255. begin
  256. SetLength(buf,BUFSIZE);
  257. FillChar(buf[1],Length(buf),' ');
  258. if WriteConsole(han,PChar(buf),BUFSIZE,Dummy,nil) then
  259. begin
  260. coord.X := 0;
  261. coord.Y := 0;
  262. if SetConsoleCursorPosition(han,coord) then Result := true;
  263. end;
  264. end;
  265. end;
  266. end;
  267. procedure ClearLine;
  268. var
  269. dwWriteCoord: TCoord;
  270. dwCount, dwSize: DWord;
  271. begin
  272. if hStdOut = 0 then Exit;
  273. dwWriteCoord.X := ConsoleRect.Left;
  274. dwWriteCoord.Y := GetCursorY;
  275. dwSize := ConsoleRect.Right - ConsoleRect.Left + 1;
  276. FillConsoleOutputAttribute(hStdOut, TextAttr, dwSize, dwWriteCoord, dwCount);
  277. FillConsoleOutputCharacter(hStdOut, ' ', dwSize, dwWriteCoord, dwCount);
  278. end;
  279. function ConsoleKeyPressed(ExpectedKey: Word): Boolean;
  280. var
  281. lpNumberOfEvents: DWORD;
  282. lpBuffer: TInputRecord;
  283. lpNumberOfEventsRead : DWORD;
  284. nStdHandle: THandle;
  285. begin
  286. Result := False;
  287. nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  288. lpNumberOfEvents := 0;
  289. GetNumberOfConsoleInputEvents(nStdHandle, lpNumberOfEvents);
  290. if lpNumberOfEvents <> 0 then
  291. begin
  292. PeekConsoleInput(nStdHandle, lpBuffer, 1, lpNumberOfEventsRead);
  293. if lpNumberOfEventsRead <> 0 then
  294. begin
  295. if lpBuffer.EventType = KEY_EVENT then
  296. begin
  297. if lpBuffer.Event.KeyEvent.bKeyDown and ((ExpectedKey = 0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode = ExpectedKey)) then Result := true
  298. else FlushConsoleInputBuffer(nStdHandle);
  299. end
  300. else FlushConsoleInputBuffer(nStdHandle);
  301. end;
  302. end;
  303. end;
  304. procedure ConsoleWaitForEnterKey;
  305. var
  306. msg: TMsg;
  307. begin
  308. while not ConsoleKeyPressed(VK_RETURN) do
  309. begin
  310. {$ifndef LVCL}
  311. if GetCurrentThreadID=MainThreadID then CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif} else
  312. {$endif}
  313. WaitMessage;
  314. while PeekMessage(msg,0,0,0,PM_REMOVE) do
  315. begin
  316. if Msg.Message = WM_QUIT then Exit
  317. else
  318. begin
  319. TranslateMessage(Msg);
  320. DispatchMessage(Msg);
  321. end;
  322. end;
  323. end;
  324. end;
  325. procedure InitConsole;
  326. var
  327. BufferInfo: TConsoleScreenBufferInfo;
  328. begin
  329. Console.LogVerbose := LOG_ALL;
  330. Rewrite(Output);
  331. hStdOut := TTextRec(Output).Handle;
  332. {$IFDEF HASERROUTPUT}
  333. Rewrite(ErrOutput);
  334. hStdErr := TTextRec(ErrOutput).Handle;
  335. {$ELSE}
  336. hStdErr := GetStdHandle(STD_ERROR_HANDLE);
  337. {$ENDIF}
  338. if not GetConsoleScreenBufferInfo(hStdOut, BufferInfo) then
  339. begin
  340. SetInOutRes(GetLastError);
  341. Exit;
  342. end;
  343. ConsoleRect.Left := 0;
  344. ConsoleRect.Top := 0;
  345. ConsoleRect.Right := BufferInfo.dwSize.X - 1;
  346. ConsoleRect.Bottom := BufferInfo.dwSize.Y - 1;
  347. TextAttr := BufferInfo.wAttributes and $FF;
  348. DefConsoleColor := TextAttr;
  349. LastMode := 3; //CO80;
  350. end;
  351. initialization
  352. InitializeCriticalSection(CSConsole);
  353. //init stdout if not a service
  354. if GetStdHandle(STD_OUTPUT_HANDLE) <> 0 then InitConsole;
  355. finalization
  356. DeleteCriticalSection(CSConsole);
  357. end.