Fix tab size (3) take 2

Vitaliy Margolen wine-patch at kievinfo.com
Sun Oct 26 16:43:54 CST 2003


This time with more fixes and tests.


Experimenting with tab control I found this differences between MSDN and actual
implementation:
- TCS_OWNERDRAWFIXED style has nothing to do with fixed with tabs. It should me
  named TCS_OWNERDRAW instead. Unless there is some apps that think otherwise,
  this should work (tested on eMule and test program).

  In other words, this quote from MSDN is not true:
  "Because all tabs in an owner-drawn tab control are the same size..."

- It looks like there is not such thing as not set tab width. According to this:

  "To give all tabs the same width, you can specify the TCS_FIXEDWIDTH
  style. The control sizes all the tabs to fit the widest label, or you can assign
  a specific width and height by using the TCM_SETITEMSIZE message."

  one can assume that if tab width is not explicitly set with TCM_SETITEMSIZE
  message "The control sizes all the tabs to fit the widest label".

  Not true. Native uses default tab size of 96.

- The minimum size of a tab is at least icon width + 4 if icon is present.

- Prior observation that "under Windows, there seems to be a minimum width of 2x
  the height for button style tabs" is not true.

Vitaliy Margolen

changelog:
  - Fix tab size for TCS_OWNERDRAWFIXED style
  - Correct size recalculation after setting tab width
  - Fix button sizes to match native.
  - Center both vertically and horizontally tab text & icon
  - Use correct left/center alignment flags

to-do (currently working on):
  - Correct text & icon position within each tab/button to match native
  - Fix hot-track
  - Correct tab sizes for vertical styles
-------------- next part --------------
? tests/tab.c
Index: tab.c
===================================================================
RCS file: /home/wine/wine/dlls/comctl32/tab.c,v
retrieving revision 1.86
diff -u -r1.86 tab.c
--- tab.c	14 Oct 2003 20:12:05 -0000	1.86
+++ tab.c	26 Oct 2003 22:05:23 -0000
@@ -93,7 +93,7 @@
 #define DISPLAY_AREA_PADDINGY   2
 #define CONTROL_BORDER_SIZEX    2
 #define CONTROL_BORDER_SIZEY    2
-#define BUTTON_SPACINGX         4
+#define BUTTON_SPACINGX         3
 #define BUTTON_SPACINGY         4
 #define FLAT_BTN_SPACINGX       8
 #define DEFAULT_TAB_WIDTH       96
@@ -344,11 +344,10 @@
     itemRect->bottom = clientRect.top +
                       infoPtr->tabHeight +
                       itemRect->top * (infoPtr->tabHeight - 2) +
-                      ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
+                      ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
     itemRect->top = clientRect.top +
-                   SELECTED_TAB_OFFSET +
                    itemRect->top * (infoPtr->tabHeight - 2) +
