[CONSRV]: Support underline code COMMON_LVB_UNDERSCORE in the console via CreateFont...
[reactos.git] / win32ss / user / winsrv / consrv / frontends / gui / text.c
index 84d0101..50ebbb6 100644 (file)
 
 #include "guiterm.h"
 
+/* GLOBALS ********************************************************************/
+
+#define IS_WHITESPACE(c)    ((c) == L'\0' || (c) == L' ' || (c) == L'\t')
+
 /* FUNCTIONS ******************************************************************/
 
-COLORREF PaletteRGBFromAttrib(PCONSOLE Console, WORD Attribute)
+static COLORREF
+PaletteRGBFromAttrib(PCONSOLE Console, WORD Attribute)
 {
     HPALETTE hPalette = Console->ActiveBuffer->PaletteHandle;
     PALETTEENTRY pe;
@@ -31,38 +36,71 @@ COLORREF PaletteRGBFromAttrib(PCONSOLE Console, WORD Attribute)
     return PALETTERGB(pe.peRed, pe.peGreen, pe.peBlue);
 }
 
-VOID
-GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
-                          PGUI_CONSOLE_DATA GuiData)
+static HFONT
+ChangeFontAttributes(PGUI_CONSOLE_DATA GuiData,
+                     // COORD   FontSize,
+                     ULONG   FontWeight,
+                     BOOLEAN bItalic,
+                     BOOLEAN bUnderline,
+                     BOOLEAN bStrikeOut)
 {
-    /*
-     * This function supposes that the system clipboard was opened.
-     */
+    HFONT NewFont;
+    LOGFONT lf;
+
+    /* Initialize the LOGFONT structure */
+    RtlZeroMemory(&lf, sizeof(lf));
+
+    /* Retrieve the details of the current font */
+    if (GetObject(GuiData->Font, sizeof(lf), &lf) == 0)
+        return NULL; // GuiData->Font;
+
+    /* Change the font attributes */
+    // lf.lfHeight = FontSize.Y;
+    // lf.lfWidth  = FontSize.X;
+    lf.lfWeight = FontWeight;
+    lf.lfItalic = bItalic;
+    lf.lfUnderline = bUnderline;
+    lf.lfStrikeOut = bStrikeOut;
+
+    /* Build a new font */
+    NewFont = CreateFontIndirect(&lf);
+    if (NewFont == NULL)
+        return NULL; // GuiData->Font;
+
+    // FIXME: Do we need to update GuiData->CharWidth and GuiData->CharHeight ??
+
+    /* Select it (return the old font) */
+    // return SelectObject(GuiData->hMemDC, NewFont);
+    return NewFont;
+}
 
+static VOID
+CopyBlock(PTEXTMODE_SCREEN_BUFFER Buffer,
+          PSMALL_RECT Selection)
+{
     /*
      * Pressing the Shift key while copying text, allows us to copy
      * text without newline characters (inline-text copy mode).
      */
-    BOOL InlineCopyMode = (GetKeyState(VK_SHIFT) & 0x8000);
+    BOOL InlineCopyMode = !!(GetKeyState(VK_SHIFT) & 0x8000);
 
     HANDLE hData;
     PCHAR_INFO ptr;
     LPWSTR data, dstPos;
     ULONG selWidth, selHeight;
-    ULONG xPos, yPos, size;
+    ULONG xPos, yPos;
+    ULONG size;
 
-    selWidth  = GuiData->Selection.srSelection.Right - GuiData->Selection.srSelection.Left + 1;
-    selHeight = GuiData->Selection.srSelection.Bottom - GuiData->Selection.srSelection.Top + 1;
-    DPRINT("Selection is (%d|%d) to (%d|%d)\n",
-           GuiData->Selection.srSelection.Left,
-           GuiData->Selection.srSelection.Top,
-           GuiData->Selection.srSelection.Right,
-           GuiData->Selection.srSelection.Bottom);
+    DPRINT("CopyBlock(%u, %u, %u, %u)\n",
+           Selection->Left, Selection->Top, Selection->Right, Selection->Bottom);
 
-#ifdef IS_WHITESPACE
-#undef IS_WHITESPACE
-#endif
-#define IS_WHITESPACE(c)    ((c) == L'\0' || (c) == L' ' || (c) == L'\t')
+    /* Prevent against empty blocks */
+    if (Selection == NULL) return;
+    if (Selection->Left > Selection->Right || Selection->Top > Selection->Bottom)
+        return;
+
+    selWidth  = Selection->Right - Selection->Left + 1;
+    selHeight = Selection->Bottom - Selection->Top + 1;
 
     /* Basic size for one line... */
     size = selWidth;
@@ -75,6 +113,11 @@ GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
          */
         size += (selWidth + (!InlineCopyMode ? 2 : 0)) * (selHeight - 1);
     }
