removed spaces at line end
@@ -1,941 +1,941 @@ | ||
1 | 1 | { } |
2 | -{ File: Velthuis.Console.pas } | |
3 | -{ Function: Console unit, similar to the Crt unit in Turbo Pascal. } | |
4 | -{ Language: Delphi 5 and above } | |
5 | -{ Author: Rudolph Velthuis } | |
6 | -{ Copyright: (c) 2006,2008 Rudy Velthuis } | |
7 | -{ Disclaimer: This code is freeware. All rights are reserved. } | |
8 | -{ This code is provided as is, expressly without a warranty } | |
9 | -{ of any kind. You use it at your own risk. } | |
10 | -{ } | |
11 | -{ If you use this code, please credit me. } | |
12 | -{ } | |
13 | -// taken from https://github.com/rvelthuis/Consoles | |
14 | -// -- 2020-02-29 twm | |
2 | +{ File: Velthuis.Console.pas } | |
3 | +{ Function: Console unit, similar to the Crt unit in Turbo Pascal. } | |
4 | +{ Language: Delphi 5 and above } | |
5 | +{ Author: Rudolph Velthuis } | |
6 | +{ Copyright: (c) 2006,2008 Rudy Velthuis } | |
7 | +{ Disclaimer: This code is freeware. All rights are reserved. } | |
8 | +{ This code is provided as is, expressly without a warranty } | |
9 | +{ of any kind. You use it at your own risk. } | |
10 | +{ } | |
11 | +{ If you use this code, please credit me. } | |
12 | +{ } | |
13 | +// taken from https://github.com/rvelthuis/Consoles | |
14 | +// -- 2020-02-29 twm | |
15 | + | |
16 | +unit Velthuis.Console; | |
17 | + | |
18 | +{$IFDEF CONDITIONALEXPRESSIONS} | |
19 | + {$IF CompilerVersion >= 17.0} | |
20 | + {$DEFINE INLINES} | |
21 | + {$IFEND} | |
22 | + {$IF RTLVersion >= 14.0} | |
23 | + {$DEFINE HASERROUTPUT} | |
24 | + {$IFEND} | |
25 | +{$ENDIF} | |
26 | + | |
27 | +interface | |
28 | + | |
29 | +uses Windows; | |
30 | + | |
31 | +const | |
32 | + // Background and foreground colors | |
33 | + Black = 0; | |
34 | + Blue = 1; | |
35 | + Green = 2; | |
36 | + Cyan = 3; | |
37 | + Red = 4; | |
38 | + Magenta = 5; | |
39 | + Brown = 6; | |
40 | + LightGray = 7; | |
41 | + | |
42 | + // Foreground colors | |
43 | + DarkGray = 8; | |
44 | + LightBlue = 9; | |
45 | + LightGreen = 10; | |
46 | + LightCyan = 11; | |
47 | + LightRed = 12; | |
48 | + LightMagenta = 13; | |
49 | + Yellow = 14; | |
50 | + White = 15; | |
51 | + | |
52 | + // Blink attribute, to be or-ed with background colors. | |
53 | + Blink = 128; | |
54 | + | |
55 | + // Text modes: | |
56 | + BW40 = 0; // 40x25 B/W on Color Adapter | |
57 | + CO40 = 1; // 40x25 Color on Color Adapter | |
58 | + BW80 = 2; // 80x25 B/W on Color Adapter | |
59 | + CO80 = 3; // 80x25 Color on Color Adapter | |
60 | + Mono = 7; // 80x25 on Monochrome Adapter | |
61 | + Font8x8 = 256; // Add-in for ROM font | |
62 | + | |
63 | + // Mode constants for 3.0 compatibility of original CRT unit } | |
64 | + C40 = CO40; | |
65 | + C80 = CO80; | |
66 | + | |
67 | + | |
68 | +// Turbo/Borland Pascal Crt routines: | |
69 | + | |
70 | +// Waits for keypress and returns the key pressed. If the key is not an ASCII | |
71 | +// key, #0 is returned, and a successive ReadKey will give the extended key | |
72 | +// code of the key. | |
73 | +function ReadKey: Char; | |
74 | + | |
75 | +// Checks whether a key was pressed. | |
76 | +function KeyPressed: Boolean; | |
77 | + | |
78 | +// Puts the cursor at the given coordinates on the screen. | |
79 | +procedure GotoXY(X, Y: Smallint); | |
80 | + | |
81 | +// Returns the current X position of the cursor. | |
82 | +function WhereX: Integer; | |
83 | + | |
84 | +// Returns the current Y position of the cursor. | |
85 | +function WhereY: Integer; | |
86 | + | |
87 | +// Sets text foreground color. | |
88 | +procedure TextColor(Color: Byte); overload; | |
89 | + | |
90 | +// Gets text forground color. | |
91 | +function TextColor: Byte; overload; | |
92 | + | |
93 | +// Sets text background color. | |
94 | +procedure TextBackground(Color: Byte); overload; | |
95 | + | |
96 | +// Gets text background color. | |
97 | +function TextBackground: Byte; overload; | |
98 | + | |
99 | +// Sets text mode. | |
100 | +procedure TextMode(Mode: Word); | |
101 | + | |
102 | +// Sets text colors to low intensity | |
103 | +procedure LowVideo; | |
104 | + | |
105 | +// Sets text colors to high intensity | |
106 | +procedure HighVideo; | |
107 | + | |
108 | +// Sets text attribute to value at startup. | |
109 | +procedure NormVideo; | |
110 | + | |
111 | +// Clears the entire screen, or, if a window is set, the entire window, | |
112 | +// in the current background color. | |
113 | +procedure ClrScr; | |
114 | + | |
115 | +// Clears until the end of the line, in the current background color. | |
116 | +procedure ClrEol; | |
117 | + | |
118 | +// Inserts a line at the current cursor position. | |
119 | +procedure InsLine; | |
120 | + | |
121 | +// Deletes the line at the current cursor position. | |
122 | +procedure DelLine; | |
123 | + | |
124 | +// Sets a window, into which all successive output will go. You can reset the | |
125 | +// window to full screen by calling Window with a zero or negative value | |
126 | +// for Left. | |
127 | +procedure Window(Left, Top, Right, Bottom: Integer); | |
128 | + | |
129 | +// Displays message and waits for the next key press. Displays key | |
130 | +// and returns. | |
131 | +function Pause(const Msg: string = ''): Char; | |
132 | + | |
133 | +type | |
134 | + // Plays a sound at the given frequency (in Herz). | |
135 | + TSoundProc = procedure(Frequency: Smallint); | |
136 | + | |
137 | + // Stops the sound started with Sound. | |
138 | + TNoSoundProc = procedure; | |
139 | + | |
140 | + // Delays for the given amount of milliseconds, or as close as possible. | |
141 | + TDelayProc = procedure(Millisecs: Integer); | |
142 | + | |
143 | + // Plays a sound at the given frequency (in Hz) and duration (in ms). | |
144 | + TBeepProc = procedure(Frequency, Duration: Smallint); | |
145 | + | |
146 | +var | |
147 | + Sound: TSoundProc; | |
148 | + NoSound: TNoSoundProc; | |
149 | + Delay: TDelayProc; | |
150 | + Beep: TBeepProc; | |
151 | + | |
152 | +// Additional routines: | |
153 | + | |
154 | +function ScreenWidth: Smallint; | |
155 | +function ScreenHeight: Smallint; | |
156 | +function BufferWidth: Smallint; | |
157 | +function BufferHeight: Smallint; | |
158 | + | |
159 | +var | |
160 | + TextWindow: TSmallRect; | |
161 | + TextAttr: Byte; | |
162 | + DefaultAttr: Byte; | |
163 | + ScreenMode: Byte; | |
164 | + BufferSize: TCoord; | |
165 | + ScreenSize: TCoord; | |
166 | + StdIn, StdOut: THandle; | |
167 | + StdErr: THandle; | |
168 | + LastMode: Word; | |
169 | + WindMin: Word; | |
170 | + WindMax: Word; | |
171 | + CheckBreak: Boolean; | |
172 | + | |
173 | +implementation | |
174 | + | |
175 | +uses SysUtils; | |
176 | + | |
177 | +type | |
178 | + PKey = ^TKey; | |
179 | + TKey = record | |
180 | + KeyCode: Smallint; | |
181 | + Normal: Smallint; | |
182 | + Shift: Smallint; | |
183 | + Ctrl: Smallint; | |
184 | + Alt: Smallint; | |
185 | + end; | |
186 | + | |
187 | +const | |
188 | + CKeys: array[0..88] of TKey = ( | |
189 | + (KeyCode: VK_BACK; Normal: $8; Shift: $8; Ctrl: $7F; Alt: $10E; ), | |
190 | + (KeyCode: VK_TAB; Normal: $9; Shift: $10F; Ctrl: $194; Alt: $1A5; ), | |
191 | + (KeyCode: VK_RETURN; Normal: $D; Shift: $D; Ctrl: $A; Alt: $1A6), | |
192 | + (KeyCode: VK_ESCAPE; Normal: $1B; Shift: $1B; Ctrl: $1B; Alt: $101), | |
193 | + (KeyCode: VK_SPACE; Normal: $20; Shift: $20; Ctrl: $103; Alt: $20), | |
194 | + (KeyCode: Ord('0'); Normal: Ord('0'); Shift: Ord(')'); Ctrl: - 1; Alt: $181), | |
195 | + (KeyCode: Ord('1'); Normal: Ord('1'); Shift: Ord('!'); Ctrl: - 1; Alt: $178), | |
196 | + (KeyCode: Ord('2'); Normal: Ord('2'); Shift: Ord('@'); Ctrl: $103; Alt: $179), | |
197 | + (KeyCode: Ord('3'); Normal: Ord('3'); Shift: Ord('#'); Ctrl: - 1; Alt: $17A), | |
198 | + (KeyCode: Ord('4'); Normal: Ord('4'); Shift: Ord('$'); Ctrl: - 1; Alt: $17B), | |
199 | + (KeyCode: Ord('5'); Normal: Ord('5'); Shift: Ord('%'); Ctrl: - 1; Alt: $17C), | |
200 | + (KeyCode: Ord('6'); Normal: Ord('6'); Shift: Ord('^'); Ctrl: $1E; Alt: $17D), | |
201 | + (KeyCode: Ord('7'); Normal: Ord('7'); Shift: Ord('&'); Ctrl: - 1; Alt: $17E), | |
202 | + (KeyCode: Ord('8'); Normal: Ord('8'); Shift: Ord('*'); Ctrl: - 1; Alt: $17F), | |
203 | + (KeyCode: Ord('9'); Normal: Ord('9'); Shift: Ord('('); Ctrl: - 1; Alt: $180), | |
204 | + (KeyCode: Ord('A'); Normal: Ord('a'); Shift: Ord('A'); Ctrl: $1; Alt: $11E), | |
205 | + (KeyCode: Ord('B'); Normal: Ord('b'); Shift: Ord('B'); Ctrl: $2; Alt: $130), | |
206 | + (KeyCode: Ord('C'); Normal: Ord('c'); Shift: Ord('C'); Ctrl: $3; Alt: $12E), | |
207 | + (KeyCode: Ord('D'); Normal: Ord('d'); Shift: Ord('D'); Ctrl: $4; Alt: $120), | |
208 | + (KeyCode: Ord('E'); Normal: Ord('e'); Shift: Ord('E'); Ctrl: $5; Alt: $112), | |
209 | + (KeyCode: Ord('F'); Normal: Ord('f'); Shift: Ord('F'); Ctrl: $6; Alt: $121), | |
210 | + (KeyCode: Ord('G'); Normal: Ord('g'); Shift: Ord('G'); Ctrl: $7; Alt: $122), | |
211 | + (KeyCode: Ord('H'); Normal: Ord('h'); Shift: Ord('H'); Ctrl: $8; Alt: $123), | |
212 | + (KeyCode: Ord('I'); Normal: Ord('i'); Shift: Ord('I'); Ctrl: $9; Alt: $117), | |
213 | + (KeyCode: Ord('J'); Normal: Ord('j'); Shift: Ord('J'); Ctrl: $A; Alt: $124), | |
214 | + (KeyCode: Ord('K'); Normal: Ord('k'); Shift: Ord('K'); Ctrl: $B; Alt: $125), | |
215 | + (KeyCode: Ord('L'); Normal: Ord('l'); Shift: Ord('L'); Ctrl: $C; Alt: $126), | |
216 | + (KeyCode: Ord('M'); Normal: Ord('m'); Shift: Ord('M'); Ctrl: $D; Alt: $132), | |
217 | + (KeyCode: Ord('N'); Normal: Ord('n'); Shift: Ord('N'); Ctrl: $E; Alt: $131), | |
218 | + (KeyCode: Ord('O'); Normal: Ord('o'); Shift: Ord('O'); Ctrl: $F; Alt: $118), | |
219 | + (KeyCode: Ord('P'); Normal: Ord('p'); Shift: Ord('P'); Ctrl: $10; Alt: $119), | |
220 | + (KeyCode: Ord('Q'); Normal: Ord('q'); Shift: Ord('Q'); Ctrl: $11; Alt: $110), | |
221 | + (KeyCode: Ord('R'); Normal: Ord('r'); Shift: Ord('R'); Ctrl: $12; Alt: $113), | |
222 | + (KeyCode: Ord('S'); Normal: Ord('s'); Shift: Ord('S'); Ctrl: $13; Alt: $11F), | |
223 | + (KeyCode: Ord('T'); Normal: Ord('t'); Shift: Ord('T'); Ctrl: $14; Alt: $114), | |
224 | + (KeyCode: Ord('U'); Normal: Ord('u'); Shift: Ord('U'); Ctrl: $15; Alt: $116), | |
225 | + (KeyCode: Ord('V'); Normal: Ord('v'); Shift: Ord('V'); Ctrl: $16; Alt: $12F), | |
226 | + (KeyCode: Ord('W'); Normal: Ord('w'); Shift: Ord('W'); Ctrl: $17; Alt: $111), | |
227 | + (KeyCode: Ord('X'); Normal: Ord('x'); Shift: Ord('X'); Ctrl: $18; Alt: $12D), | |
228 | + (KeyCode: Ord('Y'); Normal: Ord('y'); Shift: Ord('Y'); Ctrl: $19; Alt: $115), | |
229 | + (KeyCode: Ord('Z'); Normal: Ord('z'); Shift: Ord('Z'); Ctrl: $1A; Alt: $12C), | |
230 | + (KeyCode: VK_PRIOR; Normal: $149; Shift: $149; Ctrl: $184; Alt: $199), | |
231 | + (KeyCode: VK_NEXT; Normal: $151; Shift: $151; Ctrl: $176; Alt: $1A1), | |
232 | + (KeyCode: VK_END; Normal: $14F; Shift: $14F; Ctrl: $175; Alt: $19F), | |
233 | + (KeyCode: VK_HOME; Normal: $147; Shift: $147; Ctrl: $177; Alt: $197), | |
234 | + (KeyCode: VK_LEFT; Normal: $14B; Shift: $14B; Ctrl: $173; Alt: $19B), | |
235 | + (KeyCode: VK_UP; Normal: $148; Shift: $148; Ctrl: $18D; Alt: $198), | |
236 | + (KeyCode: VK_RIGHT; Normal: $14D; Shift: $14D; Ctrl: $174; Alt: $19D), | |
237 | + (KeyCode: VK_DOWN; Normal: $150; Shift: $150; Ctrl: $191; Alt: $1A0), | |
238 | + (KeyCode: VK_INSERT; Normal: $152; Shift: $152; Ctrl: $192; Alt: $1A2), | |
239 | + (KeyCode: VK_DELETE; Normal: $153; Shift: $153; Ctrl: $193; Alt: $1A3), | |
240 | + (KeyCode: VK_NUMPAD0; Normal: Ord('0'); Shift: $152; Ctrl: $192; Alt: - 1), | |
241 | + (KeyCode: VK_NUMPAD1; Normal: Ord('1'); Shift: $14F; Ctrl: $175; Alt: - 1), | |
242 | + (KeyCode: VK_NUMPAD2; Normal: Ord('2'); Shift: $150; Ctrl: $191; Alt: - 1), | |
243 | + (KeyCode: VK_NUMPAD3; Normal: Ord('3'); Shift: $151; Ctrl: $176; Alt: - 1), | |
244 | + (KeyCode: VK_NUMPAD4; Normal: Ord('4'); Shift: $14B; Ctrl: $173; Alt: - 1), | |
245 | + (KeyCode: VK_NUMPAD5; Normal: Ord('5'); Shift: $14C; Ctrl: $18F; Alt: - 1), | |
246 | + (KeyCode: VK_NUMPAD6; Normal: Ord('6'); Shift: $14D; Ctrl: $174; Alt: - 1), | |
247 | + (KeyCode: VK_NUMPAD7; Normal: Ord('7'); Shift: $147; Ctrl: $177; Alt: - 1), | |
248 | + (KeyCode: VK_NUMPAD8; Normal: Ord('8'); Shift: $148; Ctrl: $18D; Alt: - 1), | |
249 | + (KeyCode: VK_NUMPAD9; Normal: Ord('9'); Shift: $149; Ctrl: $184; Alt: - 1), | |
250 | + (KeyCode: VK_MULTIPLY; Normal: Ord('*'); Shift: Ord('*'); Ctrl: $196; Alt: $137), | |
251 | + (KeyCode: VK_ADD; Normal: Ord('+'); Shift: Ord('+'); Ctrl: $190; Alt: $14E), | |
252 | + (KeyCode: VK_SUBTRACT; Normal: Ord('-'); Shift: Ord('-'); Ctrl: $18E; Alt: $14A), | |
253 | + (KeyCode: VK_DECIMAL; Normal: Ord('.'); Shift: Ord('.'); Ctrl: $153; Alt: $193), | |
254 | + (KeyCode: VK_DIVIDE; Normal: Ord('/'); Shift: Ord('/'); Ctrl: $195; Alt: $1A4), | |
255 | + (KeyCode: VK_F1; Normal: $13B; Shift: $154; Ctrl: $15E; Alt: $168), | |
256 | + (KeyCode: VK_F2; Normal: $13C; Shift: $155; Ctrl: $15F; Alt: $169), | |
257 | + (KeyCode: VK_F3; Normal: $13D; Shift: $156; Ctrl: $160; Alt: $16A), | |
258 | + (KeyCode: VK_F4; Normal: $13E; Shift: $157; Ctrl: $161; Alt: $16B), | |
259 | + (KeyCode: VK_F5; Normal: $13F; Shift: $158; Ctrl: $162; Alt: $16C), | |
260 | + (KeyCode: VK_F6; Normal: $140; Shift: $159; Ctrl: $163; Alt: $16D), | |
261 | + (KeyCode: VK_F7; Normal: $141; Shift: $15A; Ctrl: $164; Alt: $16E), | |
262 | + (KeyCode: VK_F8; Normal: $142; Shift: $15B; Ctrl: $165; Alt: $16F), | |
263 | + (KeyCode: VK_F9; Normal: $143; Shift: $15C; Ctrl: $166; Alt: $170), | |
264 | + (KeyCode: VK_F10; Normal: $144; Shift: $15D; Ctrl: $167; Alt: $171), | |
265 | + (KeyCode: VK_F11; Normal: $185; Shift: $187; Ctrl: $189; Alt: $18B), | |
266 | + (KeyCode: VK_F12; Normal: $186; Shift: $188; Ctrl: $18A; Alt: $18C), | |
267 | + (KeyCode: $DC; Normal: Ord('\'); Shift: Ord('|'); Ctrl: $1C; Alt: $12B), | |
268 | + (KeyCode: $BF; Normal: Ord('/'); Shift: Ord('?'); Ctrl: - 1; Alt: $135), | |
269 | + (KeyCode: $BD; Normal: Ord('-'); Shift: Ord('_'); Ctrl: $1F; Alt: $182), | |
270 | + (KeyCode: $BB; Normal: Ord('='); Shift: Ord('+'); Ctrl: - 1; Alt: $183), | |
271 | + (KeyCode: $DB; Normal: Ord('['); Shift: Ord('{'); Ctrl: $1B; Alt: $11A), | |
272 | + (KeyCode: $DD; Normal: Ord(']'); Shift: Ord('}'); Ctrl: $1D; Alt: $11B), | |
273 | + (KeyCode: $BA; Normal: Ord(';'); Shift: Ord(':'); Ctrl: - 1; Alt: $127), | |
274 | + (KeyCode: $DE; Normal: Ord(''''); Shift: Ord('"'); Ctrl: - 1; Alt: $128), | |
275 | + (KeyCode: $BC; Normal: Ord(','); Shift: Ord('<'); Ctrl: - 1; Alt: $133), | |
276 | + (KeyCode: $BE; Normal: Ord('.'); Shift: Ord('>'); Ctrl: - 1; Alt: $134), | |
277 | + (KeyCode: $C0; Normal: Ord('`'); Shift: Ord('~'); Ctrl: - 1; Alt: $129) | |
278 | + ); | |
279 | + | |
280 | +var | |
281 | + ExtendedChar: Char = #0; | |
282 | + | |
283 | +function FindKeyCode(KeyCode: Smallint): PKey; {$IFDEF INLINES}inline;{$ENDIF} | |
284 | +var | |
285 | + I: Integer; | |
286 | +begin | |
287 | + for I := 0 to High(CKeys) do | |
288 | + if CKeys[I].KeyCode = KeyCode then | |
289 | + begin | |
290 | + Result := @CKeys[I]; | |
291 | + Exit; | |
292 | + end; | |
293 | + Result := nil; | |
294 | +end; | |
295 | + | |
296 | +// This has a complexity of 11, because of the if else ladder. | |
297 | +// That bugs me a bit. Looking for something more elegant. | |
298 | +function TranslateKey(const Rec: TInputRecord; State: Integer; Key: PKey; KeyCode: Integer): Smallint; | |
299 | +begin | |
300 | + if State and (RIGHT_ALT_PRESSED or LEFT_ALT_PRESSED) <> 0 then | |
301 | + Result := Key^.Alt | |
302 | + else if State and (RIGHT_CTRL_PRESSED or LEFT_CTRL_PRESSED) <> 0 then | |
303 | + Result := Key^.Ctrl | |
304 | + else if State and SHIFT_PRESSED <> 0 then | |
305 | + Result := Key^.Shift | |
306 | + else if KeyCode in [Ord('A')..Ord('Z')] then | |
307 | + Result := Ord(Rec.Event.KeyEvent.AsciiChar) | |
308 | + else | |
309 | + Result := Key^.Normal; | |
310 | +end; | |
311 | + | |
312 | +function ConvertKey(const Rec: TInputRecord; Key: PKey): Smallint; | |
313 | + {$IFDEF INLINES}inline;{$ENDIF} | |
314 | +begin | |
315 | + if Assigned(Key) then | |
316 | + Result := TranslateKey(Rec, Rec.Event.KeyEvent.dwControlKeyState, | |
317 | + Key, Rec.Event.KeyEvent.wVirtualKeyCode) | |
318 | + else | |
319 | + Result := -1 | |
320 | +end; | |
321 | + | |
322 | +function ReadKey: Char; | |
323 | +var | |
324 | + InputRec: TInputRecord; | |
325 | + NumRead: Cardinal; | |
326 | + KeyMode: DWORD; | |
327 | + KeyCode: Smallint; | |
328 | +begin | |
329 | + if ExtendedChar <> #0 then | |
330 | + begin | |
331 | + Result := ExtendedChar; | |
332 | + ExtendedChar := #0; | |
333 | + Exit; | |
334 | + end | |
335 | + else | |
336 | + begin | |
337 | + Result := #$FF; | |
338 | + GetConsoleMode(StdIn, KeyMode); | |
339 | + SetConsoleMode(StdIn, 0); | |
340 | + repeat | |
341 | + ReadConsoleInput(StdIn, InputRec, 1, NumRead); | |
342 | + if (InputRec.EventType and KEY_EVENT <> 0) and | |
343 | + InputRec.Event.KeyEvent.bKeyDown then | |
344 | + begin | |
345 | + if InputRec.Event.KeyEvent.AsciiChar <> #0 then | |
346 | + begin | |
347 | + // From Delphi 2009 on, Result is WideChar | |
348 | + Result := Chr(Ord(InputRec.Event.KeyEvent.AsciiChar)); | |
349 | + Break; | |
350 | + end; | |
351 | + KeyCode := ConvertKey(InputRec, | |
352 | + FindKeyCode(InputRec.Event.KeyEvent.wVirtualKeyCode)); | |
353 | + if KeyCode > $FF then | |
354 | + begin | |
355 | + ExtendedChar := Chr(KeyCode and $FF); | |
356 | + Result := #0; | |
357 | + Break; | |
358 | + end; | |
359 | + end; | |
360 | + until False; | |
361 | + SetConsoleMode(StdIn, KeyMode); | |
362 | + end; | |
363 | +end; | |
364 | + | |
365 | +function KeyPressed: Boolean; | |
366 | +var | |
367 | + InputRecArray: array of TInputRecord; | |
368 | + NumRead: DWORD; | |
369 | + NumEvents: DWORD; | |
370 | + I: Integer; | |
371 | + KeyCode: Word; | |
372 | +begin | |
373 | + Result := False; | |
374 | + GetNumberOfConsoleInputEvents(StdIn, NumEvents); | |
375 | + if NumEvents = 0 then | |
376 | + Exit; | |
377 | + SetLength(InputRecArray, NumEvents); | |
378 | + PeekConsoleInput(StdIn, InputRecArray[0], NumEvents, NumRead); | |
379 | + for I := 0 to High(InputRecArray) do | |
380 | + begin | |
381 | + if (InputRecArray[I].EventType and Key_Event <> 0) and | |
382 | + InputRecArray[I].Event.KeyEvent.bKeyDown then | |
383 | + begin | |
384 | + KeyCode := InputRecArray[I].Event.KeyEvent.wVirtualKeyCode; | |
385 | + if not (KeyCode in [VK_SHIFT, VK_MENU, VK_CONTROL]) then | |
386 | + begin | |
387 | + if ConvertKey(InputRecArray[I], FindKeyCode(KeyCode)) <> -1 then | |
388 | + begin | |
389 | + Result := True; | |
390 | + Exit; | |
391 | + end; | |
392 | + end; | |
393 | + end; | |
394 | + end; | |
395 | +end; | |
396 | + | |
397 | +procedure TextColor(Color: Byte); | |
398 | +begin | |
399 | + LastMode := TextAttr; | |
400 | + TextAttr := (TextAttr and $F0) or (Color and $0F); | |
401 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
402 | +end; | |
403 | + | |
404 | +procedure TextBackground(Color: Byte); | |
405 | +begin | |
406 | + LastMode := TextAttr; | |
407 | + TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0); | |
408 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
409 | +end; | |
410 | + | |
411 | +procedure LowVideo; | |
412 | +begin | |
413 | + LastMode := TextAttr; | |
414 | + TextAttr := TextAttr and $F7; | |
415 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
416 | +end; | |
417 | + | |
418 | +procedure HighVideo; | |
419 | +begin | |
420 | + LastMode := TextAttr; | |
421 | + TextAttr := TextAttr or $08; | |
422 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
423 | +end; | |
424 | + | |
425 | +procedure NormVideo; | |
426 | +begin | |
427 | + TextAttr := DefaultAttr; | |
428 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
429 | +end; | |
430 | + | |
431 | +// The following functions are independent of TextWindow. | |
432 | + | |
433 | +function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF} | |
434 | +var | |
435 | + BufferInfo: TConsoleScreenBufferInfo; | |
436 | +begin | |
437 | + GetConsoleSCreenBufferInfo(StdOut, BufferInfo); | |
438 | + Result := BufferInfo.dwCursorPosition.X; | |
439 | +end; | |
440 | + | |
441 | +function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF} | |
442 | +var | |
443 | + BufferInfo: TConsoleScreenBufferInfo; | |
444 | +begin | |
445 | + GetConsoleSCreenBufferInfo(StdOut, BufferInfo); | |
446 | + Result := BufferInfo.dwCursorPosition.Y; | |
447 | +end; | |
448 | + | |
449 | +procedure SetCursorPos(X, Y: Smallint); | |
450 | +var | |
451 | + NewPos: TCoord; | |
452 | +begin | |
453 | + NewPos.X := X; | |
454 | + NewPos.Y := Y; | |
455 | + SetConsoleCursorPosition(StdOut, NewPos); | |
456 | +end; | |
457 | + | |
458 | +// The following functions are relative to TextWindow. | |
459 | + | |
460 | +procedure ClrScr; | |
461 | +var | |
462 | + StartPos: TCoord; | |
463 | + Len, NumWritten: DWORD; | |
464 | + I: Integer; | |
465 | +begin | |
466 | + if (TextWindow.Left = 0) and (TextWindow.Top = 0) and | |
467 | + (TextWindow.Right = BufferSize.X - 1) and | |
468 | + (TextWindow.Bottom = BufferSize.Y - 1) then | |
469 | + begin | |
470 | + StartPos.X := 0; | |
471 | + StartPos.Y := 0; | |
472 | + Len := BufferSize.X * BufferSize.Y; | |
473 | + FillConsoleOutputCharacterA(StdOut, ' ', Len, StartPos, NumWritten); | |
474 | + FillConsoleOutputAttribute(StdOut, TextAttr, Len, StartPos, NumWritten); | |
475 | + if NumWritten < Len then | |
476 | + begin | |
477 | + ScreenSize.X := ScreenWidth; | |
478 | + ScreenSize.Y := ScreenHeight; | |
479 | + end; | |
480 | + end | |
481 | + else | |
482 | + begin | |
483 | + Len := TextWindow.Right - TextWindow.Left + 1; | |
484 | + StartPos.X := TextWindow.Left; | |
485 | + for I := TextWindow.Top to TextWindow.Bottom do | |
486 | + begin | |
487 | + StartPos.Y := I; | |
488 | + FillConsoleOutputCharacterA(StdOut, ' ', Len, StartPos, NumWritten); | |
489 | + FillConsoleOutputAttribute(StdOut, TextAttr, Len, StartPos, NumWritten); | |
490 | + end; | |
491 | + end; | |
492 | + GotoXY(1, 1); | |
493 | +end; | |
494 | + | |
495 | +procedure GotoXY(X, Y: Smallint); | |
496 | +begin | |
497 | + Inc(X, TextWindow.Left - 1); | |
498 | + Inc(Y, TextWindow.Top - 1); | |
499 | + if (X >= TextWindow.Left) and (X <= TextWindow.Right) and | |
500 | + (Y >= TextWindow.Top) and (Y <= TextWindow.Bottom) then | |
501 | + SetCursorPos(X, Y); | |
502 | +end; | |
503 | + | |
504 | +procedure ClrEol; | |
505 | +var | |
506 | + Len: Integer; | |
507 | + Pos: TCoord; | |
508 | + NumWritten: DWORD; | |
509 | +begin | |
510 | + Len := TextWindow.Right - GetCursorX + 1; | |
511 | + Pos.X := GetCursorX; | |
512 | + Pos.Y := GetCursorY; | |
513 | + FillConsoleOutputCharacterA(StdOut, ' ', Len, Pos, NumWritten); | |
514 | + FillConsoleOutputAttribute(StdOut, TextAttr, Len, Pos, NumWritten); | |
515 | +end; | |
516 | + | |
517 | +procedure Scroll(Left, Top, Right, Bottom: Integer; Distance: Integer = 0); | |
518 | +var | |
519 | + Rect: TSmallRect; | |
520 | + Fill: TCharInfo; | |
521 | + NewPos: TCoord; | |
522 | +begin | |
523 | + Fill.AsciiChar := ' '; | |
524 | + Fill.Attributes := TextAttr; | |
525 | + if Distance = 0 then | |
526 | + Distance := Bottom - Top + 1; | |
527 | + Rect.Left := Left; | |
528 | + Rect.Right := Right; | |
529 | + Rect.Top := Top; | |
530 | + Rect.Bottom := Bottom; | |
531 | + NewPos.X := Left; | |
532 | + NewPos.Y := Top + Distance; | |
533 | + ScrollConsoleScreenBufferA(StdOut, Rect, @Rect, NewPos, Fill); | |
534 | +end; | |
535 | + | |
536 | +procedure InsLine; | |
537 | +begin | |
538 | + Scroll(TextWindow.Left, GetCursorY, | |
539 | + TextWindow.Right, TextWindow.Bottom, 1); | |
540 | +end; | |
541 | + | |
542 | +procedure DelLine; | |
543 | +begin | |
544 | + Scroll(TextWindow.Left, GetCursorY, | |
545 | + TextWindow.Right, TextWindow.Bottom, -1); | |
546 | +end; | |
547 | + | |
548 | +function Validate(X1, Y1, X2, Y2: Integer): Boolean; | |
549 | + {$IFDEF INLINES}inline;{$ENDIF} | |
550 | +begin | |
551 | + Result := (X1 < X2) and (Y1 < Y2) and | |
552 | + (X1 >= 0) and (X2 < BufferSize.X) and | |
553 | + (Y1 >= 0) and (Y2 < BufferSize.Y); | |
554 | +end; | |
555 | + | |
556 | +procedure WriteText(Line: PAnsiChar; Len: Integer); | |
557 | +var | |
558 | + NumWritten: DWORD; | |
559 | +begin | |
560 | + SetConsoleTextAttribute(StdOut, TextAttr); | |
561 | + WriteConsoleA(StdOut, Line, Len, NumWritten, nil); | |
562 | +end; | |
563 | + | |
564 | +// Replacement for TTextRec.InOutFunc and TTextRec.FlushFunc for the Output | |
565 | +// and ErrOutput pseudo-textfiles. | |
566 | +// This is generally only used if a text window is set, otherwise this is | |
567 | +// handled by the runtime library. | |
568 | +function NewTextOut(var T: TTextRec): Integer; | |
569 | +var | |
570 | + ReadPtr, WritePtr: PAnsiChar; | |
571 | + Line: AnsiString; | |
572 | + DistanceToEdge: Integer; | |
573 | + | |
574 | + // Moves cursor to start of line, updates DistanceToEdge. | |
575 | + procedure CarriageReturn; | |
576 | + begin | |
577 | + SetCursorPos(TextWindow.Left, GetCursorY); | |
578 | + DistanceToEdge := TextWindow.Right - TextWindow.Left + 1; | |
579 | + end; | |
580 | + | |
581 | + // Moves cursor down one line. If necessary, scrolls window. | |
582 | + procedure LineFeed; {$IFDEF INLINES}inline;{$ENDIF} | |
583 | + begin | |
584 | + if GetCursorY < TextWindow.Bottom then | |
585 | + SetCursorPos(GetCursorX, GetCursorY + 1) | |
586 | + else | |
587 | + Scroll(TextWindow.Left, TextWindow.Top, TextWindow.Right, | |
588 | + TextWindow.Bottom, -1); | |
589 | + end; | |
590 | + | |
591 | + // Store one char in write buffer. | |
592 | + procedure CharToWriteBuffer(C: AnsiChar); | |
593 | + begin | |
594 | + WritePtr^ := C; | |
595 | + Inc(WritePtr); | |
596 | + Dec(DistanceToEdge); | |
597 | + end; | |
598 | + | |
599 | + // True if at right edge of window. | |
600 | + function WriteLine: Boolean; | |
601 | + begin | |
602 | + WritePtr^ := #0; | |
603 | + WriteText(PAnsiChar(Line), WritePtr - PAnsiChar(Line)); | |
604 | + Result := DistanceToEdge = 0; | |
605 | + WritePtr := PAnsiChar(Line); | |
606 | + DistanceToEdge := TextWindow.Right - TextWindow.Left + 1; | |
607 | + end; | |
608 | + | |
609 | + // Converts tabs to spaces, since WriteConsole will do its own tabbing when | |
610 | + // it encounters a #9, which is of course independent of this unit's | |
611 | + // TextWindow settings. | |
612 | + procedure ProcessTab; | |
613 | + var | |
614 | + Num, I: Integer; | |
615 | + begin | |
616 | + Num := 8 - (WritePtr - PAnsiChar(Line)) mod 8; | |
617 | + if Num > DistanceToEdge then | |
618 | + Num := DistanceToEdge; | |
619 | + for I := 1 to Num do | |
620 | + CharToWriteBuffer(' '); | |
621 | + end; | |
622 | + | |
623 | +begin | |
624 | + SetLength(Line, BufferSize.X); // Line only contains one line of windowed text. | |
625 | + WritePtr := PAnsiChar(Line); | |
626 | + ReadPtr := T.BufPtr; | |
627 | + DistanceToEdge := TextWindow.Right - GetCursorX + 1; | |
628 | + while T.BufPos > 0 do | |
629 | + begin | |
630 | + while (T.BufPos > 0) and (DistanceToEdge > 0) do | |
631 | + begin | |
632 | + case ReadPtr^ of | |
633 | + #7: Windows.Beep(800, 200); // this is what my internal speaker uses. | |
634 | + #8: begin | |
635 | + Dec(WritePtr); | |
636 | + Inc(DistanceToEdge); | |
637 | + end; | |
638 | + #9: ProcessTab; | |
639 | + // LineFeed is not just a line feed, it takes the function of #13#10 | |
640 | + #10: begin | |
641 | + WriteLine; | |
642 | + CarriageReturn; | |
643 | + LineFeed; | |
644 | + end; | |
645 | + #13: begin | |
646 | + WriteLine; | |
647 | + CarriageReturn; | |
648 | + end; | |
649 | + else | |
650 | + CharToWriteBuffer(ReadPtr^); | |
651 | + end; | |
652 | + Inc(ReadPtr); | |
653 | + Dec(T.BufPos); | |
654 | + end; | |
655 | + if WriteLine then | |
656 | + begin | |
657 | + CarriageReturn; | |
658 | + // If TexWindow.Right is at the edge of the screen, WriteConsole will | |
659 | + // already do a linefeed. | |
660 | + if TextWindow.Right <> ScreenWidth - 1 then | |
661 | + LineFeed; | |
662 | + end; | |
663 | + end; | |
664 | + Result := 0; | |
665 | +end; | |
666 | + | |
667 | +var | |
668 | + OldInOutFunc: Pointer; | |
669 | + OldFlushFunc: Pointer; | |
670 | + | |
671 | +procedure Window(Left, Top, Right, Bottom: Integer); | |
672 | +begin | |
673 | + Dec(Left); | |
674 | + Dec(Top); | |
675 | + Dec(Right); | |
676 | + Dec(Bottom); | |
677 | + if Validate(Left, Top, Right, Bottom) then | |
678 | + begin | |
679 | + TextWindow.Left := Left; | |
680 | + TextWindow.Top := Top; | |
681 | + TextWindow.Right := Right; | |
682 | + TextWindow.Bottom := Bottom; | |
683 | + if (Left > 0) or (Top > 0) or | |
684 | + (Right < BufferSize.X - 1) or (Bottom < BufferSize.Y - 1) then | |
685 | + // Text must be contained in window | |
686 | + begin | |
687 | + OldInOutFunc := TTextRec(Output).InOutFunc; | |
688 | + OldFlushFunc := TTextRec(Output).FlushFunc; | |
689 | + TTextRec(Output).InOutFunc := @NewTextOut; | |
690 | + TTextRec(Output).FlushFunc := @NewTextOut; | |
691 | + SetCursorPos(Left, Top); | |
692 | + end; | |
693 | + end | |
694 | + else | |
695 | + begin | |
696 | + TextWindow.Left := 0; | |
697 | + TextWindow.Right := BufferSize.X - 1; | |
698 | + TextWindow.Top := 0; | |
699 | + TextWindow.Bottom := BufferSize.Y - 1; | |
700 | + SetCursorPos(0, 0); | |
701 | + if Assigned(OldInOutFunc) then | |
702 | + begin | |
703 | + TTextRec(Output).InOutFunc := OldInOutFunc; | |
704 | + OldInOutFunc := nil; | |
705 | + end; | |
706 | + if Assigned(OldFlushFunc) then | |
707 | + begin | |
708 | + TTextRec(Output).FlushFunc := OldFlushFunc; | |
709 | + OldFlushFunc := nil; | |
710 | + end; | |
711 | + end; | |
712 | + WindMin := (TextWindow.Left and $FF) or (TextWindow.Top and $FF) shl 8; | |
713 | + WindMax := (TextWindow.Right and $FF) or (TextWindow.Bottom and $FF) shl 8; | |
714 | +end; | |
715 | + | |
716 | +procedure HardwareSound(Frequency: Smallint); | |
717 | +asm | |
718 | + CMP AX,37 | |
719 | + JB @@1 | |
720 | + MOV CX,AX | |
721 | + MOV AL,$B6 | |
722 | + OUT $43,AL | |
723 | + MOV AX,$3540 | |
724 | + MOV DX,$0012 | |
725 | + DIV CX | |
726 | + OUT $42,AL | |
727 | + MOV AL,AH | |
728 | + OUT $42,AL | |
729 | + MOV AL,3 | |
730 | + OUT $61,AL | |
731 | +@@1: | |
732 | +end; | |
733 | + | |
734 | +procedure HardwareNoSound; | |
735 | +asm | |
736 | + MOV AL,0 | |
737 | + OUT $61,AL | |
738 | +end; | |
739 | + | |
740 | +procedure HardwareDelay(Millisecs: Integer); | |
741 | +begin | |
742 | + Sleep(Millisecs); | |
743 | +end; | |
744 | + | |
745 | +procedure HardwareBeep(Frequency, Duration: Smallint); | |
746 | +begin | |
747 | + Sound(Frequency); | |
748 | + Delay(Duration); | |
749 | + NoSound; | |
750 | +end; | |
751 | + | |
752 | +type | |
753 | + TSoundState = (ssPending, ssPlaying, ssFreed); | |
754 | + | |
755 | +var | |
756 | + CurrentFrequency: Integer; | |
757 | + SoundState: TSoundState; | |
758 | + | |
759 | +// On Windows NT and later, direct port access is prohibited, so there is | |
760 | +// no way to use HardwareSound and HardwareNoSound. | |
761 | +// | |
762 | +// Since probably every note played by Sound will be delimited by some kind | |
763 | +// of Delay, the playing of the note is deferred to Delay. Sound only stores | |
764 | +// the frequency and sets the SoundState to ssPending. Delay now knows both | |
765 | +// parameters, and can use Windows.Beep. | |
766 | +// | |
767 | +// Note that such code is not reentrant. | |
768 | + | |
769 | +procedure SoftwareSound(Frequency: Smallint); | |
770 | +begin | |
771 | + // $123540 div Frequency must be <= $7FFF, so Frequency must be >= 37. | |
772 | + if Frequency >= 37 then | |
773 | + begin | |
774 | + CurrentFrequency := Frequency; | |
775 | + SoundState := ssPending; | |
776 | + end; | |
777 | +end; | |
778 | + | |
779 | +procedure SoftwareDelay(Millisecs: Integer); | |
780 | +begin | |
781 | + if SoundState = ssPending then | |
782 | + begin | |
783 | + SoundState := ssPlaying; | |
784 | + Windows.Beep(CurrentFrequency, MilliSecs); | |
785 | + SoundState := ssFreed; | |
786 | + end | |
787 | + else | |
788 | + Sleep(MilliSecs); | |
789 | +end; | |
790 | + | |
791 | +procedure SoftwareBeep(Frequency, Duration: Smallint); | |
792 | +begin | |
793 | + if Frequency >= 37 then | |
794 | + begin | |
795 | + SoundState := ssPlaying; | |
796 | + Windows.Beep(Frequency, Duration); | |
797 | + SoundState := ssFreed; | |
798 | + end; | |
799 | +end; | |
800 | + | |
801 | +procedure SoftwareNoSound; | |
802 | +begin | |
803 | + Windows.Beep(CurrentFrequency, 0); | |
804 | + SoundState := ssFreed; | |
805 | +end; | |
806 | + | |
807 | +function WhereX: Integer; | |
808 | +begin | |
809 | + Result := GetCursorX - TextWindow.Left + 1; | |
810 | +end; | |
811 | + | |
812 | +function WhereY: Integer; | |
813 | +begin | |
814 | + Result := GetCursorY - TextWindow.Top + 1; | |
815 | +end; | |
816 | + | |
817 | +procedure GetScreenSizes(var Width, Height: Smallint); | |
818 | +var | |
819 | + BufferInfo: TConsoleScreenBufferInfo; | |
820 | +begin | |
821 | + GetConsoleScreenBufferInfo(StdOut, BufferInfo); | |
822 | + Width := BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1; | |
823 | + Height := BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1; | |
824 | +end; | |
825 | + | |
826 | +function ScreenWidth: Smallint; | |
827 | +var | |
828 | + Height: Smallint; | |
829 | +begin | |
830 | + GetScreenSizes(Result, Height); | |
831 | +end; | |
832 | + | |
833 | +function ScreenHeight: Smallint; | |
834 | +var | |
835 | + Width: Smallint; | |
836 | +begin | |
837 | + GetScreenSizes(Width, Result); | |
838 | +end; | |
839 | + | |
840 | +procedure GetBufferSizes(var Width, Height: Smallint); | |
841 | +var | |
842 | + BufferInfo: TConsoleScreenBufferInfo; | |
843 | +begin | |
844 | + GetConsoleScreenBufferInfo(StdOut, BufferInfo); | |
845 | + Width := BufferInfo.dwSize.X; | |
846 | + Height := BufferInfo.dwSize.Y; | |
847 | +end; | |
848 | + | |
849 | +function BufferWidth: Smallint; | |
850 | +var | |
851 | + Height: Smallint; | |
852 | +begin | |
853 | + GetBufferSizes(Result, Height); | |
854 | +end; | |
855 | + | |
856 | +function BufferHeight: Smallint; | |
857 | +var | |
858 | + Width: Smallint; | |
859 | +begin | |
860 | + GetBufferSizes(Width, Result); | |
861 | +end; | |
862 | + | |
863 | +function TextColor: Byte; | |
864 | +begin | |
865 | + Result := TextAttr and $0F; | |
866 | +end; | |
867 | + | |
868 | +function TextBackground: Byte; | |
869 | +begin | |
870 | + Result := (TextAttr and $F0) shr 4; | |
871 | +end; | |
872 | + | |
873 | +procedure TextMode(Mode: Word); | |
874 | +begin | |
875 | + Window(0, 0, 0, 0); | |
876 | + NormVideo; | |
877 | +end; | |
878 | + | |
879 | +function Pause(const Msg: string = ''): Char; | |
880 | +begin | |
881 | + if Msg = '' then | |
882 | + Write('Press any key... ') | |
883 | + else | |
884 | + Write(Msg); | |
885 | + Result := ReadKey; | |
886 | +end; | |
887 | + | |
888 | +procedure InitScreenMode; | |
889 | +var | |
890 | + BufferInfo: TConsoleScreenBufferInfo; | |
891 | +begin | |
892 | + Reset(Input); | |
893 | + Rewrite(Output); | |
894 | + StdIn := TTextRec(Input).Handle; | |
895 | + StdOut := TTextRec(Output).Handle; | |
896 | +{$IFDEF HASERROUTPUT} | |
897 | + Rewrite(ErrOutput); | |
898 | + StdErr := TTextRec(ErrOutput).Handle; | |
899 | +{$ELSE} | |
900 | + StdErr := GetStdHandle(STD_ERROR_HANDLE); | |
901 | +{$ENDIF} | |
902 | + if not GetConsoleScreenBufferInfo(StdOut, BufferInfo) then | |
903 | + begin | |
904 | + SetInOutRes(GetLastError); | |
905 | + Exit; | |
906 | + end; | |
907 | + TextWindow.Left := 0; | |
908 | + TextWindow.Top := 0; | |
909 | + TextWindow.Right := BufferInfo.dwSize.X - 1; | |
910 | + TextWindow.Bottom := BufferInfo.dwSize.Y - 1; | |
911 | + TextAttr := BufferInfo.wAttributes and $FF; | |
912 | + DefaultAttr := TextAttr; | |
913 | + BufferSize := BufferInfo.dwSize; | |
914 | + ScreenSize.X := BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1; | |
915 | + ScreenSize.Y := BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1; | |
916 | + WindMin := 0; | |
917 | + WindMax := (ScreenSize.X and $FF) or (ScreenSize.Y and $FF) shl 8; | |
918 | + LastMode := CO80; | |
919 | + OldInOutFunc := nil; | |
920 | + OldFlushFunc := nil; | |
921 | + if Win32Platform = VER_PLATFORM_WIN32_NT then | |
922 | + begin | |
923 | + Sound := SoftwareSound; | |
924 | + NoSound := SoftwareNoSound; | |
925 | + Delay := SoftwareDelay; | |
926 | + Beep := SoftwareBeep; | |
927 | + end | |
928 | + else | |
929 | + begin | |
930 | + Sound := HardwareSound; | |
931 | + NoSound := HardwareNoSound; | |
932 | + Delay := HardwareDelay; | |
933 | + Beep := HardwareBeep; | |
934 | + end; | |
935 | +end; | |
936 | + | |
937 | +initialization | |
938 | + InitScreenMode; | |
939 | + | |
940 | +end. | |
15 | 941 | |
16 | -unit Velthuis.Console; | |
17 | - | |
18 | -{$IFDEF CONDITIONALEXPRESSIONS} | |
19 | - {$IF CompilerVersion >= 17.0} | |
20 | - {$DEFINE INLINES} | |
21 | - {$IFEND} | |
22 | - {$IF RTLVersion >= 14.0} | |
23 | - {$DEFINE HASERROUTPUT} | |
24 | - {$IFEND} | |
25 | -{$ENDIF} | |
26 | - | |
27 | -interface | |
28 | - | |
29 | -uses Windows; | |
30 | - | |
31 | -const | |
32 | - // Background and foreground colors | |
33 | - Black = 0; | |
34 | - Blue = 1; | |
35 | - Green = 2; | |
36 | - Cyan = 3; | |
37 | - Red = 4; | |
38 | - Magenta = 5; | |
39 | - Brown = 6; | |
40 | - LightGray = 7; | |
41 | - | |
42 | - // Foreground colors | |
43 | - DarkGray = 8; | |
44 | - LightBlue = 9; | |
45 | - LightGreen = 10; | |
46 | - LightCyan = 11; | |
47 | - LightRed = 12; | |
48 | - LightMagenta = 13; | |
49 | - Yellow = 14; | |
50 | - White = 15; | |
51 | - | |
52 | - // Blink attribute, to be or-ed with background colors. | |
53 | - Blink = 128; | |
54 | - | |
55 | - // Text modes: | |
56 | - BW40 = 0; // 40x25 B/W on Color Adapter | |
57 | - CO40 = 1; // 40x25 Color on Color Adapter | |
58 | - BW80 = 2; // 80x25 B/W on Color Adapter | |
59 | - CO80 = 3; // 80x25 Color on Color Adapter | |
60 | - Mono = 7; // 80x25 on Monochrome Adapter | |
61 | - Font8x8 = 256; // Add-in for ROM font | |
62 | - | |
63 | - // Mode constants for 3.0 compatibility of original CRT unit } | |
64 | - C40 = CO40; | |
65 | - C80 = CO80; | |
66 | - | |
67 | - | |
68 | -// Turbo/Borland Pascal Crt routines: | |
69 | - | |
70 | -// Waits for keypress and returns the key pressed. If the key is not an ASCII | |
71 | -// key, #0 is returned, and a successive ReadKey will give the extended key | |
72 | -// code of the key. | |
73 | -function ReadKey: Char; | |
74 | - | |
75 | -// Checks whether a key was pressed. | |
76 | -function KeyPressed: Boolean; | |
77 | - | |
78 | -// Puts the cursor at the given coordinates on the screen. | |
79 | -procedure GotoXY(X, Y: Smallint); | |
80 | - | |
81 | -// Returns the current X position of the cursor. | |
82 | -function WhereX: Integer; | |
83 | - | |
84 | -// Returns the current Y position of the cursor. | |
85 | -function WhereY: Integer; | |
86 | - | |
87 | -// Sets text foreground color. | |
88 | -procedure TextColor(Color: Byte); overload; | |
89 | - | |
90 | -// Gets text forground color. | |
91 | -function TextColor: Byte; overload; | |
92 | - | |
93 | -// Sets text background color. | |
94 | -procedure TextBackground(Color: Byte); overload; | |
95 | - | |
96 | -// Gets text background color. | |
97 | -function TextBackground: Byte; overload; | |
98 | - | |
99 | -// Sets text mode. | |
100 | -procedure TextMode(Mode: Word); | |
101 | - | |
102 | -// Sets text colors to low intensity | |
103 | -procedure LowVideo; | |
104 | - | |
105 | -// Sets text colors to high intensity | |
106 | -procedure HighVideo; | |
107 | - | |
108 | -// Sets text attribute to value at startup. | |
109 | -procedure NormVideo; | |
110 | - | |
111 | -// Clears the entire screen, or, if a window is set, the entire window, | |
112 | -// in the current background color. | |
113 | -procedure ClrScr; | |
114 | - | |
115 | -// Clears until the end of the line, in the current background color. | |
116 | -procedure ClrEol; | |
117 | - | |
118 | -// Inserts a line at the current cursor position. | |
119 | -procedure InsLine; | |
120 | - | |
121 | -// Deletes the line at the current cursor position. | |
122 | -procedure DelLine; | |
123 | - | |
124 | -// Sets a window, into which all successive output will go. You can reset the | |
125 | -// window to full screen by calling Window with a zero or negative value | |
126 | -// for Left. | |
127 | -procedure Window(Left, Top, Right, Bottom: Integer); | |
128 | - | |
129 | -// Displays message and waits for the next key press. Displays key | |
130 | -// and returns. | |
131 | -function Pause(const Msg: string = ''): Char; | |
132 | - | |
133 | -type | |
134 | - // Plays a sound at the given frequency (in Herz). | |
135 | - TSoundProc = procedure(Frequency: Smallint); | |
136 | - | |
137 | - // Stops the sound started with Sound. | |
138 | - TNoSoundProc = procedure; | |
139 | - | |
140 | - // Delays for the given amount of milliseconds, or as close as possible. | |
141 | - TDelayProc = procedure(Millisecs: Integer); | |
142 | - | |
143 | - // Plays a sound at the given frequency (in Hz) and duration (in ms). | |
144 | - TBeepProc = procedure(Frequency, Duration: Smallint); | |
145 | - | |
146 | -var | |
147 | - Sound: TSoundProc; | |
148 | - NoSound: TNoSoundProc; | |
149 | - Delay: TDelayProc; | |
150 | - Beep: TBeepProc; | |
151 | - | |
152 | -// Additional routines: | |
153 | - | |
154 | -function ScreenWidth: Smallint; | |
155 | -function ScreenHeight: Smallint; | |
156 | -function BufferWidth: Smallint; | |
157 | -function BufferHeight: Smallint; | |
158 | - | |
159 | -var | |
160 | - TextWindow: TSmallRect; | |
161 | - TextAttr: Byte; | |
162 | - DefaultAttr: Byte; | |
163 | - ScreenMode: Byte; | |
164 | - BufferSize: TCoord; | |
165 | - ScreenSize: TCoord; | |
166 | - StdIn, StdOut: THandle; | |
167 | - StdErr: THandle; | |
168 | - LastMode: Word; | |
169 | - WindMin: Word; | |
170 | - WindMax: Word; | |
171 | - CheckBreak: Boolean; | |
172 | - | |
173 | -implementation | |
174 | - | |
175 | -uses SysUtils; | |
176 | - | |
177 | -type | |
178 | - PKey = ^TKey; | |
179 | - TKey = record | |
180 | - KeyCode: Smallint; | |
181 | - Normal: Smallint; | |
182 | - Shift: Smallint; | |
183 | - Ctrl: Smallint; | |
184 | - Alt: Smallint; | |
185 | - end; | |
186 | - | |
187 | -const | |
188 | - CKeys: array[0..88] of TKey = ( | |
189 | - (KeyCode: VK_BACK; Normal: $8; Shift: $8; Ctrl: $7F; Alt: $10E; ), | |
190 | - (KeyCode: VK_TAB; Normal: $9; Shift: $10F; Ctrl: $194; Alt: $1A5; ), | |
191 | - (KeyCode: VK_RETURN; Normal: $D; Shift: $D; Ctrl: $A; Alt: $1A6), | |
192 | - (KeyCode: VK_ESCAPE; Normal: $1B; Shift: $1B; Ctrl: $1B; Alt: $101), | |
193 | - (KeyCode: VK_SPACE; Normal: $20; Shift: $20; Ctrl: $103; Alt: $20), | |
194 | - (KeyCode: Ord('0'); Normal: Ord('0'); Shift: Ord(')'); Ctrl: - 1; Alt: $181), | |
195 | - (KeyCode: Ord('1'); Normal: Ord('1'); Shift: Ord('!'); Ctrl: - 1; Alt: $178), | |
196 | - (KeyCode: Ord('2'); Normal: Ord('2'); Shift: Ord('@'); Ctrl: $103; Alt: $179), | |
197 | - (KeyCode: Ord('3'); Normal: Ord('3'); Shift: Ord('#'); Ctrl: - 1; Alt: $17A), | |
198 | - (KeyCode: Ord('4'); Normal: Ord('4'); Shift: Ord('$'); Ctrl: - 1; Alt: $17B), | |
199 | - (KeyCode: Ord('5'); Normal: Ord('5'); Shift: Ord('%'); Ctrl: - 1; Alt: $17C), | |
200 | - (KeyCode: Ord('6'); Normal: Ord('6'); Shift: Ord('^'); Ctrl: $1E; Alt: $17D), | |
201 | - (KeyCode: Ord('7'); Normal: Ord('7'); Shift: Ord('&'); Ctrl: - 1; Alt: $17E), | |
202 | - (KeyCode: Ord('8'); Normal: Ord('8'); Shift: Ord('*'); Ctrl: - 1; Alt: $17F), | |
203 | - (KeyCode: Ord('9'); Normal: Ord('9'); Shift: Ord('('); Ctrl: - 1; Alt: $180), | |
204 | - (KeyCode: Ord('A'); Normal: Ord('a'); Shift: Ord('A'); Ctrl: $1; Alt: $11E), | |
205 | - (KeyCode: Ord('B'); Normal: Ord('b'); Shift: Ord('B'); Ctrl: $2; Alt: $130), | |
206 | - (KeyCode: Ord('C'); Normal: Ord('c'); Shift: Ord('C'); Ctrl: $3; Alt: $12E), | |
207 | - (KeyCode: Ord('D'); Normal: Ord('d'); Shift: Ord('D'); Ctrl: $4; Alt: $120), | |
208 | - (KeyCode: Ord('E'); Normal: Ord('e'); Shift: Ord('E'); Ctrl: $5; Alt: $112), | |
209 | - (KeyCode: Ord('F'); Normal: Ord('f'); Shift: Ord('F'); Ctrl: $6; Alt: $121), | |
210 | - (KeyCode: Ord('G'); Normal: Ord('g'); Shift: Ord('G'); Ctrl: $7; Alt: $122), | |
211 | - (KeyCode: Ord('H'); Normal: Ord('h'); Shift: Ord('H'); Ctrl: $8; Alt: $123), | |
212 | - (KeyCode: Ord('I'); Normal: Ord('i'); Shift: Ord('I'); Ctrl: $9; Alt: $117), | |
213 | - (KeyCode: Ord('J'); Normal: Ord('j'); Shift: Ord('J'); Ctrl: $A; Alt: $124), | |
214 | - (KeyCode: Ord('K'); Normal: Ord('k'); Shift: Ord('K'); Ctrl: $B; Alt: $125), | |
215 | - (KeyCode: Ord('L'); Normal: Ord('l'); Shift: Ord('L'); Ctrl: $C; Alt: $126), | |
216 | - (KeyCode: Ord('M'); Normal: Ord('m'); Shift: Ord('M'); Ctrl: $D; Alt: $132), | |
217 | - (KeyCode: Ord('N'); Normal: Ord('n'); Shift: Ord('N'); Ctrl: $E; Alt: $131), | |
218 | - (KeyCode: Ord('O'); Normal: Ord('o'); Shift: Ord('O'); Ctrl: $F; Alt: $118), | |
219 | - (KeyCode: Ord('P'); Normal: Ord('p'); Shift: Ord('P'); Ctrl: $10; Alt: $119), | |
220 | - (KeyCode: Ord('Q'); Normal: Ord('q'); Shift: Ord('Q'); Ctrl: $11; Alt: $110), | |
221 | - (KeyCode: Ord('R'); Normal: Ord('r'); Shift: Ord('R'); Ctrl: $12; Alt: $113), | |
222 | - (KeyCode: Ord('S'); Normal: Ord('s'); Shift: Ord('S'); Ctrl: $13; Alt: $11F), | |
223 | - (KeyCode: Ord('T'); Normal: Ord('t'); Shift: Ord('T'); Ctrl: $14; Alt: $114), | |
224 | - (KeyCode: Ord('U'); Normal: Ord('u'); Shift: Ord('U'); Ctrl: $15; Alt: $116), | |
225 | - (KeyCode: Ord('V'); Normal: Ord('v'); Shift: Ord('V'); Ctrl: $16; Alt: $12F), | |
226 | - (KeyCode: Ord('W'); Normal: Ord('w'); Shift: Ord('W'); Ctrl: $17; Alt: $111), | |
227 | - (KeyCode: Ord('X'); Normal: Ord('x'); Shift: Ord('X'); Ctrl: $18; Alt: $12D), | |
228 | - (KeyCode: Ord('Y'); Normal: Ord('y'); Shift: Ord('Y'); Ctrl: $19; Alt: $115), | |
229 | - (KeyCode: Ord('Z'); Normal: Ord('z'); Shift: Ord('Z'); Ctrl: $1A; Alt: $12C), | |
230 | - (KeyCode: VK_PRIOR; Normal: $149; Shift: $149; Ctrl: $184; Alt: $199), | |
231 | - (KeyCode: VK_NEXT; Normal: $151; Shift: $151; Ctrl: $176; Alt: $1A1), | |
232 | - (KeyCode: VK_END; Normal: $14F; Shift: $14F; Ctrl: $175; Alt: $19F), | |
233 | - (KeyCode: VK_HOME; Normal: $147; Shift: $147; Ctrl: $177; Alt: $197), | |
234 | - (KeyCode: VK_LEFT; Normal: $14B; Shift: $14B; Ctrl: $173; Alt: $19B), | |
235 | - (KeyCode: VK_UP; Normal: $148; Shift: $148; Ctrl: $18D; Alt: $198), | |
236 | - (KeyCode: VK_RIGHT; Normal: $14D; Shift: $14D; Ctrl: $174; Alt: $19D), | |
237 | - (KeyCode: VK_DOWN; Normal: $150; Shift: $150; Ctrl: $191; Alt: $1A0), | |
238 | - (KeyCode: VK_INSERT; Normal: $152; Shift: $152; Ctrl: $192; Alt: $1A2), | |
239 | - (KeyCode: VK_DELETE; Normal: $153; Shift: $153; Ctrl: $193; Alt: $1A3), | |
240 | - (KeyCode: VK_NUMPAD0; Normal: Ord('0'); Shift: $152; Ctrl: $192; Alt: - 1), | |
241 | - (KeyCode: VK_NUMPAD1; Normal: Ord('1'); Shift: $14F; Ctrl: $175; Alt: - 1), | |
242 | - (KeyCode: VK_NUMPAD2; Normal: Ord('2'); Shift: $150; Ctrl: $191; Alt: - 1), | |
243 | - (KeyCode: VK_NUMPAD3; Normal: Ord('3'); Shift: $151; Ctrl: $176; Alt: - 1), | |
244 | - (KeyCode: VK_NUMPAD4; Normal: Ord('4'); Shift: $14B; Ctrl: $173; Alt: - 1), | |
245 | - (KeyCode: VK_NUMPAD5; Normal: Ord('5'); Shift: $14C; Ctrl: $18F; Alt: - 1), | |
246 | - (KeyCode: VK_NUMPAD6; Normal: Ord('6'); Shift: $14D; Ctrl: $174; Alt: - 1), | |
247 | - (KeyCode: VK_NUMPAD7; Normal: Ord('7'); Shift: $147; Ctrl: $177; Alt: - 1), | |
248 | - (KeyCode: VK_NUMPAD8; Normal: Ord('8'); Shift: $148; Ctrl: $18D; Alt: - 1), | |
249 | - (KeyCode: VK_NUMPAD9; Normal: Ord('9'); Shift: $149; Ctrl: $184; Alt: - 1), | |
250 | - (KeyCode: VK_MULTIPLY; Normal: Ord('*'); Shift: Ord('*'); Ctrl: $196; Alt: $137), | |
251 | - (KeyCode: VK_ADD; Normal: Ord('+'); Shift: Ord('+'); Ctrl: $190; Alt: $14E), | |
252 | - (KeyCode: VK_SUBTRACT; Normal: Ord('-'); Shift: Ord('-'); Ctrl: $18E; Alt: $14A), | |
253 | - (KeyCode: VK_DECIMAL; Normal: Ord('.'); Shift: Ord('.'); Ctrl: $153; Alt: $193), | |
254 | - (KeyCode: VK_DIVIDE; Normal: Ord('/'); Shift: Ord('/'); Ctrl: $195; Alt: $1A4), | |
255 | - (KeyCode: VK_F1; Normal: $13B; Shift: $154; Ctrl: $15E; Alt: $168), | |
256 | - (KeyCode: VK_F2; Normal: $13C; Shift: $155; Ctrl: $15F; Alt: $169), | |
257 | - (KeyCode: VK_F3; Normal: $13D; Shift: $156; Ctrl: $160; Alt: $16A), | |
258 | - (KeyCode: VK_F4; Normal: $13E; Shift: $157; Ctrl: $161; Alt: $16B), | |
259 | - (KeyCode: VK_F5; Normal: $13F; Shift: $158; Ctrl: $162; Alt: $16C), | |
260 | - (KeyCode: VK_F6; Normal: $140; Shift: $159; Ctrl: $163; Alt: $16D), | |
261 | - (KeyCode: VK_F7; Normal: $141; Shift: $15A; Ctrl: $164; Alt: $16E), | |
262 | - (KeyCode: VK_F8; Normal: $142; Shift: $15B; Ctrl: $165; Alt: $16F), | |
263 | - (KeyCode: VK_F9; Normal: $143; Shift: $15C; Ctrl: $166; Alt: $170), | |
264 | - (KeyCode: VK_F10; Normal: $144; Shift: $15D; Ctrl: $167; Alt: $171), | |
265 | - (KeyCode: VK_F11; Normal: $185; Shift: $187; Ctrl: $189; Alt: $18B), | |
266 | - (KeyCode: VK_F12; Normal: $186; Shift: $188; Ctrl: $18A; Alt: $18C), | |
267 | - (KeyCode: $DC; Normal: Ord('\'); Shift: Ord('|'); Ctrl: $1C; Alt: $12B), | |
268 | - (KeyCode: $BF; Normal: Ord('/'); Shift: Ord('?'); Ctrl: - 1; Alt: $135), | |
269 | - (KeyCode: $BD; Normal: Ord('-'); Shift: Ord('_'); Ctrl: $1F; Alt: $182), | |
270 | - (KeyCode: $BB; Normal: Ord('='); Shift: Ord('+'); Ctrl: - 1; Alt: $183), | |
271 | - (KeyCode: $DB; Normal: Ord('['); Shift: Ord('{'); Ctrl: $1B; Alt: $11A), | |
272 | - (KeyCode: $DD; Normal: Ord(']'); Shift: Ord('}'); Ctrl: $1D; Alt: $11B), | |
273 | - (KeyCode: $BA; Normal: Ord(';'); Shift: Ord(':'); Ctrl: - 1; Alt: $127), | |
274 | - (KeyCode: $DE; Normal: Ord(''''); Shift: Ord('"'); Ctrl: - 1; Alt: $128), | |
275 | - (KeyCode: $BC; Normal: Ord(','); Shift: Ord('<'); Ctrl: - 1; Alt: $133), | |
276 | - (KeyCode: $BE; Normal: Ord('.'); Shift: Ord('>'); Ctrl: - 1; Alt: $134), | |
277 | - (KeyCode: $C0; Normal: Ord('`'); Shift: Ord('~'); Ctrl: - 1; Alt: $129) | |
278 | - ); | |
279 | - | |
280 | -var | |
281 | - ExtendedChar: Char = #0; | |
282 | - | |
283 | -function FindKeyCode(KeyCode: Smallint): PKey; {$IFDEF INLINES}inline;{$ENDIF} | |
284 | -var | |
285 | - I: Integer; | |
286 | -begin | |
287 | - for I := 0 to High(CKeys) do | |
288 | - if CKeys[I].KeyCode = KeyCode then | |
289 | - begin | |
290 | - Result := @CKeys[I]; | |
291 | - Exit; | |
292 | - end; | |
293 | - Result := nil; | |
294 | -end; | |
295 | - | |
296 | -// This has a complexity of 11, because of the if else ladder. | |
297 | -// That bugs me a bit. Looking for something more elegant. | |
298 | -function TranslateKey(const Rec: TInputRecord; State: Integer; Key: PKey; KeyCode: Integer): Smallint; | |
299 | -begin | |
300 | - if State and (RIGHT_ALT_PRESSED or LEFT_ALT_PRESSED) <> 0 then | |
301 | - Result := Key^.Alt | |
302 | - else if State and (RIGHT_CTRL_PRESSED or LEFT_CTRL_PRESSED) <> 0 then | |
303 | - Result := Key^.Ctrl | |
304 | - else if State and SHIFT_PRESSED <> 0 then | |
305 | - Result := Key^.Shift | |
306 | - else if KeyCode in [Ord('A')..Ord('Z')] then | |
307 | - Result := Ord(Rec.Event.KeyEvent.AsciiChar) | |
308 | - else | |
309 | - Result := Key^.Normal; | |
310 | -end; | |
311 | - | |
312 | -function ConvertKey(const Rec: TInputRecord; Key: PKey): Smallint; | |
313 | - {$IFDEF INLINES}inline;{$ENDIF} | |
314 | -begin | |
315 | - if Assigned(Key) then | |
316 | - Result := TranslateKey(Rec, Rec.Event.KeyEvent.dwControlKeyState, | |
317 | - Key, Rec.Event.KeyEvent.wVirtualKeyCode) | |
318 | - else | |
319 | - Result := -1 | |
320 | -end; | |
321 | - | |
322 | -function ReadKey: Char; | |
323 | -var | |
324 | - InputRec: TInputRecord; | |
325 | - NumRead: Cardinal; | |
326 | - KeyMode: DWORD; | |
327 | - KeyCode: Smallint; | |
328 | -begin | |
329 | - if ExtendedChar <> #0 then | |
330 | - begin | |
331 | - Result := ExtendedChar; | |
332 | - ExtendedChar := #0; | |
333 | - Exit; | |
334 | - end | |
335 | - else | |
336 | - begin | |
337 | - Result := #$FF; | |
338 | - GetConsoleMode(StdIn, KeyMode); | |
339 | - SetConsoleMode(StdIn, 0); | |
340 | - repeat | |
341 | - ReadConsoleInput(StdIn, InputRec, 1, NumRead); | |
342 | - if (InputRec.EventType and KEY_EVENT <> 0) and | |
343 | - InputRec.Event.KeyEvent.bKeyDown then | |
344 | - begin | |
345 | - if InputRec.Event.KeyEvent.AsciiChar <> #0 then | |
346 | - begin | |
347 | - // From Delphi 2009 on, Result is WideChar | |
348 | - Result := Chr(Ord(InputRec.Event.KeyEvent.AsciiChar)); | |
349 | - Break; | |
350 | - end; | |
351 | - KeyCode := ConvertKey(InputRec, | |
352 | - FindKeyCode(InputRec.Event.KeyEvent.wVirtualKeyCode)); | |
353 | - if KeyCode > $FF then | |
354 | - begin | |
355 | - ExtendedChar := Chr(KeyCode and $FF); | |
356 | - Result := #0; | |
357 | - Break; | |
358 | - end; | |
359 | - end; | |
360 | - until False; | |
361 | - SetConsoleMode(StdIn, KeyMode); | |
362 | - end; | |
363 | -end; | |
364 | - | |
365 | -function KeyPressed: Boolean; | |
366 | -var | |
367 | - InputRecArray: array of TInputRecord; | |
368 | - NumRead: DWORD; | |
369 | - NumEvents: DWORD; | |
370 | - I: Integer; | |
371 | - KeyCode: Word; | |
372 | -begin | |
373 | - Result := False; | |
374 | - GetNumberOfConsoleInputEvents(StdIn, NumEvents); | |
375 | - if NumEvents = 0 then | |
376 | - Exit; | |
377 | - SetLength(InputRecArray, NumEvents); | |
378 | - PeekConsoleInput(StdIn, InputRecArray[0], NumEvents, NumRead); | |
379 | - for I := 0 to High(InputRecArray) do | |
380 | - begin | |
381 | - if (InputRecArray[I].EventType and Key_Event <> 0) and | |
382 | - InputRecArray[I].Event.KeyEvent.bKeyDown then | |
383 | - begin | |
384 | - KeyCode := InputRecArray[I].Event.KeyEvent.wVirtualKeyCode; | |
385 | - if not (KeyCode in [VK_SHIFT, VK_MENU, VK_CONTROL]) then | |
386 | - begin | |
387 | - if ConvertKey(InputRecArray[I], FindKeyCode(KeyCode)) <> -1 then | |
388 | - begin | |
389 | - Result := True; | |
390 | - Exit; | |
391 | - end; | |
392 | - end; | |
393 | - end; | |
394 | - end; | |
395 | -end; | |
396 | - | |
397 | -procedure TextColor(Color: Byte); | |
398 | -begin | |
399 | - LastMode := TextAttr; | |
400 | - TextAttr := (TextAttr and $F0) or (Color and $0F); | |
401 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
402 | -end; | |
403 | - | |
404 | -procedure TextBackground(Color: Byte); | |
405 | -begin | |
406 | - LastMode := TextAttr; | |
407 | - TextAttr := (TextAttr and $0F) or ((Color shl 4) and $F0); | |
408 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
409 | -end; | |
410 | - | |
411 | -procedure LowVideo; | |
412 | -begin | |
413 | - LastMode := TextAttr; | |
414 | - TextAttr := TextAttr and $F7; | |
415 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
416 | -end; | |
417 | - | |
418 | -procedure HighVideo; | |
419 | -begin | |
420 | - LastMode := TextAttr; | |
421 | - TextAttr := TextAttr or $08; | |
422 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
423 | -end; | |
424 | - | |
425 | -procedure NormVideo; | |
426 | -begin | |
427 | - TextAttr := DefaultAttr; | |
428 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
429 | -end; | |
430 | - | |
431 | -// The following functions are independent of TextWindow. | |
432 | - | |
433 | -function GetCursorX: Integer; {$IFDEF INLINES}inline;{$ENDIF} | |
434 | -var | |
435 | - BufferInfo: TConsoleScreenBufferInfo; | |
436 | -begin | |
437 | - GetConsoleSCreenBufferInfo(StdOut, BufferInfo); | |
438 | - Result := BufferInfo.dwCursorPosition.X; | |
439 | -end; | |
440 | - | |
441 | -function GetCursorY: Integer; {$IFDEF INLINES}inline;{$ENDIF} | |
442 | -var | |
443 | - BufferInfo: TConsoleScreenBufferInfo; | |
444 | -begin | |
445 | - GetConsoleSCreenBufferInfo(StdOut, BufferInfo); | |
446 | - Result := BufferInfo.dwCursorPosition.Y; | |
447 | -end; | |
448 | - | |
449 | -procedure SetCursorPos(X, Y: Smallint); | |
450 | -var | |
451 | - NewPos: TCoord; | |
452 | -begin | |
453 | - NewPos.X := X; | |
454 | - NewPos.Y := Y; | |
455 | - SetConsoleCursorPosition(StdOut, NewPos); | |
456 | -end; | |
457 | - | |
458 | -// The following functions are relative to TextWindow. | |
459 | - | |
460 | -procedure ClrScr; | |
461 | -var | |
462 | - StartPos: TCoord; | |
463 | - Len, NumWritten: DWORD; | |
464 | - I: Integer; | |
465 | -begin | |
466 | - if (TextWindow.Left = 0) and (TextWindow.Top = 0) and | |
467 | - (TextWindow.Right = BufferSize.X - 1) and | |
468 | - (TextWindow.Bottom = BufferSize.Y - 1) then | |
469 | - begin | |
470 | - StartPos.X := 0; | |
471 | - StartPos.Y := 0; | |
472 | - Len := BufferSize.X * BufferSize.Y; | |
473 | - FillConsoleOutputCharacterA(StdOut, ' ', Len, StartPos, NumWritten); | |
474 | - FillConsoleOutputAttribute(StdOut, TextAttr, Len, StartPos, NumWritten); | |
475 | - if NumWritten < Len then | |
476 | - begin | |
477 | - ScreenSize.X := ScreenWidth; | |
478 | - ScreenSize.Y := ScreenHeight; | |
479 | - end; | |
480 | - end | |
481 | - else | |
482 | - begin | |
483 | - Len := TextWindow.Right - TextWindow.Left + 1; | |
484 | - StartPos.X := TextWindow.Left; | |
485 | - for I := TextWindow.Top to TextWindow.Bottom do | |
486 | - begin | |
487 | - StartPos.Y := I; | |
488 | - FillConsoleOutputCharacterA(StdOut, ' ', Len, StartPos, NumWritten); | |
489 | - FillConsoleOutputAttribute(StdOut, TextAttr, Len, StartPos, NumWritten); | |
490 | - end; | |
491 | - end; | |
492 | - GotoXY(1, 1); | |
493 | -end; | |
494 | - | |
495 | -procedure GotoXY(X, Y: Smallint); | |
496 | -begin | |
497 | - Inc(X, TextWindow.Left - 1); | |
498 | - Inc(Y, TextWindow.Top - 1); | |
499 | - if (X >= TextWindow.Left) and (X <= TextWindow.Right) and | |
500 | - (Y >= TextWindow.Top) and (Y <= TextWindow.Bottom) then | |
501 | - SetCursorPos(X, Y); | |
502 | -end; | |
503 | - | |
504 | -procedure ClrEol; | |
505 | -var | |
506 | - Len: Integer; | |
507 | - Pos: TCoord; | |
508 | - NumWritten: DWORD; | |
509 | -begin | |
510 | - Len := TextWindow.Right - GetCursorX + 1; | |
511 | - Pos.X := GetCursorX; | |
512 | - Pos.Y := GetCursorY; | |
513 | - FillConsoleOutputCharacterA(StdOut, ' ', Len, Pos, NumWritten); | |
514 | - FillConsoleOutputAttribute(StdOut, TextAttr, Len, Pos, NumWritten); | |
515 | -end; | |
516 | - | |
517 | -procedure Scroll(Left, Top, Right, Bottom: Integer; Distance: Integer = 0); | |
518 | -var | |
519 | - Rect: TSmallRect; | |
520 | - Fill: TCharInfo; | |
521 | - NewPos: TCoord; | |
522 | -begin | |
523 | - Fill.AsciiChar := ' '; | |
524 | - Fill.Attributes := TextAttr; | |
525 | - if Distance = 0 then | |
526 | - Distance := Bottom - Top + 1; | |
527 | - Rect.Left := Left; | |
528 | - Rect.Right := Right; | |
529 | - Rect.Top := Top; | |
530 | - Rect.Bottom := Bottom; | |
531 | - NewPos.X := Left; | |
532 | - NewPos.Y := Top + Distance; | |
533 | - ScrollConsoleScreenBufferA(StdOut, Rect, @Rect, NewPos, Fill); | |
534 | -end; | |
535 | - | |
536 | -procedure InsLine; | |
537 | -begin | |
538 | - Scroll(TextWindow.Left, GetCursorY, | |
539 | - TextWindow.Right, TextWindow.Bottom, 1); | |
540 | -end; | |
541 | - | |
542 | -procedure DelLine; | |
543 | -begin | |
544 | - Scroll(TextWindow.Left, GetCursorY, | |
545 | - TextWindow.Right, TextWindow.Bottom, -1); | |
546 | -end; | |
547 | - | |
548 | -function Validate(X1, Y1, X2, Y2: Integer): Boolean; | |
549 | - {$IFDEF INLINES}inline;{$ENDIF} | |
550 | -begin | |
551 | - Result := (X1 < X2) and (Y1 < Y2) and | |
552 | - (X1 >= 0) and (X2 < BufferSize.X) and | |
553 | - (Y1 >= 0) and (Y2 < BufferSize.Y); | |
554 | -end; | |
555 | - | |
556 | -procedure WriteText(Line: PAnsiChar; Len: Integer); | |
557 | -var | |
558 | - NumWritten: DWORD; | |
559 | -begin | |
560 | - SetConsoleTextAttribute(StdOut, TextAttr); | |
561 | - WriteConsoleA(StdOut, Line, Len, NumWritten, nil); | |
562 | -end; | |
563 | - | |
564 | -// Replacement for TTextRec.InOutFunc and TTextRec.FlushFunc for the Output | |
565 | -// and ErrOutput pseudo-textfiles. | |
566 | -// This is generally only used if a text window is set, otherwise this is | |
567 | -// handled by the runtime library. | |
568 | -function NewTextOut(var T: TTextRec): Integer; | |
569 | -var | |
570 | - ReadPtr, WritePtr: PAnsiChar; | |
571 | - Line: AnsiString; | |
572 | - DistanceToEdge: Integer; | |
573 | - | |
574 | - // Moves cursor to start of line, updates DistanceToEdge. | |
575 | - procedure CarriageReturn; | |
576 | - begin | |
577 | - SetCursorPos(TextWindow.Left, GetCursorY); | |
578 | - DistanceToEdge := TextWindow.Right - TextWindow.Left + 1; | |
579 | - end; | |
580 | - | |
581 | - // Moves cursor down one line. If necessary, scrolls window. | |
582 | - procedure LineFeed; {$IFDEF INLINES}inline;{$ENDIF} | |
583 | - begin | |
584 | - if GetCursorY < TextWindow.Bottom then | |
585 | - SetCursorPos(GetCursorX, GetCursorY + 1) | |
586 | - else | |
587 | - Scroll(TextWindow.Left, TextWindow.Top, TextWindow.Right, | |
588 | - TextWindow.Bottom, -1); | |
589 | - end; | |
590 | - | |
591 | - // Store one char in write buffer. | |
592 | - procedure CharToWriteBuffer(C: AnsiChar); | |
593 | - begin | |
594 | - WritePtr^ := C; | |
595 | - Inc(WritePtr); | |
596 | - Dec(DistanceToEdge); | |
597 | - end; | |
598 | - | |
599 | - // True if at right edge of window. | |
600 | - function WriteLine: Boolean; | |
601 | - begin | |
602 | - WritePtr^ := #0; | |
603 | - WriteText(PAnsiChar(Line), WritePtr - PAnsiChar(Line)); | |
604 | - Result := DistanceToEdge = 0; | |
605 | - WritePtr := PAnsiChar(Line); | |
606 | - DistanceToEdge := TextWindow.Right - TextWindow.Left + 1; | |
607 | - end; | |
608 | - | |
609 | - // Converts tabs to spaces, since WriteConsole will do its own tabbing when | |
610 | - // it encounters a #9, which is of course independent of this unit's | |
611 | - // TextWindow settings. | |
612 | - procedure ProcessTab; | |
613 | - var | |
614 | - Num, I: Integer; | |
615 | - begin | |
616 | - Num := 8 - (WritePtr - PAnsiChar(Line)) mod 8; | |
617 | - if Num > DistanceToEdge then | |
618 | - Num := DistanceToEdge; | |
619 | - for I := 1 to Num do | |
620 | - CharToWriteBuffer(' '); | |
621 | - end; | |
622 | - | |
623 | -begin | |
624 | - SetLength(Line, BufferSize.X); // Line only contains one line of windowed text. | |
625 | - WritePtr := PAnsiChar(Line); | |
626 | - ReadPtr := T.BufPtr; | |
627 | - DistanceToEdge := TextWindow.Right - GetCursorX + 1; | |
628 | - while T.BufPos > 0 do | |
629 | - begin | |
630 | - while (T.BufPos > 0) and (DistanceToEdge > 0) do | |
631 | - begin | |
632 | - case ReadPtr^ of | |
633 | - #7: Windows.Beep(800, 200); // this is what my internal speaker uses. | |
634 | - #8: begin | |
635 | - Dec(WritePtr); | |
636 | - Inc(DistanceToEdge); | |
637 | - end; | |
638 | - #9: ProcessTab; | |
639 | - // LineFeed is not just a line feed, it takes the function of #13#10 | |
640 | - #10: begin | |
641 | - WriteLine; | |
642 | - CarriageReturn; | |
643 | - LineFeed; | |
644 | - end; | |
645 | - #13: begin | |
646 | - WriteLine; | |
647 | - CarriageReturn; | |
648 | - end; | |
649 | - else | |
650 | - CharToWriteBuffer(ReadPtr^); | |
651 | - end; | |
652 | - Inc(ReadPtr); | |
653 | - Dec(T.BufPos); | |
654 | - end; | |
655 | - if WriteLine then | |
656 | - begin | |
657 | - CarriageReturn; | |
658 | - // If TexWindow.Right is at the edge of the screen, WriteConsole will | |
659 | - // already do a linefeed. | |
660 | - if TextWindow.Right <> ScreenWidth - 1 then | |
661 | - LineFeed; | |
662 | - end; | |
663 | - end; | |
664 | - Result := 0; | |
665 | -end; | |
666 | - | |
667 | -var | |
668 | - OldInOutFunc: Pointer; | |
669 | - OldFlushFunc: Pointer; | |
670 | - | |
671 | -procedure Window(Left, Top, Right, Bottom: Integer); | |
672 | -begin | |
673 | - Dec(Left); | |
674 | - Dec(Top); | |
675 | - Dec(Right); | |
676 | - Dec(Bottom); | |
677 | - if Validate(Left, Top, Right, Bottom) then | |
678 | - begin | |
679 | - TextWindow.Left := Left; | |
680 | - TextWindow.Top := Top; | |
681 | - TextWindow.Right := Right; | |
682 | - TextWindow.Bottom := Bottom; | |
683 | - if (Left > 0) or (Top > 0) or | |
684 | - (Right < BufferSize.X - 1) or (Bottom < BufferSize.Y - 1) then | |
685 | - // Text must be contained in window | |
686 | - begin | |
687 | - OldInOutFunc := TTextRec(Output).InOutFunc; | |
688 | - OldFlushFunc := TTextRec(Output).FlushFunc; | |
689 | - TTextRec(Output).InOutFunc := @NewTextOut; | |
690 | - TTextRec(Output).FlushFunc := @NewTextOut; | |
691 | - SetCursorPos(Left, Top); | |
692 | - end; | |
693 | - end | |
694 | - else | |
695 | - begin | |
696 | - TextWindow.Left := 0; | |
697 | - TextWindow.Right := BufferSize.X - 1; | |
698 | - TextWindow.Top := 0; | |
699 | - TextWindow.Bottom := BufferSize.Y - 1; | |
700 | - SetCursorPos(0, 0); | |
701 | - if Assigned(OldInOutFunc) then | |
702 | - begin | |
703 | - TTextRec(Output).InOutFunc := OldInOutFunc; | |
704 | - OldInOutFunc := nil; | |
705 | - end; | |
706 | - if Assigned(OldFlushFunc) then | |
707 | - begin | |
708 | - TTextRec(Output).FlushFunc := OldFlushFunc; | |
709 | - OldFlushFunc := nil; | |
710 | - end; | |
711 | - end; | |
712 | - WindMin := (TextWindow.Left and $FF) or (TextWindow.Top and $FF) shl 8; | |
713 | - WindMax := (TextWindow.Right and $FF) or (TextWindow.Bottom and $FF) shl 8; | |
714 | -end; | |
715 | - | |
716 | -procedure HardwareSound(Frequency: Smallint); | |
717 | -asm | |
718 | - CMP AX,37 | |
719 | - JB @@1 | |
720 | - MOV CX,AX | |
721 | - MOV AL,$B6 | |
722 | - OUT $43,AL | |
723 | - MOV AX,$3540 | |
724 | - MOV DX,$0012 | |
725 | - DIV CX | |
726 | - OUT $42,AL | |
727 | - MOV AL,AH | |
728 | - OUT $42,AL | |
729 | - MOV AL,3 | |
730 | - OUT $61,AL | |
731 | -@@1: | |
732 | -end; | |
733 | - | |
734 | -procedure HardwareNoSound; | |
735 | -asm | |
736 | - MOV AL,0 | |
737 | - OUT $61,AL | |
738 | -end; | |
739 | - | |
740 | -procedure HardwareDelay(Millisecs: Integer); | |
741 | -begin | |
742 | - Sleep(Millisecs); | |
743 | -end; | |
744 | - | |
745 | -procedure HardwareBeep(Frequency, Duration: Smallint); | |
746 | -begin | |
747 | - Sound(Frequency); | |
748 | - Delay(Duration); | |
749 | - NoSound; | |
750 | -end; | |
751 | - | |
752 | -type | |
753 | - TSoundState = (ssPending, ssPlaying, ssFreed); | |
754 | - | |
755 | -var | |
756 | - CurrentFrequency: Integer; | |
757 | - SoundState: TSoundState; | |
758 | - | |
759 | -// On Windows NT and later, direct port access is prohibited, so there is | |
760 | -// no way to use HardwareSound and HardwareNoSound. | |
761 | -// | |
762 | -// Since probably every note played by Sound will be delimited by some kind | |
763 | -// of Delay, the playing of the note is deferred to Delay. Sound only stores | |
764 | -// the frequency and sets the SoundState to ssPending. Delay now knows both | |
765 | -// parameters, and can use Windows.Beep. | |
766 | -// | |
767 | -// Note that such code is not reentrant. | |
768 | - | |
769 | -procedure SoftwareSound(Frequency: Smallint); | |
770 | -begin | |
771 | - // $123540 div Frequency must be <= $7FFF, so Frequency must be >= 37. | |
772 | - if Frequency >= 37 then | |
773 | - begin | |
774 | - CurrentFrequency := Frequency; | |
775 | - SoundState := ssPending; | |
776 | - end; | |
777 | -end; | |
778 | - | |
779 | -procedure SoftwareDelay(Millisecs: Integer); | |
780 | -begin | |
781 | - if SoundState = ssPending then | |
782 | - begin | |
783 | - SoundState := ssPlaying; | |
784 | - Windows.Beep(CurrentFrequency, MilliSecs); | |
785 | - SoundState := ssFreed; | |
786 | - end | |
787 | - else | |
788 | - Sleep(MilliSecs); | |
789 | -end; | |
790 | - | |
791 | -procedure SoftwareBeep(Frequency, Duration: Smallint); | |
792 | -begin | |
793 | - if Frequency >= 37 then | |
794 | - begin | |
795 | - SoundState := ssPlaying; | |
796 | - Windows.Beep(Frequency, Duration); | |
797 | - SoundState := ssFreed; | |
798 | - end; | |
799 | -end; | |
800 | - | |
801 | -procedure SoftwareNoSound; | |
802 | -begin | |
803 | - Windows.Beep(CurrentFrequency, 0); | |
804 | - SoundState := ssFreed; | |
805 | -end; | |
806 | - | |
807 | -function WhereX: Integer; | |
808 | -begin | |
809 | - Result := GetCursorX - TextWindow.Left + 1; | |
810 | -end; | |
811 | - | |
812 | -function WhereY: Integer; | |
813 | -begin | |
814 | - Result := GetCursorY - TextWindow.Top + 1; | |
815 | -end; | |
816 | - | |
817 | -procedure GetScreenSizes(var Width, Height: Smallint); | |
818 | -var | |
819 | - BufferInfo: TConsoleScreenBufferInfo; | |
820 | -begin | |
821 | - GetConsoleScreenBufferInfo(StdOut, BufferInfo); | |
822 | - Width := BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1; | |
823 | - Height := BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1; | |
824 | -end; | |
825 | - | |
826 | -function ScreenWidth: Smallint; | |
827 | -var | |
828 | - Height: Smallint; | |
829 | -begin | |
830 | - GetScreenSizes(Result, Height); | |
831 | -end; | |
832 | - | |
833 | -function ScreenHeight: Smallint; | |
834 | -var | |
835 | - Width: Smallint; | |
836 | -begin | |
837 | - GetScreenSizes(Width, Result); | |
838 | -end; | |
839 | - | |
840 | -procedure GetBufferSizes(var Width, Height: Smallint); | |
841 | -var | |
842 | - BufferInfo: TConsoleScreenBufferInfo; | |
843 | -begin | |
844 | - GetConsoleScreenBufferInfo(StdOut, BufferInfo); | |
845 | - Width := BufferInfo.dwSize.X; | |
846 | - Height := BufferInfo.dwSize.Y; | |
847 | -end; | |
848 | - | |
849 | -function BufferWidth: Smallint; | |
850 | -var | |
851 | - Height: Smallint; | |
852 | -begin | |
853 | - GetBufferSizes(Result, Height); | |
854 | -end; | |
855 | - | |
856 | -function BufferHeight: Smallint; | |
857 | -var | |
858 | - Width: Smallint; | |
859 | -begin | |
860 | - GetBufferSizes(Width, Result); | |
861 | -end; | |
862 | - | |
863 | -function TextColor: Byte; | |
864 | -begin | |
865 | - Result := TextAttr and $0F; | |
866 | -end; | |
867 | - | |
868 | -function TextBackground: Byte; | |
869 | -begin | |
870 | - Result := (TextAttr and $F0) shr 4; | |
871 | -end; | |
872 | - | |
873 | -procedure TextMode(Mode: Word); | |
874 | -begin | |
875 | - Window(0, 0, 0, 0); | |
876 | - NormVideo; | |
877 | -end; | |
878 | - | |
879 | -function Pause(const Msg: string = ''): Char; | |
880 | -begin | |
881 | - if Msg = '' then | |
882 | - Write('Press any key... ') | |
883 | - else | |
884 | - Write(Msg); | |
885 | - Result := ReadKey; | |
886 | -end; | |
887 | - | |
888 | -procedure InitScreenMode; | |
889 | -var | |
890 | - BufferInfo: TConsoleScreenBufferInfo; | |
891 | -begin | |
892 | - Reset(Input); | |
893 | - Rewrite(Output); | |
894 | - StdIn := TTextRec(Input).Handle; | |
895 | - StdOut := TTextRec(Output).Handle; | |
896 | -{$IFDEF HASERROUTPUT} | |
897 | - Rewrite(ErrOutput); | |
898 | - StdErr := TTextRec(ErrOutput).Handle; | |
899 | -{$ELSE} | |
900 | - StdErr := GetStdHandle(STD_ERROR_HANDLE); | |
901 | -{$ENDIF} | |
902 | - if not GetConsoleScreenBufferInfo(StdOut, BufferInfo) then | |
903 | - begin | |
904 | - SetInOutRes(GetLastError); | |
905 | - Exit; | |
906 | - end; | |
907 | - TextWindow.Left := 0; | |
908 | - TextWindow.Top := 0; | |
909 | - TextWindow.Right := BufferInfo.dwSize.X - 1; | |
910 | - TextWindow.Bottom := BufferInfo.dwSize.Y - 1; | |
911 | - TextAttr := BufferInfo.wAttributes and $FF; | |
912 | - DefaultAttr := TextAttr; | |
913 | - BufferSize := BufferInfo.dwSize; | |
914 | - ScreenSize.X := BufferInfo.srWindow.Right - BufferInfo.srWindow.Left + 1; | |
915 | - ScreenSize.Y := BufferInfo.srWindow.Bottom - BufferInfo.srWindow.Top + 1; | |
916 | - WindMin := 0; | |
917 | - WindMax := (ScreenSize.X and $FF) or (ScreenSize.Y and $FF) shl 8; | |
918 | - LastMode := CO80; | |
919 | - OldInOutFunc := nil; | |
920 | - OldFlushFunc := nil; | |
921 | - if Win32Platform = VER_PLATFORM_WIN32_NT then | |
922 | - begin | |
923 | - Sound := SoftwareSound; | |
924 | - NoSound := SoftwareNoSound; | |
925 | - Delay := SoftwareDelay; | |
926 | - Beep := SoftwareBeep; | |
927 | - end | |
928 | - else | |
929 | - begin | |
930 | - Sound := HardwareSound; | |
931 | - NoSound := HardwareNoSound; | |
932 | - Delay := HardwareDelay; | |
933 | - Beep := HardwareBeep; | |
934 | - end; | |
935 | -end; | |
936 | - | |
937 | -initialization | |
938 | - InitScreenMode; | |
939 | - | |
940 | -end. | |
941 | - |