[WIN32SS][WINSRV] Fullwidth character handling Part 2 (#2240)
[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(PCONSRV_CONSOLE 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 VOID
40 CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,
41 PSMALL_RECT Selection)
42 {
43 /*
44 * Pressing the Shift key while copying text, allows us to copy
45 * text without newline characters (inline-text copy mode).
46 */
47 BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & KEY_PRESSED);
48
49 HANDLE hData;
50 PCHAR_INFO ptr;
51 LPWSTR data, dstPos;
52 ULONG selWidth, selHeight;
53 ULONG xPos, yPos;
54 ULONG size;
55
56 DPRINT("CopyBlock(%u, %u, %u, %u)\n",
57 Selection->Left, Selection->Top, Selection->Right, Selection->Bottom);
58
59 /* Prevent against empty blocks */
60 if (Selection == NULL) return;
61 if (Selection->Left > Selection->Right || Selection->Top > Selection->Bottom)
62 return;
63
64 selWidth = Selection->Right - Selection->Left + 1;
65 selHeight = Selection->Bottom - Selection->Top + 1;
66
67 /* Basic size for one line... */
68 size = selWidth;
69 /* ... and for the other lines, add newline characters if needed. */
70 if (selHeight > 0)
71 {
72 /*
73 * If we are not in inline-text copy mode, each selected line must
74 * finish with \r\n . Otherwise, the lines will be just concatenated.
75 */
76 size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1);
77 }
78 else
79 {
80 DPRINT1("This case must never happen, because selHeight is at least == 1\n");
81 }
82
83 size++; /* Null-termination */
84 size *= sizeof(WCHAR);
85
86 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
87 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
88 if (hData == NULL) return;
89
90 data = GlobalLock(hData);
91 if (data == NULL)
92 {
93 GlobalFree(hData);
94 return;
95 }
96
97 DPRINT("Copying %dx%d selection\n", selWidth, selHeight);
98 dstPos = data;
99
100 for (yPos = 0; yPos < selHeight; yPos++)
101 {
102 ULONG length = selWidth;
103
104 ptr = ConioCoordToPointer(Buffer,
105 Selection->Left,
106 Selection->Top + yPos);
107
108 /* Trim whitespace from the right */
109 while (length > 0)
110 {
111 if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar))
112 --length;
113 else
114 break;
115 }
116
117 /* Copy only the characters, leave attributes alone */
118 for (xPos = 0; xPos < length; xPos++)
119 {
120 /*
121 * Sometimes, applications can put NULL chars into the screen-buffer
122 * (this behaviour is allowed). Detect this and replace by a space.
123 */
124 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
125 }
126
127 /* Add newline characters if we are not in inline-text copy mode */
128 if (!InlineCopyMode)
129 {
130 if (yPos != (selHeight - 1))
131 {
132 wcscat(dstPos, L"\r\n");
133 dstPos += 2;
134 }
135 }
136 }
137
138 DPRINT("Setting data <%S> to clipboard\n", data);
139 GlobalUnlock(hData);
140
141 EmptyClipboard();
142 SetClipboardData(CF_UNICODETEXT, hData);
143 }
144
145 static VOID
146 CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,
147 PCOORD Begin,
148 PCOORD End)
149 {
150 HANDLE hData;
151 PCHAR_INFO ptr;
152 LPWSTR data, dstPos;
153 ULONG NumChars, size;
154 ULONG xPos, yPos, xBeg, xEnd;
155
156 DPRINT("CopyLines((%u, %u) ; (%u, %u))\n",
157 Begin->X, Begin->Y, End->X, End->Y);
158
159 /* Prevent against empty blocks... */
160 if (Begin == NULL || End == NULL) return;
161 /* ... or malformed blocks */
162 if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return;
163
164 /* Compute the number of characters to copy */
165 if (End->Y == Begin->Y) // top == bottom
166 {
167 NumChars = End->X - Begin->X + 1;
168 }
169 else // if (End->Y > Begin->Y)
170 {
171 NumChars = Buffer->ScreenBufferSize.X - Begin->X;
172
173 if (End->Y >= Begin->Y + 2)
174 {
175 NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X;
176 }
177
178 NumChars += End->X + 1;
179 }
180
181 size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */
182
183 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
184 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
185 if (hData == NULL) return;
186
187 data = GlobalLock(hData);
188 if (data == NULL)
189 {
190 GlobalFree(hData);
191 return;
192 }
193
194 DPRINT("Copying %d characters\n", NumChars);
195 dstPos = data;
196
197 /*
198 * We need to walk per-lines, and not just looping in the big screen-buffer
199 * array, because of the way things are stored inside it. The downside is
200 * that it makes the code more complicated.
201 */
202 for (yPos = Begin->Y; (yPos <= (ULONG)End->Y) && (NumChars > 0); yPos++)
203 {
204 xBeg = (yPos == Begin->Y ? Begin->X : 0);
205 xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1);
206
207 ptr = ConioCoordToPointer(Buffer, 0, yPos);
208
209 /* Copy only the characters, leave attributes alone */
210 for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++)
211 {
212 /*
213 * Sometimes, applications can put NULL chars into the screen-buffer
214 * (this behaviour is allowed). Detect this and replace by a space.
215 */
216 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
217 }
218 }
219
220 DPRINT("Setting data <%S> to clipboard\n", data);
221 GlobalUnlock(hData);
222
223 EmptyClipboard();
224 SetClipboardData(CF_UNICODETEXT, hData);
225 }
226
227
228 VOID
229 PasteText(
230 IN PCONSRV_CONSOLE Console,
231 IN PWCHAR Buffer,
232 IN SIZE_T cchSize)
233 {
234 USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state);
235 INPUT_RECORD er;
236 WCHAR CurChar = 0;
237
238 /* Do nothing if we have nothing to paste */
239 if (!Buffer || (cchSize <= 0))
240 return;
241
242 er.EventType = KEY_EVENT;
243 er.Event.KeyEvent.wRepeatCount = 1;
244 while (cchSize--)
245 {
246 /* \r or \n characters. Go to the line only if we get "\r\n" sequence. */
247 if (CurChar == L'\r' && *Buffer == L'\n')
248 {
249 ++Buffer;
250 continue;
251 }
252 CurChar = *Buffer++;
253
254 /* Get the key code (+ shift state) corresponding to the character */
255 VkKey = VkKeyScanW(CurChar);
256 if (VkKey == 0xFFFF)
257 {
258 DPRINT1("FIXME: TODO: VkKeyScanW failed - Should simulate the key!\n");
259 /*
260 * We don't really need the scan/key code because we actually only
261 * use the UnicodeChar for output purposes. It may pose few problems
262 * later on but it's not of big importance. One trick would be to
263 * convert the character to OEM / multibyte and use MapVirtualKey()
264 * on each byte (simulating an Alt-0xxx OEM keyboard press).
265 */
266 }
267
268 /* Pressing some control keys */
269
270 /* Pressing the character key, with the control keys maintained pressed */
271 er.Event.KeyEvent.bKeyDown = TRUE;
272 er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey);
273 er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_VSC);
274 er.Event.KeyEvent.uChar.UnicodeChar = CurChar;
275 er.Event.KeyEvent.dwControlKeyState = 0;
276 if (HIBYTE(VkKey) & 1)
277 er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
278 if (HIBYTE(VkKey) & 2)
279 er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED;
280 if (HIBYTE(VkKey) & 4)
281 er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED;
282
283 ConioProcessInputEvent(Console, &er);
284
285 /* Up all the character and control keys */
286 er.Event.KeyEvent.bKeyDown = FALSE;
287 ConioProcessInputEvent(Console, &er);
288 }
289 }
290
291 VOID
292 GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
293 PCOORD SelectionAnchor,
294 PSMALL_RECT SmallRect);
295
296 VOID
297 GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
298 PGUI_CONSOLE_DATA GuiData)
299 {
300 /*
301 * This function supposes that the system clipboard was opened.
302 */
303
304 BOOL LineSelection = GuiData->LineSelection;
305
306 DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n",
307 GuiData->Selection.srSelection.Left,
308 GuiData->Selection.srSelection.Top,
309 GuiData->Selection.srSelection.Right,
310 GuiData->Selection.srSelection.Bottom,
311 (LineSelection ? "line" : "block"));
312
313 if (!LineSelection)
314 {
315 CopyBlock(Buffer, &GuiData->Selection.srSelection);
316 }
317 else
318 {
319 COORD Begin, End;
320
321 GetSelectionBeginEnd(&Begin, &End,
322 &GuiData->Selection.dwSelectionAnchor,
323 &GuiData->Selection.srSelection);
324
325 CopyLines(Buffer, &Begin, &End);
326 }
327 }
328
329 VOID
330 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
331 PGUI_CONSOLE_DATA GuiData)
332 {
333 /*
334 * This function supposes that the system clipboard was opened.
335 */
336
337 PCONSRV_CONSOLE Console = Buffer->Header.Console;
338
339 HANDLE hData;
340 LPWSTR pszText;
341
342 hData = GetClipboardData(CF_UNICODETEXT);
343 if (hData == NULL) return;
344
345 pszText = GlobalLock(hData);
346 if (pszText == NULL) return;
347
348 DPRINT("Got data <%S> from clipboard\n", pszText);
349 PasteText(Console, pszText, wcslen(pszText));
350
351 GlobalUnlock(hData);
352 }
353
354 VOID
355 GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
356 PGUI_CONSOLE_DATA GuiData,
357 PRECT rcView,
358 PRECT rcFramebuffer)
359 {
360 PCONSRV_CONSOLE Console = Buffer->Header.Console;
361 ULONG TopLine, BottomLine, LeftColumn, RightColumn;
362 ULONG Line, Char, Start;
363 PCHAR_INFO From;
364 PWCHAR To;
365 WORD LastAttribute, Attribute;
366 ULONG CursorX, CursorY, CursorHeight;
367 HBRUSH CursorBrush, OldBrush;
368 HFONT OldFont, NewFont;
369 BOOLEAN IsUnderline;
370
371 // ASSERT(Console == GuiData->Console);
372
373 ConioInitLongRect(rcFramebuffer, 0, 0, 0, 0);
374
375 if (Buffer->Buffer == NULL)
376 return;
377
378 if (!ConDrvValidateConsoleUnsafe((PCONSOLE)Console, CONSOLE_RUNNING, TRUE))
379 return;
380
381 ConioInitLongRect(rcFramebuffer,
382 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top,
383 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left,
384 Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom,
385 Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right);
386
387 LeftColumn = rcFramebuffer->left / GuiData->CharWidth;
388 RightColumn = rcFramebuffer->right / GuiData->CharWidth;
389 if (RightColumn >= (ULONG)Buffer->ScreenBufferSize.X)
390 RightColumn = Buffer->ScreenBufferSize.X - 1;
391
392 TopLine = rcFramebuffer->top / GuiData->CharHeight;
393 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight;
394 if (BottomLine >= (ULONG)Buffer->ScreenBufferSize.Y)
395 BottomLine = Buffer->ScreenBufferSize.Y - 1;
396
397 LastAttribute = ConioCoordToPointer(Buffer, LeftColumn, TopLine)->Attributes;
398
399 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
400 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
401
402 /* We use the underscore flag as a underline flag */
403 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
404 /* Select the new font */
405 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
406 OldFont = SelectObject(GuiData->hMemDC, NewFont);
407
408 if (Console->IsCJK)
409 {
410 for (Line = TopLine; Line <= BottomLine; Line++)
411 {
412 for (Char = LeftColumn; Char <= RightColumn; Char++)
413 {
414 From = ConioCoordToPointer(Buffer, Char, Line);
415 Attribute = From->Attributes;
416 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
417 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute)));
418
419 /* Change underline state if needed */
420 if (!!(Attribute & COMMON_LVB_UNDERSCORE) != IsUnderline)
421 {
422 IsUnderline = !!(Attribute & COMMON_LVB_UNDERSCORE);
423
424 /* Select the new font */
425 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
426 SelectObject(GuiData->hMemDC, NewFont);
427 }
428
429 if (Attribute & COMMON_LVB_TRAILING_BYTE)
430 continue;
431
432 TextOutW(GuiData->hMemDC,
433 Char * GuiData->CharWidth,
434 Line * GuiData->CharHeight,
435 &From->Char.UnicodeChar, 1);
436 }
437 }
438 }
439 else
440 {
441 for (Line = TopLine; Line <= BottomLine; Line++)
442 {
443 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed
444 From = ConioCoordToPointer(Buffer, LeftColumn, Line); // Get the first code of the line
445 Start = LeftColumn;
446 To = LineBuffer;
447
448 for (Char = LeftColumn; Char <= RightColumn; Char++)
449 {
450 /*
451 * We flush the buffer if the new attribute is different
452 * from the current one, or if the buffer is full.
453 */
454 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR)))
455 {
456 TextOutW(GuiData->hMemDC,
457 Start * GuiData->CharWidth,
458 Line * GuiData->CharHeight,
459 LineBuffer,
460 Char - Start);
461 Start = Char;
462 To = LineBuffer;
463 Attribute = From->Attributes;
464 if (Attribute != LastAttribute)
465 {
466 LastAttribute = Attribute;
467 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
468 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
469
470 /* Change underline state if needed */
471 if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderline)
472 {
473 IsUnderline = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
474 /* Select the new font */
475 NewFont = GuiData->Font[IsUnderline ? FONT_BOLD : FONT_NORMAL];
476 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 RightColumn - Start + 1);
489 }
490 }
491
492 /* Restore the old font */
493 SelectObject(GuiData->hMemDC, OldFont);
494
495 /*
496 * Draw the caret
497 */
498 if (Buffer->CursorInfo.bVisible &&
499 Buffer->CursorBlinkOn &&
500 !Buffer->ForceCursorOff)
501 {
502 CursorX = Buffer->CursorPosition.X;
503 CursorY = Buffer->CursorPosition.Y;
504 if (LeftColumn <= CursorX && CursorX <= RightColumn &&
505 TopLine <= CursorY && CursorY <= BottomLine)
506 {
507 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
508
509 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes;
510 if (Attribute == DEFAULT_SCREEN_ATTRIB)
511 Attribute = Buffer->ScreenDefaultAttrib;
512
513 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
514 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush);
515
516 if (Attribute & COMMON_LVB_LEADING_BYTE)
517 {
518 /* The caret is on the leading byte */
519 PatBlt(GuiData->hMemDC,
520 CursorX * GuiData->CharWidth,
521 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
522 GuiData->CharWidth * 2,
523 CursorHeight,
524 PATCOPY);
525 }
526 else if (Attribute & COMMON_LVB_TRAILING_BYTE)
527 {
528 /* The caret is on the trailing byte */
529 PatBlt(GuiData->hMemDC,
530 (CursorX - 1) * GuiData->CharWidth,
531 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
532 GuiData->CharWidth * 2,
533 CursorHeight,
534 PATCOPY);
535 }
536 else
537 {
538 PatBlt(GuiData->hMemDC,
539 CursorX * GuiData->CharWidth,
540 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
541 GuiData->CharWidth,
542 CursorHeight,
543 PATCOPY);
544 }
545
546 SelectObject(GuiData->hMemDC, OldBrush);
547 DeleteObject(CursorBrush);
548 }
549 }
550
551 LeaveCriticalSection(&Console->Lock);
552 }
553
554 /* EOF */