-                   ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : 0);
+                   ((lStyle & TCS_BUTTONS) ? itemRect->top * BUTTON_SPACINGY : SELECTED_TAB_OFFSET);
  }
 
   /*
@@ -1072,6 +1071,7 @@
   INT         iTemp;
   RECT*       rcItem;
   INT         iIndex;
+  INT         icon_width = 0;
 
   /*
    * We need to get text information so we need a DC and we need to select
@@ -1134,38 +1134,36 @@
 
   TRACE("client right=%ld\n", clientRect.right);
 
+  /* Get the icon width */
+  if (infoPtr->himl)
+  {
+    ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
+
+    if (lStyle & TCS_FIXEDWIDTH)
+      icon_width += 4;
+    else
+      /* Add padding if icon is present */
+      icon_width += infoPtr->uHItemPadding;
+  }
+
   for (curItem = 0; curItem < infoPtr->uNumItem; curItem++)
   {
     /* Set the leftmost position of the tab. */
     infoPtr->items[curItem].rect.left = curItemLeftPos;
 
-    if ( lStyle & (TCS_FIXEDWIDTH | TCS_OWNERDRAWFIXED) )
+    if (lStyle & TCS_FIXEDWIDTH)
     {
       infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
-                                           infoPtr->tabWidth +
-                                           2 * infoPtr->uHItemPadding;
+        max(infoPtr->tabWidth, icon_width);
     }
     else
     {
-      int icon_width  = 0;
       int num = 2;
 
       /* Calculate how wide the tab is depending on the text it contains */
       GetTextExtentPoint32W(hdc, infoPtr->items[curItem].pszText,
                             lstrlenW(infoPtr->items[curItem].pszText), &size);
 
-      /* under Windows, there seems to be a minimum width of 2x the height
-       * for button style tabs */
-      if (lStyle & TCS_BUTTONS)
-	      size.cx = max(size.cx, 2 * (infoPtr->tabHeight - 2));
-
-      /* Add the icon width */
-      if (infoPtr->himl)
-      {
-        ImageList_GetIconSize(infoPtr->himl, &icon_width, 0);
-        num++;
-      }
-
       infoPtr->items[curItem].rect.right = infoPtr->items[curItem].rect.left +
                                            size.cx + icon_width +
                                            num * infoPtr->uHItemPadding;
@@ -1215,7 +1213,7 @@
      */
     if (lStyle & TCS_BUTTONS)
     {
-      curItemLeftPos = infoPtr->items[curItem].rect.right + 1;
+      curItemLeftPos = infoPtr->items[curItem].rect.right + BUTTON_SPACINGX;
       if (lStyle & TCS_FLATBUTTONS)
         curItemLeftPos += FLAT_BTN_SPACINGX;
     }
@@ -1547,7 +1545,7 @@
 
     /* used to center the icon and text in the tab */
     RECT rcText;
-    INT center_offset;
+    INT center_offset_h, center_offset_v;
 
     /*
      * Deflate the rectangle to acount for the padding
@@ -1585,34 +1583,46 @@
       ImageList_GetIconSize(infoPtr->himl, &cx, &cy);
 
       if(lStyle & TCS_VERTICAL)
-        center_offset = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
+      {
+        center_offset_h = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
+        center_offset_v = ((drawRect->right - drawRect->left) - (cx + infoPtr->uVItemPadding)) / 2;
+      }
       else
-        center_offset = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right - rcText.left))) / 2;
+      {
+        center_offset_h = ((drawRect->right - drawRect->left) - (cx + infoPtr->uHItemPadding + (rcText.right  - rcText.left))) / 2;
+        center_offset_v = ((drawRect->bottom - drawRect->top) - (cy + infoPtr->uVItemPadding)) / 2;
+      }
+
+      if ((lStyle & TCS_FIXEDWIDTH &&
+           lStyle & (TCS_FORCELABELLEFT | TCS_FORCEICONLEFT)) ||
+	  (center_offset_h < 0))
+	center_offset_h = 0;
 
       TRACE("for <%s>, c_o=%d, draw=(%ld,%ld)-(%ld,%ld), textlen=%ld\n",
-	  debugstr_w(infoPtr->items[iItem].pszText), center_offset,
+	  debugstr_w(infoPtr->items[iItem].pszText), center_offset_h,
 	  drawRect->left, drawRect->top, drawRect->right, drawRect->bottom,
 	  (rcText.right-rcText.left));
 
       if((lStyle & TCS_VERTICAL) && (lStyle & TCS_BOTTOM))
       {
-        rcImage.top = drawRect->top + center_offset;
-        rcImage.left = drawRect->right - cx; /* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
-                                             /* right side of the tab, but the image still uses the left as its x position */
-                                             /* this keeps the image always drawn off of the same side of the tab */
+        rcImage.top  = drawRect->top + center_offset_h;
+	/* if tab is TCS_VERTICAL and TCS_BOTTOM, the text is drawn from the */
+	/* right side of the tab, but the image still uses the left as its x position */
+	/* this keeps the image always drawn off of the same side of the tab */
+        rcImage.left = drawRect->right - cx - center_offset_v;
         drawRect->top = rcImage.top + (cx + infoPtr->uHItemPadding);
       }
       else if(lStyle & TCS_VERTICAL)
       {
-        rcImage.top = drawRect->bottom - cy - center_offset;
-	rcImage.left--;
+        rcImage.top  = drawRect->bottom - cy - center_offset_h;
+	rcImage.left = drawRect->left + center_offset_v;
         drawRect->bottom = rcImage.top - infoPtr->uHItemPadding;
       }
       else /* normal style, whether TCS_BOTTOM or not */
       {
-        rcImage.left = drawRect->left + center_offset + 3;
+        rcImage.left = drawRect->left + center_offset_h + 3;
         drawRect->left = rcImage.left + cx + infoPtr->uHItemPadding;
-	rcImage.top -= (lStyle & TCS_BOTTOM) ? 2 : 1;
+	rcImage.top = drawRect->top + center_offset_v;
       }
 
       TRACE("drawing image=%d, left=%ld, top=%ld\n",
@@ -1626,33 +1636,47 @@
         rcImage.top,
         ILD_NORMAL
         );
-    } else /* no image, so just shift the drawRect borders around */
+    }
+    else /* no image, so just shift the drawRect borders around */
     {
       if(lStyle & TCS_VERTICAL)
       {
-        center_offset = 0;
+        center_offset_h = 0;
         /*
         currently the rcText rect is flawed because the rotated font does not
         often match the horizontal font. So leave this as 0
         ((drawRect->bottom - drawRect->top) - (rcText.right - rcText.left)) / 2;
         */
         if(lStyle & TCS_BOTTOM)
-          drawRect->top+=center_offset;
+          drawRect->top+=center_offset_h;
         else
-          drawRect->bottom-=center_offset;
+          drawRect->bottom-=center_offset_h;
       }
       else
       {
-        center_offset = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
-        drawRect->left+=center_offset;
+        center_offset_h = ((drawRect->right - drawRect->left) - (rcText.right - rcText.left)) / 2;
+        drawRect->left+=center_offset_h;
       }
     }
 
