Quick.Console.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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; overload;
  80. procedure ClearLine(Y : Integer); overload;
  81. procedure ConsoleWaitForEnterKey;
  82. procedure InitConsole;
  83. var
  84. Console : TConsoleProperties;
  85. CSConsole : TRTLCriticalSection;
  86. LastMode : Word;
  87. DefConsoleColor : Byte;
  88. TextAttr : Byte;
  89. hStdOut: THandle;
  90. hStdErr: THandle;
  91. ConsoleRect: TSmallRect;
  92. ScreenBufInfo : TConsoleScreenBufferInfo;
  93. implementation
  94. procedure cout(const cMsg : Integer; cEventType : TEventType);
  95. var
  96. FmtSets : TFormatSettings;
  97. begin
  98. try
  99. FmtSets := TFormatSettings.Create;
  100. FmtSets.ThousandSeparator := '.';
  101. FmtSets.DecimalSeparator := ',';
  102. cout(FormatFloat('0,',cMsg,FmtSets),cEventType);
  103. except
  104. cout(cMsg.ToString,cEventType);
  105. end;
  106. end;
  107. procedure cout(const cMsg : Double; cEventType : TEventType);
  108. var
  109. FmtSets : TFormatSettings;
  110. begin
  111. try
  112. FmtSets := TFormatSettings.Create;
  113. FmtSets.ThousandSeparator := '.';
  114. FmtSets.DecimalSeparator := ',';
  115. cout(FormatFloat('.0###,',cMsg,FmtSets),cEventType);
  116. except
  117. cout(cMsg.ToString,cEventType);
  118. end;
  119. end;
  120. procedure cout(const cMsg : string; cEventType : TEventType);
  121. begin
  122. if cEventType in Console.LogVerbose then
  123. begin
  124. EnterCriticalSection(CSConsole);
  125. try
  126. if hStdOut <> 0 then
  127. begin
  128. case cEventType of
  129. etError : TextColor(ccLightRed);
  130. etInfo : TextColor(ccWhite);
  131. etSuccess : TextColor(ccLightGreen);
  132. etWarning : TextColor(ccYellow);
  133. etDebug : TextColor(ccLightCyan);
  134. etTrace : TextColor(ccLightMagenta);
  135. else TextColor(ccWhite);
  136. end;
  137. Writeln(cMsg);
  138. TextColor(LastMode);
  139. end;
  140. finally
  141. LeaveCriticalSection(CSConsole);
  142. end;
  143. if Assigned(Console.Log) then Console.Log.Add(cMsg,cEventType);
  144. end;
  145. end;
  146. procedure cout(const cMsg : string; cColor : TConsoleColor);
  147. begin
  148. EnterCriticalSection(CSConsole);
  149. try
  150. if hStdOut <> 0 then
  151. begin
  152. TextColor(cColor);
  153. Writeln(cMsg);
  154. TextColor(LastMode);
  155. end;
  156. finally
  157. LeaveCriticalSection(CSConsole);
  158. end;
  159. end;
  160. function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  161. var
  162. BufferInfo: TConsoleScreenBufferInfo;
  163. begin
  164. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  165. Result := BufferInfo.dwCursorPosition.X;
  166. end;
  167. function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF}
  168. var
  169. BufferInfo: TConsoleScreenBufferInfo;
  170. begin
  171. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  172. Result := BufferInfo.dwCursorPosition.Y;
  173. end;
  174. function GetCursorMaxBottom : Integer;
  175. var
  176. BufferInfo: TConsoleScreenBufferInfo;
  177. begin
  178. GetConsoleSCreenBufferInfo(hStdOut, BufferInfo);
  179. Result := BufferInfo.srWindow.Bottom;
  180. end;
  181. procedure SetCursorPos(NewCoord : TCoord);
  182. begin
  183. SetConsoleCursorPosition(hStdOut, NewCoord);
  184. end;
  185. procedure coutXY(x,y : Integer; const s : string; cEventType : TEventType);
  186. var
  187. NewCoord : TCoord;
  188. LastCoord : TCoord;
  189. begin
  190. if hStdOut = 0 then Exit;
  191. LastCoord.X := GetCursorX;
  192. LastCoord.Y := GetCursorY;
  193. NewCoord.X := x;
  194. NewCoord.Y := y;
  195. ClearLine(Y);
  196. SetCursorPos(NewCoord);
  197. try
  198. cout(s,cEventType);
  199. finally
  200. SetCursorPos(LastCoord);
  201. end;
  202. end;
  203. procedure coutBL(const s : string; cEventType : TEventType);
  204. var
  205. NewCoord : TCoord;
  206. begin
  207. coutXY(0,GetCurSorMaxBottom - 1,s,cEventType);
  208. end;
  209. procedure coutFmt(const cMsg : string; params : array of const; cEventType : TEventType);
  210. begin
  211. cout(Format(cMsg,params),cEventType);
  212. end;
  213. procedure TextColor(Color: TConsoleColor);
  214. begin
  215. TextColor(Integer(Color));
  216. end;
  217. procedure TextColor(Color: Byte);
  218. begin
  219. if hStdOut = 0 then Exit;
  220. LastMode := TextAttr;
  221. TextAttr := (TextAttr and $F0) or (Color and $0F);
  222. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  223. end;
  224. procedure TextBackground(Color: TConsoleColor);
  225. begin
  226. TextBackground(Integer(Color));
  227. end;
  228. procedure TextBackground(Color: Byte);
  229. begin
  230. if hStdOut = 0 then Exit;
  231. LastMode := TextAttr;
  232. TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0);
  233. if TextAttr <> LastMode then SetConsoleTextAttribute(hStdOut, TextAttr);
  234. end;
  235. procedure ResetColors;
  236. begin
  237. SetConsoleTextAttribute(hStdOut, DefConsoleColor);
  238. TextAttr := DefConsoleColor;
  239. end;
  240. function ClearScreen : Boolean;
  241. const
  242. BUFSIZE = 80*25;
  243. var
  244. Han, Dummy: LongWord;
  245. buf: string;
  246. coord: TCoord;
  247. begin
  248. Result := false;
  249. Han := GetStdHandle(STD_OUTPUT_HANDLE);
  250. if Han <> INVALID_HANDLE_VALUE then
  251. begin
  252. if SetConsoleTextAttribute(han, FOREGROUND_RED or FOREGROUND_GREEN or FOREGROUND_BLUE) then
  253. begin
  254. SetLength(buf,BUFSIZE);
  255. FillChar(buf[1],Length(buf),' ');
  256. if WriteConsole(han,PChar(buf),BUFSIZE,Dummy,nil) then
  257. begin
  258. coord.X := 0;
  259. coord.Y := 0;
  260. if SetConsoleCursorPosition(han,coord) then Result := true;
  261. end;
  262. end;
  263. end;
  264. end;
  265. procedure ClearLine;
  266. begin
  267. ClearLine(GetCursorY);
  268. end;
  269. procedure ClearLine(Y : Integer);
  270. var
  271. dwWriteCoord: TCoord;
  272. dwCount, dwSize: DWord;
  273. begin
  274. if hStdOut = 0 then Exit;
  275. dwWriteCoord.X := 0;
  276. dwWriteCoord.Y := Y;
  277. dwSize := ConsoleRect.Right + 1;
  278. FillConsoleOutputAttribute(hStdOut, TextAttr, dwSize, dwWriteCoord, dwCount);
  279. FillConsoleOutputCharacter(hStdOut, ' ', dwSize, dwWriteCoord, dwCount);
  280. end;
  281. function ConsoleKeyPressed(ExpectedKey: Word): Boolean;
  282. var
  283. lpNumberOfEvents: DWORD;
  284. lpBuffer: TInputRecord;
  285. lpNumberOfEventsRead : DWORD;
  286. nStdHandle: THandle;
  287. begin
  288. Result := False;
  289. nStdHandle := GetStdHandle(STD_INPUT_HANDLE);
  290. lpNumberOfEvents := 0;
  291. GetNumberOfConsoleInputEvents(nStdHandle, lpNumberOfEvents);
  292. if lpNumberOfEvents <> 0 then
  293. begin
  294. PeekConsoleInput(nStdHandle, lpBuffer, 1, lpNumberOfEventsRead);
  295. if lpNumberOfEventsRead <> 0 then
  296. begin
  297. if lpBuffer.EventType = KEY_EVENT then
  298. begin
  299. if lpBuffer.Event.KeyEvent.bKeyDown and ((ExpectedKey = 0) or (lpBuffer.Event.KeyEvent.wVirtualKeyCode = ExpectedKey)) then Result := true
  300. else FlushConsoleInputBuffer(nStdHandle);
  301. end
  302. else FlushConsoleInputBuffer(nStdHandle);
  303. end;
  304. end;
  305. end;
  306. procedure ConsoleWaitForEnterKey;
  307. var
  308. msg: TMsg;
  309. begin
  310. while not ConsoleKeyPressed(VK_RETURN) do
  311. begin
  312. {$ifndef LVCL}
  313. if GetCurrentThreadID=MainThreadID then CheckSynchronize{$ifdef WITHUXTHEME}(1000){$endif} else
  314. {$endif}
  315. WaitMessage;
  316. while PeekMessage(msg,0,0,0,PM_REMOVE) do
  317. begin
  318. if Msg.Message = WM_QUIT then Exit
  319. else
  320. begin
  321. TranslateMessage(Msg);
  322. DispatchMessage(Msg);
  323. end;
  324. end;
  325. end;
  326. end;
  327. procedure InitConsole;
  328. var
  329. BufferInfo: TConsoleScreenBufferInfo;
  330. begin
  331. Console.LogVerbose := LOG_ALL;
  332. Rewrite(Output);
  333. hStdOut := TTextRec(Output).Handle;
  334. {$IFDEF HASERROUTPUT}
  335. Rewrite(ErrOutput);
  336. hStdErr := TTextRec(ErrOutput).Handle;
  337. {$ELSE}
  338. hStdErr := GetStdHandle(STD_ERROR_HANDLE);
  339. {$ENDIF}
  340. if not GetConsoleScreenBufferInfo(hStdOut, BufferInfo) then
  341. begin
  342. SetInOutRes(GetLastError);
  343. Exit;
  344. end;
  345. ConsoleRect.Left := 0;
  346. ConsoleRect.Top := 0;
  347. ConsoleRect.Right := BufferInfo.dwSize.X - 1;
  348. ConsoleRect.Bottom := BufferInfo.dwSize.Y - 1;
  349. TextAttr := BufferInfo.wAttributes and $FF;
  350. DefConsoleColor := TextAttr;
  351. LastMode := 3; //CO80;
  352. end;
  353. initialization
  354. InitializeCriticalSection(CSConsole);
  355. //init stdout if not a service
  356. if GetStdHandle(STD_OUTPUT_HANDLE) <> 0 then InitConsole;
  357. finalization
  358. DeleteCriticalSection(CSConsole);
  359. end.