+    else
+    {
+        DPRINT1("This case must never happen, because selHeight is at least == 1\n");
+    }
+
     size += 1; /* Null-termination */
     size *= sizeof(WCHAR);
 
@@ -97,8 +140,8 @@ GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
         ULONG length = selWidth;
 
         ptr = ConioCoordToPointer(Buffer,
-                                  GuiData->Selection.srSelection.Left,
-                                  GuiData->Selection.srSelection.Top + yPos);
+                                  Selection->Left,
+                                  Selection->Top + yPos);
 
         /* Trim whitespace from the right */
         while (length > 0)
@@ -116,16 +159,15 @@ GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
              * Sometimes, applications can put NULL chars into the screen-buffer
              * (this behaviour is allowed). Detect this and replace by a space.
              */
-            dstPos[xPos] = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
+            *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
         }
-        dstPos += length;
 
         /* Add newline characters if we are not in inline-text copy mode */
         if (!InlineCopyMode)
         {
             if (yPos != (selHeight - 1))
             {
-                wcscat(data, L"\r\n");
+                wcscat(dstPos, L"\r\n");
                 dstPos += 2;
             }
         }
@@ -138,6 +180,127 @@ GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
     SetClipboardData(CF_UNICODETEXT, hData);
 }
 
+static VOID
+CopyLines(PTEXTMODE_SCREEN_BUFFER Buffer,
+          PCOORD Begin,
+          PCOORD End)
+{
+    HANDLE hData;
+    PCHAR_INFO ptr;
+    LPWSTR data, dstPos;
+    ULONG NumChars, size;
+    ULONG xPos, yPos, xBeg, xEnd;
+
+    DPRINT("CopyLines((%u, %u) ; (%u, %u))\n",
+           Begin->X, Begin->Y, End->X, End->Y);
+
+    /* Prevent against empty blocks... */
+    if (Begin == NULL || End == NULL) return;
+    /* ... or malformed blocks */
+    if (Begin->Y > End->Y || (Begin->Y == End->Y && Begin->X > End->X)) return;
+
+    /* Compute the number of characters to copy */
+    if (End->Y == Begin->Y) // top == bottom
+    {
+        NumChars = End->X - Begin->X + 1;
+    }
+    else // if (End->Y > Begin->Y)
+    {
+        NumChars = Buffer->ScreenBufferSize.X - Begin->X;
+
+        if (End->Y >= Begin->Y + 2)
+        {
+            NumChars += (End->Y - Begin->Y - 1) * Buffer->ScreenBufferSize.X;
+        }
+
+        NumChars += End->X + 1;
+    }
+
+    size = (NumChars + 1) * sizeof(WCHAR); /* Null-terminated */
+
+    /* Allocate some memory area to be given to the clipboard, so it will not be freed here */
+    hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, size);
+    if (hData == NULL) return;
+
+    data = GlobalLock(hData);
+    if (data == NULL)
+    {
+        GlobalFree(hData);
+        return;
+    }
+
+    DPRINT("Copying %d characters\n", NumChars);
+    dstPos = data;
+
+    /*
+     * We need to walk per-lines, and not just looping in the big screen-buffer
+     * array, because of the way things are stored inside it. The downside is
+     * that it makes the code more complicated.
+     */
+    for (yPos = Begin->Y; (yPos <= End->Y) && (NumChars > 0); yPos++)
+    {
+        xBeg = (yPos == Begin->Y ? Begin->X : 0);
+        xEnd = (yPos ==   End->Y ?   End->X : Buffer->ScreenBufferSize.X - 1);
+
+        ptr = ConioCoordToPointer(Buffer, 0, yPos);
+
+        /* Copy only the characters, leave attributes alone */
+        for (xPos = xBeg; (xPos <= xEnd) && (NumChars-- > 0); xPos++)
+        {
+            /*
+             * Sometimes, applications can put NULL chars into the screen-buffer
+             * (this behaviour is allowed). Detect this and replace by a space.
+             */
+            *dstPos++ = (ptr[xPos].Char.UnicodeChar ? ptr[xPos].Char.UnicodeChar : L' ');
+        }
+    }
+
+    DPRINT("Setting data <%S> to clipboard\n", data);
+    GlobalUnlock(hData);
+
+    EmptyClipboard();
+    SetClipboardData(CF_UNICODETEXT, hData);
+}
+
+
+VOID
+GetSelectionBeginEnd(PCOORD Begin, PCOORD End,
+                     PCOORD SelectionAnchor,
+                     PSMALL_RECT SmallRect);
+
+VOID
+GuiCopyFromTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
+                          PGUI_CONSOLE_DATA GuiData)
+{
+    /*
+     * This function supposes that the system clipboard was opened.
+     */
+
+    BOOL LineSelection = GuiData->LineSelection;
+
+    DPRINT("Selection is (%d|%d) to (%d|%d) in %s mode\n",
+           GuiData->Selection.srSelection.Left,
+           GuiData->Selection.srSelection.Top,
+           GuiData->Selection.srSelection.Right,
+           GuiData->Selection.srSelection.Bottom,
+           (LineSelection ? "line" : "block"));
+
+    if (!LineSelection)
+    {
+        CopyBlock(Buffer, &GuiData->Selection.srSelection);
+    }
+    else
+    {
+        COORD Begin, End;
+
+        GetSelectionBeginEnd(&Begin, &End,
+                             &GuiData->Selection.dwSelectionAnchor,
+                             &GuiData->Selection.srSelection);
+
+        CopyLines(Buffer, &Begin, &End);
+    }
+}
+
 VOID
 GuiPasteToTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
                          PGUI_CONSOLE_DATA GuiData)
