Sync with trunk r63878.
[reactos.git] / win32ss / user / winsrv / consrv / frontends / gui / text.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Console Server DLL
4 * FILE: win32ss/user/winsrv/consrv/frontends/gui/text.c
5 * PURPOSE: GUI Terminal Front-End - Support for text-mode screen-buffers
6 * PROGRAMMERS: Gé van Geldorp
7 * Johannes Anderwald
8 * Jeffrey Morlan
9 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
10 */
11
12 /* INCLUDES *******************************************************************/
13
14 #include <consrv.h>
15
16 #define NDEBUG
17 #include <debug.h>
18
19 #include "guiterm.h"
20
21 /* GLOBALS ********************************************************************/
22
23 #define IS_WHITESPACE(c) ((c) == L'\0' || (c) == L' ' || (c) == L'\t')
24
25 /* FUNCTIONS ******************************************************************/
26
27 static COLORREF
28 PaletteRGBFromAttrib(PCONSOLE Console, WORD Attribute)
29 {
30 HPALETTE hPalette = Console->ActiveBuffer->PaletteHandle;
31 PALETTEENTRY pe;
32
33 if (hPalette == NULL) return RGBFromAttrib(Console, Attribute);
34
35 GetPaletteEntries(hPalette, Attribute, 1, &pe);
36 return PALETTERGB(pe.peRed, pe.peGreen, pe.peBlue);
37 }
38
39 static HFONT
40 ChangeFontAttributes(PGUI_CONSOLE_DATA GuiData,
41 // COORD FontSize,
42 ULONG FontWeight,
43 BOOLEAN bItalic,
44 BOOLEAN bUnderline,
45 BOOLEAN bStrikeOut)
46 {
47 HFONT NewFont;
48 LOGFONT lf;
49
50 /* Initialize the LOGFONT structure */
51 RtlZeroMemory(&lf, sizeof(lf));
52
53 /* Retrieve the details of the current font */
54 if (GetObject(GuiData->Font, sizeof(lf), &lf) == 0)
55 return NULL; // GuiData->Font;
56
57 /* Change the font attributes */
58 // lf.lfHeight = FontSize.Y;
59 // lf.lfWidth = FontSize.X;
60 lf.lfWeight = FontWeight;
61 lf.lfItalic = bItalic;
62 lf.lfUnderline = bUnderline;
63 lf.lfStrikeOut = bStrikeOut;
64
65 /* Build a new font */
66 NewFont = CreateFontIndirect(&lf);
67 if (NewFont == NULL)
68 return NULL; // GuiData->Font;
69
70 // FIXME: Do we need to update GuiData->CharWidth and GuiData->CharHeight ??
71
72 /* Select it (return the old font) */
73 // return SelectObject(GuiData->hMemDC, NewFont);
74 return NewFont;
75 }
76
77 static VOID
78 CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,
79 PSMALL_RECT Selection)
80 {
81 /*
82 * Pressing the Shift key while copying text, allows us to copy
83 * text without newline characters (inline-text copy mode).
84 */
85 BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & 0x8000);
86
87 HANDLE hData;
88 PCHAR_INFO ptr;
89 LPWSTR data, dstPos;
90 ULONG selWidth, selHeight;
91 ULONG xPos, yPos;
92 ULONG size;
93
94 DPRINT("CopyBlock(%u, %u, %u, %u)\n",
95 Selection->Left, Selection->Top, Selection->Right, Selection->Bottom);
96
97 /* Prevent against empty blocks */
98 if (Selection == NULL) return;
99 if (Selection->Left > Selection->Right || Selection->Top > Selection->Bottom)
100 return;
101
102 selWidth = Selection->Right - Selection->Left + 1;
103 selHeight = Selection->Bottom - Selection->Top + 1;
104
105 /* Basic size for one line... */
106 size = selWidth;
107 /* ... and for the other lines, add newline characters if needed. */
108 if (selHeight > 0)
109 {
110 /*
111 * If we are not in inline-text copy mode, each selected line must
112 * finish with \r\n . Otherwise, the lines will be just concatenated.
113 */
114 size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1);
115 }
116 else
117 {
118 DPRINT1("This case must never happen, because selHeight is at least == 1\n");
119 }
120
121 size += 1; /* Null-termination */
122 size *= sizeof(WCHAR);
123
124 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
125 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
126 if (hData == NULL) return;
127
128 data = GlobalLock(hData);
129 if (data == NULL)
130 {
131 GlobalFree(hData);
132 return;
133 }
134
135 DPRINT("Copying %dx%d selection\n", selWidth, selHeight);
136 dstPos = data;
137
138 for (yPos = 0; yPos < selHeight; yPos++)
139 {
140 ULONG length = selWidth;
141
142 ptr = ConioCoordToPointer(Buffer,
143 Selection->Left,
144 Selection->Top + yPos);
145
146 /* Trim whitespace from the right */
147 while (length > 0)
148 {
149 if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar))
150 --length;
151 else
152 break;
153 }
154
155 /* Copy only the characters, leave attributes alone */
156 for (xPos = 0; xPos < length; xPos++)
157 {
158 /*
159 * Sometimes, applications can put NULL chars into the screen-buffer
160 * (this behaviour is allowed). Detect this and replace by a space.
161 */
162 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
163 }
164
165 /* Add newline characters if we are not in inline-text copy mode */
166 if (!InlineCopyMode)
167 {
168 if (yPos != (selHeight - 1))
169 {
170 wcscat(dstPos, L"\r\n");
171 dstPos += 2;
172 }
173 }
174 }
175
176 DPRINT("Setting data <%S> to clipboard\n", data);
177 GlobalUnlock(hData);
178
179 EmptyClipboard();
180 SetClipboardData(CF_UNICODETEXT, hData);
181 }
182
183 static VOID
184 CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,
185 PCOORD Begin,
186 PCOORD End)
187 {
188 HANDLE hData;
189 PCHAR_INFO ptr;
190 LPWSTR data, dstPos;
191 ULONG NumChars, size;
192 ULONG xPos, yPos, xBeg, xEnd;
193
194 DPRINT("CopyLines((%u, %u) ; (%u, %u))\n",
195 Begin->X, Begin->Y, End->X, End->Y);
196
197 /* Prevent against empty blocks... */
198 if (Begin == NULL || End == NULL) return;
199 /* ... or malformed blocks */
200 if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return;
201
202 /* Compute the number of characters to copy */
203 if (End->Y == Begin->Y) // top == bottom
204 {
205 NumChars = End->X - Begin->X + 1;
206 }
207 else // if (End->Y > Begin->Y)
208 {
209 NumChars = Buffer->ScreenBufferSize.X - Begin->X;
210
211 if (End->Y >= Begin->Y + 2)
212 {
213 NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X;
214 }
215
216 NumChars += End->X + 1;
217 }
218
219 size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */
220
221 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
222 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
223 if (hData == NULL) return;
224
225 data = GlobalLock(hData);
226 if (data == NULL)
227 {
228 GlobalFree(hData);
229 return;
230 }
231
232 DPRINT("Copying %d characters\n", NumChars);
233 dstPos = data;
234
235 /*
236 * We need to walk per-lines, and not just looping in the big screen-buffer
237 * array, because of the way things are stored inside it. The downside is
238 * that it makes the code more complicated.
239 */
240 for (yPos = Begin->Y; (yPos <= End->Y) && (NumChars > 0); yPos++)
241 {
242 xBeg = (yPos == Begin->Y ? Begin->X : 0);
243 xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1);
244
245 ptr = ConioCoordToPointer(Buffer, 0, yPos);
246
247 /* Copy only the characters, leave attributes alone */
248 for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++)
249 {
250 /*
251 * Sometimes, applications can put NULL chars into the screen-buffer
252 * (this behaviour is allowed). Detect this and replace by a space.
253 */
254 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
255 }
256 }
257
258 DPRINT("Setting data <%S> to clipboard\n", data);
259 GlobalUnlock(hData);
260
261 EmptyClipboard();
262 SetClipboardData(CF_UNICODETEXT, hData);
263 }
264
265
266 VOID
267 GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
268 PCOORD SelectionAnchor,
269 PSMALL_RECT SmallRect);
270
271 VOID
272 GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
273 PGUI_CONSOLE_DATA GuiData)
274 {
275 /*
276 * This function supposes that the system clipboard was opened.
277 */
278
279 BOOL LineSelection = GuiData->LineSelection;
280
281 DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n",
282 GuiData->Selection.srSelection.Left,
283 GuiData->Selection.srSelection.Top,
284 GuiData->Selection.srSelection.Right,
285 GuiData->Selection.srSelection.Bottom,
286 (LineSelection ? "line" : "block"));
287
288 if (!LineSelection)
289 {
290 CopyBlock(Buffer, &GuiData->Selection.srSelection);
291 }
292 else
293 {
294 COORD Begin, End;
295
296 GetSelectionBeginEnd(&Begin, &End,
297 &GuiData->Selection.dwSelectionAnchor,
298 &GuiData->Selection.srSelection);
299
300 CopyLines(Buffer, &Begin, &End);
301 }
302 }
303
304 VOID
305 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
306 PGUI_CONSOLE_DATA GuiData)
307 {
308 /*
309 * This function supposes that the system clipboard was opened.
310 */
311
312 PCONSOLE Console = Buffer->Header.Console;
313
314 HANDLE hData;
315 LPWSTR str;
316 WCHAR CurChar = 0;
317
318 USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state);
319 INPUT_RECORD er;
320
321 hData = GetClipboardData(CF_UNICODETEXT);
322 if (hData == NULL) return;
323
324 str = GlobalLock(hData);
325 if (str == NULL) return;
326
327 DPRINT("Got data <%S> from clipboard\n", str);
328
329 er.EventType = KEY_EVENT;
330 er.Event.KeyEvent.wRepeatCount = 1;
331 while (*str)
332 {
333 /* \r or \n characters. Go to the line only if we get "\r\n" sequence. */
334 if (CurChar == L'\r' && *str == L'\n')
335 {
336 str++;
337 continue;
338 }
339 CurChar = *str++;
340
341 /* Get the key code (+ shift state) corresponding to the character */
342 VkKey = VkKeyScanW(CurChar);
343 if (VkKey == 0xFFFF)
344 {
345 DPRINT1("VkKeyScanW failed - Should simulate the key...\n");
346 continue;
347 }
348
349 /* Pressing some control keys */
350
351 /* Pressing the character key, with the control keys maintained pressed */
352 er.Event.KeyEvent.bKeyDown = TRUE;
353 er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey);
354 er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_CHAR);
355 er.Event.KeyEvent.uChar.UnicodeChar = CurChar;
356 er.Event.KeyEvent.dwControlKeyState = 0;
357 if (HIBYTE(VkKey) & 1)
358 er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
359 if (HIBYTE(VkKey) & 2)
360 er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED;
361 if (HIBYTE(VkKey) & 4)
362 er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED;
363
364 ConioProcessInputEvent(Console, &er);
365
366 /* Up all the character and control keys */
367 er.Event.KeyEvent.bKeyDown = FALSE;
368 ConioProcessInputEvent(Console, &er);
369 }
370
371 GlobalUnlock(hData);
372 }
373
374 VOID
375 GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
376 PGUI_CONSOLE_DATA GuiData,
377 PRECT rcView,
378 PRECT rcFramebuffer)
379 {
380 PCONSOLE Console = Buffer->Header.Console;
381 // ASSERT(Console == GuiData->Console);
382
383 ULONG TopLine, BottomLine, LeftChar, RightChar;
384 ULONG Line, Char, Start;
385 PCHAR_INFO From;
386 PWCHAR To;
387 WORD LastAttribute, Attribute;
388 ULONG CursorX, CursorY, CursorHeight;
389 HBRUSH CursorBrush, OldBrush;
390 HFONT OldFont, NewFont;
391 BOOLEAN IsUnderscore;
392
393 if (Buffer->Buffer == NULL) return;
394
395 if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
396
397 rcFramebuffer->left = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left;
398 rcFramebuffer->top = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top;
399 rcFramebuffer->right = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right;
400 rcFramebuffer->bottom = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom;
401
402 LeftChar = rcFramebuffer->left / GuiData->CharWidth;
403 TopLine = rcFramebuffer->top / GuiData->CharHeight;
404 RightChar = rcFramebuffer->right / GuiData->CharWidth;
405 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight;
406
407 if (RightChar >= Buffer->ScreenBufferSize.X) RightChar = Buffer->ScreenBufferSize.X - 1;
408 if (BottomLine >= Buffer->ScreenBufferSize.Y) BottomLine = Buffer->ScreenBufferSize.Y - 1;
409
410 LastAttribute = ConioCoordToPointer(Buffer, LeftChar, TopLine)->Attributes;
411
412 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
413 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
414
415 // OldFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight, FALSE, FALSE, FALSE);
416 IsUnderscore = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
417 NewFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight,
418 FALSE,
419 IsUnderscore,
420 FALSE);
421 if (NewFont == NULL)
422 {
423 DPRINT1("ChangeFontAttributes failed, use the original font\n");
424 NewFont = GuiData->Font;
425 }
426 OldFont = SelectObject(GuiData->hMemDC, NewFont);
427
428 for (Line = TopLine; Line <= BottomLine; Line++)
429 {
430 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed
431 From = ConioCoordToPointer(Buffer, LeftChar, Line); // Get the first code of the line
432 Start = LeftChar;
433 To = LineBuffer;
434
435 for (Char = LeftChar; Char <= RightChar; Char++)
436 {
437 /*
438 * We flush the buffer if the new attribute is different
439 * from the current one, or if the buffer is full.
440 */
441 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR)))
442 {
443 TextOutW(GuiData->hMemDC,
444 Start * GuiData->CharWidth,
445 Line * GuiData->CharHeight,
446 LineBuffer,
447 Char - Start);
448 Start = Char;
449 To = LineBuffer;
450 Attribute = From->Attributes;
451 if (Attribute != LastAttribute)
452 {
453 LastAttribute = Attribute;
454 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
455 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
456
457 /* Change underscore state if needed */
458 if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderscore)
459 {
460 IsUnderscore = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
461
462 /* Delete the font we used up to now */
463 // SelectObject(GuiData->hMemDC, OldFont);
464 if (NewFont != GuiData->Font) DeleteObject(NewFont);
465 /* Recreate it */
466 NewFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight,
467 FALSE,
468 IsUnderscore,
469 FALSE);
470 if (NewFont == NULL)
471 {
472 DPRINT1("ChangeFontAttributes failed, use the original font\n");
473 NewFont = GuiData->Font;
474 }
475 /* Select it */
476 /* OldFont = */ SelectObject(GuiData->hMemDC, NewFont);
477 }
478 }
479 }
480
481 *(To++) = (From++)->Char.UnicodeChar;
482 }
483
484 TextOutW(GuiData->hMemDC,
485 Start * GuiData->CharWidth,
486 Line * GuiData->CharHeight,
487 LineBuffer,
488 RightChar - Start + 1);
489 }
490
491 /*
492 * Draw the caret
493 */
494 if (Buffer->CursorInfo.bVisible &&
495 Buffer->CursorBlinkOn &&
496 !Buffer->ForceCursorOff)
497 {
498 CursorX = Buffer->CursorPosition.X;
499 CursorY = Buffer->CursorPosition.Y;
500 if (LeftChar <= CursorX && CursorX <= RightChar &&
501 TopLine <= CursorY && CursorY <= BottomLine)
502 {
503 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
504
505 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes;
506 if (Attribute == DEFAULT_SCREEN_ATTRIB) Attribute = Buffer->ScreenDefaultAttrib;
507
508 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
509 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush);
510
511 PatBlt(GuiData->hMemDC,
512 CursorX * GuiData->CharWidth,
513 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
514 GuiData->CharWidth,
515 CursorHeight,
516 PATCOPY);
517
518 SelectObject(GuiData->hMemDC, OldBrush);
519 DeleteObject(CursorBrush);
520 }
521 }
522
523 /* Restore the old font and delete the font we used up to now */
524 SelectObject(GuiData->hMemDC, OldFont);
525 if (NewFont != GuiData->Font) DeleteObject(NewFont);
526
527 LeaveCriticalSection(&Console->Lock);
528 }
529
530 /* EOF */