[CONSRV]: Fix erroneous X-offset when copying text lines.
[reactos.git] / reactos / 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 COLORREF PaletteRGBFromAttrib(PCONSOLE Console, WORD Attribute)
28 {
29 HPALETTE hPalette = Console->ActiveBuffer->PaletteHandle;
30 PALETTEENTRY pe;
31
32 if (hPalette == NULL) return RGBFromAttrib(Console, Attribute);
33
34 GetPaletteEntries(hPalette, Attribute, 1, &pe);
35 return PALETTERGB(pe.peRed, pe.peGreen, pe.peBlue);
36 }
37
38 static VOID
39 CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,
40 PSMALL_RECT Selection)
41 {
42 /*
43 * Pressing the Shift key while copying text, allows us to copy
44 * text without newline characters (inline-text copy mode).
45 */
46 BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & 0x8000);
47
48 HANDLE hData;
49 PCHAR_INFO ptr;
50 LPWSTR data, dstPos;
51 ULONG selWidth, selHeight;
52 ULONG xPos, yPos;
53 ULONG size;
54
55 DPRINT("CopyBlock(%u, %u, %u, %u)\n",
56 Selection->Left, Selection->Top, Selection->Right, Selection->Bottom);
57
58 /* Prevent against empty blocks */
59 if (Selection == NULL) return;
60 if (Selection->Left > Selection->Right || Selection->Top > Selection->Bottom)
61 return;
62
63 selWidth = Selection->Right - Selection->Left + 1;
64 selHeight = Selection->Bottom - Selection->Top + 1;
65
66 /* Basic size for one line... */
67 size = selWidth;
68 /* ... and for the other lines, add newline characters if needed. */
69 if (selHeight > 0)
70 {
71 /*
72 * If we are not in inline-text copy mode, each selected line must
73 * finish with \r\n . Otherwise, the lines will be just concatenated.
74 */
75 size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1);
76 }
77 else
78 {
79 DPRINT1("This case must never happen, because selHeight is at least == 1\n");
80 }
81
82 size += 1; /* Null-termination */
83 size *= sizeof(WCHAR);
84
85 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
86 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
87 if (hData == NULL) return;
88
89 data = GlobalLock(hData);
90 if (data == NULL)
91 {
92 GlobalFree(hData);
93 return;
94 }
95
96 DPRINT("Copying %dx%d selection\n", selWidth, selHeight);
97 dstPos = data;
98
99 for (yPos = 0; yPos < selHeight; yPos++)
100 {
101 ULONG length = selWidth;
102
103 ptr = ConioCoordToPointer(Buffer,
104 Selection->Left,
105 Selection->Top + yPos);
106
107 /* Trim whitespace from the right */
108 while (length > 0)
109 {
110 if (IS_WHITESPACE(ptr[length-1].Char.UnicodeChar))
111 --length;
112 else
113 break;
114 }
115
116 /* Copy only the characters, leave attributes alone */
117 for (xPos = 0; xPos < length; xPos++)
118 {
119 /*
120 * Sometimes, applications can put NULL chars into the screen-buffer
121 * (this behaviour is allowed). Detect this and replace by a space.
122 */
123 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
124 }
125
126 /* Add newline characters if we are not in inline-text copy mode */
127 if (!InlineCopyMode)
128 {
129 if (yPos != (selHeight - 1))
130 {
131 wcscat(dstPos, L"\r\n");
132 dstPos += 2;
133 }
134 }
135 }
136
137 DPRINT("Setting data <%S> to clipboard\n", data);
138 GlobalUnlock(hData);
139
140 EmptyClipboard();
141 SetClipboardData(CF_UNICODETEXT, hData);
142 }
143
144 static VOID
145 CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,
146 PCOORD Begin,
147 PCOORD End)
148 {
149 HANDLE hData;
150 PCHAR_INFO ptr;
151 LPWSTR data, dstPos;
152 ULONG NumChars, size;
153 ULONG xPos, yPos, xBeg, xEnd;
154
155 DPRINT("CopyLines((%u, %u) ; (%u, %u))\n",
156 Begin->X, Begin->Y, End->X, End->Y);
157
158 /* Prevent against empty blocks... */
159 if (Begin == NULL || End == NULL) return;
160 /* ... or malformed blocks */
161 if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return;
162
163 /* Compute the number of characters to copy */
164 if (End->Y == Begin->Y) // top == bottom
165 {
166 NumChars = End->X - Begin->X + 1;
167 }
168 else // if (End->Y > Begin->Y)
169 {
170 NumChars = Buffer->ScreenBufferSize.X - Begin->X;
171
172 if (End->Y >= Begin->Y + 2)
173 {
174 NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X;
175 }
176
177 NumChars += End->X + 1;
178 }
179
180 size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */
181
182 /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
183 hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
184 if (hData == NULL) return;
185
186 data = GlobalLock(hData);
187 if (data == NULL)
188 {
189 GlobalFree(hData);
190 return;
191 }
192
193 DPRINT("Copying %d characters\n", NumChars);
194 dstPos = data;
195
196 /*
197 * We need to walk per-lines, and not just looping in the big screen-buffer
198 * array, because of the way things are stored inside it. The downside is
199 * that it makes the code more complicated.
200 */
201 for (yPos = Begin->Y; (yPos <= End->Y) && (NumChars > 0); yPos++)
202 {
203 xBeg = (yPos == Begin->Y ? Begin->X : 0);
204 xEnd = (yPos == End->Y ? End->X : Buffer->ScreenBufferSize.X - 1);
205
206 ptr = ConioCoordToPointer(Buffer, 0, yPos);
207
208 /* Copy only the characters, leave attributes alone */
209 for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++)
210 {
211 /*
212 * Sometimes, applications can put NULL chars into the screen-buffer
213 * (this behaviour is allowed). Detect this and replace by a space.
214 */
215 *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
216 }
217 }
218
219 DPRINT("Setting data <%S> to clipboard\n", data);
220 GlobalUnlock(hData);
221
222 EmptyClipboard();
223 SetClipboardData(CF_UNICODETEXT, hData);
224 }
225
226
227 VOID
228 GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
229 PCOORD SelectionAnchor,
230 PSMALL_RECT SmallRect);
231
232 VOID
233 GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
234 PGUI_CONSOLE_DATA GuiData)
235 {
236 /*
237 * This function supposes that the system clipboard was opened.
238 */
239
240 BOOL LineSelection = GuiData->LineSelection;
241
242 DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n",
243 GuiData->Selection.srSelection.Left,
244 GuiData->Selection.srSelection.Top,
245 GuiData->Selection.srSelection.Right,
246 GuiData->Selection.srSelection.Bottom,
247 (LineSelection ? "line" : "block"));
248
249 if (!LineSelection)
250 {
251 CopyBlock(Buffer, &GuiData->Selection.srSelection);
252 }
253 else
254 {
255 COORD Begin, End;
256
257 GetSelectionBeginEnd(&Begin, &End,
258 &GuiData->Selection.dwSelectionAnchor,
259 &GuiData->Selection.srSelection);
260
261 CopyLines(Buffer, &Begin, &End);
262 }
263 }
264
265 VOID
266 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
267 PGUI_CONSOLE_DATA GuiData)
268 {
269 /*
270 * This function supposes that the system clipboard was opened.
271 */
272
273 PCONSOLE Console = Buffer->Header.Console;
274
275 HANDLE hData;
276 LPWSTR str;
277 WCHAR CurChar = 0;
278
279 USHORT VkKey; // MAKEWORD(low = vkey_code, high = shift_state);
280 INPUT_RECORD er;
281
282 hData = GetClipboardData(CF_UNICODETEXT);
283 if (hData == NULL) return;
284
285 str = GlobalLock(hData);
286 if (str == NULL) return;
287
288 DPRINT("Got data <%S> from clipboard\n", str);
289
290 er.EventType = KEY_EVENT;
291 er.Event.KeyEvent.wRepeatCount = 1;
292 while (*str)
293 {
294 /* \r or \n characters. Go to the line only if we get "\r\n" sequence. */
295 if (CurChar == L'\r' && *str == L'\n')
296 {
297 str++;
298 continue;
299 }
300 CurChar = *str++;
301
302 /* Get the key code (+ shift state) corresponding to the character */
303 VkKey = VkKeyScanW(CurChar);
304 if (VkKey == 0xFFFF)
305 {
306 DPRINT1("VkKeyScanW failed - Should simulate the key...\n");
307 continue;
308 }
309
310 /* Pressing some control keys */
311
312 /* Pressing the character key, with the control keys maintained pressed */
313 er.Event.KeyEvent.bKeyDown = TRUE;
314 er.Event.KeyEvent.wVirtualKeyCode = LOBYTE(VkKey);
315 er.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyW(LOBYTE(VkKey), MAPVK_VK_TO_CHAR);
316 er.Event.KeyEvent.uChar.UnicodeChar = CurChar;
317 er.Event.KeyEvent.dwControlKeyState = 0;
318 if (HIBYTE(VkKey) & 1)
319 er.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
320 if (HIBYTE(VkKey) & 2)
321 er.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED; // RIGHT_CTRL_PRESSED;
322 if (HIBYTE(VkKey) & 4)
323 er.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; // RIGHT_ALT_PRESSED;
324
325 ConioProcessInputEvent(Console, &er);
326
327 /* Up all the character and control keys */
328 er.Event.KeyEvent.bKeyDown = FALSE;
329 ConioProcessInputEvent(Console, &er);
330 }
331
332 GlobalUnlock(hData);
333 }
334
335 VOID
336 GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
337 PGUI_CONSOLE_DATA GuiData,
338 PRECT rcView,
339 PRECT rcFramebuffer)
340 {
341 PCONSOLE Console = Buffer->Header.Console;
342 // ASSERT(Console == GuiData->Console);
343
344 ULONG TopLine, BottomLine, LeftChar, RightChar;
345 ULONG Line, Char, Start;
346 PCHAR_INFO From;
347 PWCHAR To;
348 WORD LastAttribute, Attribute;
349 ULONG CursorX, CursorY, CursorHeight;
350 HBRUSH CursorBrush, OldBrush;
351 HFONT OldFont;
352
353 if (Buffer->Buffer == NULL) return;
354
355 if (!ConDrvValidateConsoleUnsafe(Console, CONSOLE_RUNNING, TRUE)) return;
356
357 rcFramebuffer->left = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->left;
358 rcFramebuffer->top = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->top;
359 rcFramebuffer->right = Buffer->ViewOrigin.X * GuiData->CharWidth + rcView->right;
360 rcFramebuffer->bottom = Buffer->ViewOrigin.Y * GuiData->CharHeight + rcView->bottom;
361
362 LeftChar = rcFramebuffer->left / GuiData->CharWidth;
363 TopLine = rcFramebuffer->top / GuiData->CharHeight;
364 RightChar = rcFramebuffer->right / GuiData->CharWidth;
365 BottomLine = rcFramebuffer->bottom / GuiData->CharHeight;
366
367 if (RightChar >= Buffer->ScreenBufferSize.X) RightChar = Buffer->ScreenBufferSize.X - 1;
368 if (BottomLine >= Buffer->ScreenBufferSize.Y) BottomLine = Buffer->ScreenBufferSize.Y - 1;
369
370 LastAttribute = ConioCoordToPointer(Buffer, LeftChar, TopLine)->Attributes;
371
372 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
373 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
374
375 OldFont = SelectObject(GuiData->hMemDC, GuiData->Font);
376
377 for (Line = TopLine; Line <= BottomLine; Line++)
378 {
379 WCHAR LineBuffer[80]; // Buffer containing a part or all the line to be displayed
380 From = ConioCoordToPointer(Buffer, LeftChar, Line); // Get the first code of the line
381 Start = LeftChar;
382 To = LineBuffer;
383
384 for (Char = LeftChar; Char <= RightChar; Char++)
385 {
386 /*
387 * We flush the buffer if the new attribute is different
388 * from the current one, or if the buffer is full.
389 */
390 if (From->Attributes != LastAttribute || (Char - Start == sizeof(LineBuffer) / sizeof(WCHAR)))
391 {
392 TextOutW(GuiData->hMemDC,
393 Start * GuiData->CharWidth,
394 Line * GuiData->CharHeight,
395 LineBuffer,
396 Char - Start);
397 Start = Char;
398 To = LineBuffer;
399 Attribute = From->Attributes;
400 if (Attribute != LastAttribute)
401 {
402 SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
403 SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute)));
404 LastAttribute = Attribute;
405 }
406 }
407
408 *(To++) = (From++)->Char.UnicodeChar;
409 }
410
411 TextOutW(GuiData->hMemDC,
412 Start * GuiData->CharWidth,
413 Line * GuiData->CharHeight,
414 LineBuffer,
415 RightChar - Start + 1);
416 }
417
418 /*
419 * Draw the caret
420 */
421 if (Buffer->CursorInfo.bVisible &&
422 Buffer->CursorBlinkOn &&
423 !Buffer->ForceCursorOff)
424 {
425 CursorX = Buffer->CursorPosition.X;
426 CursorY = Buffer->CursorPosition.Y;
427 if (LeftChar <= CursorX && CursorX <= RightChar &&
428 TopLine <= CursorY && CursorY <= BottomLine)
429 {
430 CursorHeight = ConioEffectiveCursorSize(Console, GuiData->CharHeight);
431
432 Attribute = ConioCoordToPointer(Buffer, Buffer->CursorPosition.X, Buffer->CursorPosition.Y)->Attributes;
433 if (Attribute == DEFAULT_SCREEN_ATTRIB) Attribute = Buffer->ScreenDefaultAttrib;
434
435 CursorBrush = CreateSolidBrush(PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
436 OldBrush = SelectObject(GuiData->hMemDC, CursorBrush);
437
438 PatBlt(GuiData->hMemDC,
439 CursorX * GuiData->CharWidth,
440 CursorY * GuiData->CharHeight + (GuiData->CharHeight - CursorHeight),
441 GuiData->CharWidth,
442 CursorHeight,
443 PATCOPY);
444 SelectObject(GuiData->hMemDC, OldBrush);
445 DeleteObject(CursorBrush);
446 }
447 }
448
449 SelectObject(GuiData->hMemDC, OldFont);
450
451 LeaveCriticalSection(&Console->Lock);
452 }
453
454 /* EOF */