@@ -224,7 +387,8 @@ GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
     WORD LastAttribute, Attribute;
     ULONG CursorX, CursorY, CursorHeight;
     HBRUSH CursorBrush, OldBrush;
-    HFONT OldFont;
+    HFONT OldFont, NewFont;
+    BOOLEAN IsUnderscore;
 
     if (Buffer->Buffer == NULL) return;
 
@@ -248,7 +412,18 @@ GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
     SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
     SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
 
-    OldFont = SelectObject(GuiData->hMemDC, GuiData->Font);
+    // OldFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight, FALSE, FALSE, FALSE);
+    IsUnderscore = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
+    NewFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight,
+                                   FALSE,
+                                   IsUnderscore,
+                                   FALSE);
+    if (NewFont == NULL)
+    {
+        DPRINT1("ChangeFontAttributes failed, use the original font\n");
+        NewFont = GuiData->Font;
+    }
+    OldFont = SelectObject(GuiData->hMemDC, NewFont);
 
     for (Line = TopLine; Line <= BottomLine; Line++)
     {
@@ -275,9 +450,31 @@ GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
                 Attribute = From->Attributes;
                 if (Attribute != LastAttribute)
                 {
-                    SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(Attribute)));
-                    SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(Attribute)));
                     LastAttribute = Attribute;
+                    SetTextColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, TextAttribFromAttrib(LastAttribute)));
+                    SetBkColor(GuiData->hMemDC, PaletteRGBFromAttrib(Console, BkgdAttribFromAttrib(LastAttribute)));
+
+                    /* Change underscore state if needed */
+                    if (!!(LastAttribute & COMMON_LVB_UNDERSCORE) != IsUnderscore)
+                    {
+                        IsUnderscore = !!(LastAttribute & COMMON_LVB_UNDERSCORE);
+
+                        /* Delete the font we used up to now */
+                        // SelectObject(GuiData->hMemDC, OldFont);
+                        if (NewFont != GuiData->Font) DeleteObject(NewFont);
+                        /* Recreate it */
+                        NewFont = ChangeFontAttributes(GuiData, /* {0}, */ GuiData->GuiInfo.FontWeight,
+                                                       FALSE,
+                                                       IsUnderscore,
+                                                       FALSE);
+                        if (NewFont == NULL)
+                        {
+                            DPRINT1("ChangeFontAttributes failed, use the original font\n");
+                            NewFont = GuiData->Font;
+                        }
+                        /* Select it */
+                        /* OldFont = */ SelectObject(GuiData->hMemDC, NewFont);
+                    }
                 }
             }
 
@@ -317,12 +514,15 @@ GuiPaintTextModeBuffer(PTEXTMODE_SCREEN_BUFFER Buffer,
                    GuiData->CharWidth,
                    CursorHeight,
                    PATCOPY);
+
             SelectObject(GuiData->hMemDC, OldBrush);
             DeleteObject(CursorBrush);
         }
     }
 
+    /* Restore the old font and delete the font we used up to now */
     SelectObject(GuiData->hMemDC, OldFont);
+    if (NewFont != GuiData->Font) DeleteObject(NewFont);
 
     LeaveCriticalSection(&Console->Lock);
 }