-    /* Draw the text */
-    if (lStyle & TCS_RIGHTJUSTIFY)
-      uHorizAlign = DT_CENTER;
+    if(lStyle & TCS_VERTICAL)
+    {
+      center_offset_v = ((drawRect->right - drawRect->left) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
+      drawRect->left += center_offset_v;
+    }
     else
+    {
+      center_offset_v = ((drawRect->bottom - drawRect->top) - ((rcText.bottom - rcText.top) + infoPtr->uVItemPadding)) / 2;
+      drawRect->top += center_offset_v;
+    }
+
+
+    /* Draw the text */
+    if ((lStyle & TCS_FIXEDWIDTH && lStyle & TCS_FORCELABELLEFT) ||
+	!center_offset_h)
       uHorizAlign = DT_LEFT;
+    else
+      uHorizAlign = DT_CENTER;
 
     if(lStyle & TCS_VERTICAL) /* if we are vertical rotate the text and each character */
     {
@@ -2572,8 +2596,7 @@
   lResult = MAKELONG(infoPtr->tabWidth, infoPtr->tabHeight);
 
   /* UNDOCUMENTED: If requested Width or Height is 0 this means that program wants to use auto size. */
-  if ((lStyle & (TCS_FIXEDWIDTH | TCS_OWNERDRAWFIXED)) &&
-      (infoPtr->tabWidth != (INT)LOWORD(lParam)))
+  if (lStyle & TCS_FIXEDWIDTH && (infoPtr->tabWidth != (INT)LOWORD(lParam)))
   {
     infoPtr->tabWidth = max((INT)LOWORD(lParam), infoPtr->tabMinWidth);
     bNeedPaint = TRUE;
@@ -2583,8 +2606,6 @@
   {
     if ((infoPtr->fHeightSet = ((INT)HIWORD(lParam) != 0)))
       infoPtr->tabHeight = (INT)HIWORD(lParam);
-    else
-      TAB_SetItemBounds(hwnd);
 
     bNeedPaint = TRUE;
   }
@@ -2593,7 +2614,10 @@
        infoPtr->tabHeight, infoPtr->tabWidth);
 
   if (bNeedPaint)
+  {
+    TAB_SetItemBounds(hwnd);
     RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
+  }
     
   return lResult;
 }
@@ -2990,7 +3014,7 @@
   infoPtr->needsScrolling  = FALSE;
   infoPtr->hwndUpDown      = 0;
   infoPtr->leftmostVisible = 0;
-  infoPtr->fHeightSet     = FALSE;
+  infoPtr->fHeightSet      = FALSE;
   infoPtr->bUnicode	   = IsWindowUnicode (hwnd);
 
   TRACE("Created tab control, hwnd [%p]\n", hwnd);
@@ -3043,8 +3067,7 @@
 
   /* Initialize the width of a tab. */
   infoPtr->tabWidth = DEFAULT_TAB_WIDTH;
-  /* The minimum width is the default width at creation */
-  infoPtr->tabMinWidth = DEFAULT_TAB_WIDTH;
+  infoPtr->tabMinWidth = 0;
 
   TRACE("tabH=%d, tabW=%d\n", infoPtr->tabHeight, infoPtr->tabWidth);
 
Index: tests/Makefile.in
===================================================================
RCS file: /home/wine/wine/dlls/comctl32/tests/Makefile.in,v
retrieving revision 1.1
diff -u -r1.1 Makefile.in
--- tests/Makefile.in	15 May 2003 23:58:48 -0000	1.1
+++ tests/Makefile.in	26 Oct 2003 22:05:23 -0000
@@ -3,10 +3,11 @@
 SRCDIR    = @srcdir@
 VPATH     = @srcdir@
 TESTDLL   = comctl32.dll
-IMPORTS   = comctl32
+IMPORTS   = comctl32 user32
 
 CTESTS = \
-	dpa.c
+	dpa.c \
+	tab.c
 
 @MAKE_TEST_RULES@
 
--- /dev/null	2003-09-20 05:56:08.000000000 -0600
+++ tests/tab.c	2003-10-26 15:09:08.000000000 -0700
@@ -0,0 +1,165 @@
+/* Unit test suite for tab control.
+ *
+ * Copyright 2003 Vitaliy Margolen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <assert.h>
+#include <windows.h>
+#include <commctrl.h>
+
+#include "wine/test.h"
+
+#undef VISIBLE
+
+#define TAB_DEFAULT_WIDTH 96
+#define TAB_PADDING_X 2
+#define TAB_PADDING_Y 2
+
+#ifdef VISIBLE
+#define WAIT Sleep (1000)
+#define REDRAW(hwnd) RedrawWindow (hwnd, NULL, 0, RDW_UPDATENOW)
+#else
+#define WAIT
+#define REDRAW(hwnd)
+#endif
+
+HWND
+create_tabcontrol (DWORD style)
+{
+    HWND handle;
+    TCITEM tcNewTab;
+
+    handle = CreateWindow (
+	WC_TABCONTROLA,
+	"TestTab",
+	WS_CLIPSIBLINGS | WS_CLIPCHILDREN | TCS_FOCUSNEVER | style,
+        0, 0, 300, 100,
+        NULL, NULL, NULL, 0);
+
+    assert (handle);
+
+    tcNewTab.mask = TCIF_TEXT | TCIF_IMAGE;
+    tcNewTab.pszText = "Tab 1";
+    tcNewTab.iImage = 0;
+    SendMessage (handle, TCM_INSERTITEM, 0, (LPARAM) &tcNewTab);
+    tcNewTab.pszText = "Wide Tab 2";
+    tcNewTab.iImage = 1;
+    SendMessage (handle, TCM_INSERTITEM, 1, (LPARAM) &tcNewTab);
+    tcNewTab.pszText = "T 3";
+    tcNewTab.iImage = 2;
+    SendMessage (handle, TCM_INSERTITEM, 2, (LPARAM) &tcNewTab);
+
+#ifdef VISIBLE
+    ShowWindow (handle, SW_SHOW);
+#endif
+    REDRAW(handle);
+    WAIT;
+
+    return handle;
+}
+
+void CheckSize(HWND hwnd, INT width, INT height)
+{
+    RECT rTab;
+
+    SendMessage (hwnd, TCM_GETITEMRECT, 1, (LPARAM) &rTab);
+    /*trace ("Got (%ld,%ld)-(%ld,%ld)\n", rTab.left, rTab.top, rTab.right, rTab.bottom);*/
+    if ((width  >= 0) && (height < 0))
+	ok (width  == rTab.right  - rTab.left, "Expected [%d] got [%ld]",  width,  rTab.right  - rTab.left);
+    else if ((height >= 0) && (width  < 0))
+	ok (height == rTab.bottom - rTab.top,  "Expected [%d] got [%ld]",  height, rTab.bottom - rTab.top);
+    else
+	ok ((width  == rTab.right  - rTab.left) &&
+	    (height == rTab.bottom - rTab.top ),
+	    "Expected [%d,%d] got [%ld,%ld]", width, height, rTab.right - rTab.left, rTab.bottom - rTab.top);
+}
+
+void TabCheckSetSize(HWND hwnd, INT SetWidth, INT SetHeight, INT ExpWidth, INT ExpHeight)
+{
+    SendMessage (hwnd, TCM_SETITEMSIZE, 0,
+	(LPARAM) MAKELPARAM((SetWidth >= 0) ? SetWidth:0, (SetHeight >= 0) ? SetHeight:0));
+    REDRAW(hwnd);
+    CheckSize(hwnd, ExpWidth, ExpHeight);
+    WAIT;
+}
+
+START_TEST(tab)
+{
+    HWND hwTab;
+    HIMAGELIST himl = ImageList_Create(21, 21, ILC_COLOR, 3, 4);
+
+    InitCommonControls();
+
+
+    hwTab = create_tabcontrol(TCS_FIXEDWIDTH);
+
+    trace ("Testing TCS_FIXEDWIDTH tabs no icon...\n");
+    trace ("  default width...\n");
+    CheckSize(hwTab, TAB_DEFAULT_WIDTH, -1);
+    trace ("  set size...\n");
+    TabCheckSetSize(hwTab, 50, 20, 50, 20);
+    WAIT;
+    trace ("  min size...\n");
+    TabCheckSetSize(hwTab, 0, 1, 0, 1);
+    WAIT;
+
+    SendMessage(hwTab, TCM_SETIMAGELIST, 0, (LPARAM)himl);
+
+    trace ("Testing TCS_FIXEDWIDTH tabs with icon...\n");
+    trace ("  set size > icon...\n");
+    TabCheckSetSize(hwTab, 50, 30, 50, 30);
+    WAIT;
+    trace ("  set size < icon...\n");
+    TabCheckSetSize(hwTab, 20, 20, 25, 20);
+    WAIT;
+    trace ("  min size...\n");
+    TabCheckSetSize(hwTab, 0, 1, 25, 1);
+    WAIT;
+
+    DestroyWindow (hwTab);
+
+    trace ("Testing TCS_FIXEDWIDTH buttons no icon...\n");
+    hwTab = create_tabcontrol(TCS_FIXEDWIDTH | TCS_BUTTONS);
+
+    trace ("  default width...\n");
+    CheckSize(hwTab, TAB_DEFAULT_WIDTH, -1);
+    trace ("  set size 1...\n");
+    TabCheckSetSize(hwTab, 20, 20, 20, 20);
+    trace ("  set size 2...\n");
+    TabCheckSetSize(hwTab, 10, 50, 10, 50);
+    trace ("  min size...\n");
+    TabCheckSetSize(hwTab, 0, 1, 0, 1);
+
+    SendMessage(hwTab, TCM_SETIMAGELIST, 0, (LPARAM)himl);
+
+    trace ("Testing TCS_FIXEDWIDTH buttons with icon...\n");
+    trace ("  set size > icon...\n");
+    TabCheckSetSize(hwTab, 50, 30, 50, 30);
+    trace ("  set size < icon...\n");
+    TabCheckSetSize(hwTab, 20, 20, 25, 20);
+    trace ("  min size...\n");
+    TabCheckSetSize(hwTab, 0, 1, 25, 1);
+
+    trace (" Add padding...\n");
+    SendMessage(hwTab, TCM_SETPADDING, 0, MAKELPARAM(4,4));
+    trace ("  min size...\n");
+    TabCheckSetSize(hwTab, 0, 1, 25, 1);
+    WAIT;
+    
+    DestroyWindow (hwTab);
+    ImageList_Destroy(himl);
+}


More information about the wine-patches mailing list