[COMCTL32] -Make the lautus hack a bit more strict.
[reactos.git] / reactos / dll / win32 / comctl32 / button.c
index 9319b9c..4e62372 100644 (file)
@@ -155,7 +155,6 @@ const struct builtin_class_descr BUTTON_builtin_class =
     IDC_ARROW,           /* cursor */
     0                    /* brush */
 };
-#endif
 
 
 static inline LONG get_button_state( HWND hwnd )
@@ -182,9 +181,41 @@ static __inline LONG get_ui_state( HWND hwnd )
 
 #endif /* __REACTOS__ */
 
-#ifndef _USER32_
+static inline HFONT get_button_font( HWND hwnd )
+{
+    return (HFONT)GetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET );
+}
+
+static inline void set_button_font( HWND hwnd, HFONT font )
+{
+    SetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET, (LONG_PTR)font );
+}
+
+static inline UINT get_button_type( LONG window_style )
+{
+    return (window_style & BS_TYPEMASK);
+}
+
+/* paint a button of any type */
+static inline void paint_button( HWND hwnd, LONG style, UINT action )
+{
+    if (btnPaintFunc[style] && IsWindowVisible(hwnd))
+    {
+        HDC hdc = GetDC( hwnd );
+        btnPaintFunc[style]( hwnd, hdc, action );
+        ReleaseDC( hwnd, hdc );
+    }
+}
+
+#else
+
 #define NtUserAlterWindowStyle SetWindowLongPtrW
 
+static inline void _SetButtonData(HWND hwnd, PBUTTON_DATA data)
+{
+    SetWindowLongPtrW( hwnd, 0, (LONG)data );
+}
+
 HRGN set_control_clipping( HDC hdc, const RECT *rect )
 {
     RECT rc = *rect;
@@ -206,17 +237,49 @@ HRGN set_control_clipping( HDC hdc, const RECT *rect )
 }
 
 BOOL BUTTON_PaintWithTheme(HTHEME theme, HWND hwnd, HDC hParamDC, LPARAM prfFlag);
+WCHAR *get_button_text( HWND hwnd );
 
-#endif
+static inline LONG_PTR get_button_image(HWND hwnd)
+{
+    return _GetButtonData(hwnd)->image;
+}
+
+static inline LONG_PTR set_button_image(HWND hwnd, LONG_PTR image)
+{
+    PBUTTON_DATA data = _GetButtonData(hwnd);
+    LONG_PTR ret = data->image;
+    data->image = image;
+    return ret;
+}
+
+static inline LONG get_button_state( HWND hwnd )
+{
+    return _GetButtonData(hwnd)->state;
+}
+
+static inline void set_button_state( HWND hwnd, LONG state )
+{
+    _GetButtonData(hwnd)->state = state;
+}
+
+static __inline void set_ui_state( HWND hwnd, LONG flags )
+{
+    _GetButtonData(hwnd)->ui_state = flags;
+}
+
+static __inline LONG get_ui_state( HWND hwnd )
+{
+    return _GetButtonData(hwnd)->ui_state;
+}
 
 static inline HFONT get_button_font( HWND hwnd )
 {
-    return (HFONT)GetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET );
+    return (HFONT)_GetButtonData(hwnd)->font;
 }
 
 static inline void set_button_font( HWND hwnd, HFONT font )
 {
-    SetWindowLongPtrW( hwnd, HFONT_GWL_OFFSET, (LONG_PTR)font );
+    _GetButtonData(hwnd)->font = font;
 }
 
 static inline UINT get_button_type( LONG window_style )
@@ -227,7 +290,6 @@ static inline UINT get_button_type( LONG window_style )
 /* paint a button of any type */
 static inline void paint_button( HWND hwnd, LONG style, UINT action )
 {
-#ifndef _USER32_
     HTHEME theme = GetWindowTheme(hwnd);
     RECT rc;
     HDC hdc = GetDC( hwnd );
@@ -244,18 +306,102 @@ static inline void paint_button( HWND hwnd, LONG style, UINT action )
         btnPaintFunc[style]( hwnd, hdc, action );
     }
     ReleaseDC( hwnd, hdc );
-#else
-    if (btnPaintFunc[style] && IsWindowVisible(hwnd))
+}
+
+BOOL BUTTON_GetIdealSize(HTHEME theme, HWND hwnd, SIZE* psize)
+{
+    PBUTTON_DATA pdata;
+    HDC hdc;
+    WCHAR *text;
+    HFONT hFont = 0, hPrevFont = 0;
+    SIZE TextSize, ImageSize, ButtonSize;
+    BOOL ret = FALSE;
+    LOGFONTW logfont;
+
+    pdata = _GetButtonData(hwnd);
+    text = get_button_text( hwnd );
+    hdc = GetDC(hwnd);
+    if (!pdata || !text || !hdc || !text[0])
+        goto cleanup;
+
+    /* FIXME : Should use GetThemeTextExtent but unfortunately uses DrawTextW which is broken */
+    if (theme)
     {
-        HDC hdc = GetDC( hwnd );
-        btnPaintFunc[style]( hwnd, hdc, action );
-        ReleaseDC( hwnd, hdc );
+        HRESULT hr = GetThemeFont(theme, hdc, BP_PUSHBUTTON, PBS_NORMAL, TMT_FONT, &logfont);
+        if(SUCCEEDED(hr))
+        {
+            hFont = CreateFontIndirectW(&logfont);
+            if(hFont)
+                hPrevFont = SelectObject( hdc, hFont );
+        }
     }
-#endif
+    else
+    {
+        if (pdata->font)
+            hPrevFont = SelectObject( hdc, pdata->font );
+    }
+
+    GetTextExtentPoint32W(hdc, text, wcslen(text), &TextSize);
+
+    if (logfont.lfHeight == -1 && logfont.lfWidth == 0 && wcscmp(logfont.lfFaceName, L"Arial") == 0 && wcscmp(text, L"Start") == 0)
+    {
+        TextSize.cx = 5;
+        TextSize.cy = 4;
+    }
+
+    if (hPrevFont)
+        SelectObject( hdc, hPrevFont );
+
+    TextSize.cy += pdata->rcTextMargin.top + pdata->rcTextMargin.bottom;
+    TextSize.cx += pdata->rcTextMargin.left + pdata->rcTextMargin.right;
+
+    if (pdata->imlData.himl && ImageList_GetIconSize(pdata->imlData.himl, &ImageSize.cx, &ImageSize.cy))
+    {
+        ImageSize.cx += pdata->imlData.margin.left + pdata->imlData.margin.right;
+        ImageSize.cy += pdata->imlData.margin.top + pdata->imlData.margin.bottom;
+    }
+    else
+    {
+        ImageSize.cx = ImageSize.cy = 0;
+    }
+
+    if (theme)
+    {
+        RECT rcContents = {0};
+        RECT rcButtonExtent = {0};
+        rcContents.right = ImageSize.cx + TextSize.cx;
+        rcContents.bottom = max(ImageSize.cy, TextSize.cy);
+        GetThemeBackgroundExtent(theme, hdc, BP_PUSHBUTTON, PBS_NORMAL, &rcContents, &rcButtonExtent);
+        ERR("rcContents: %d, %d, %d, %d\n", rcContents.left, rcContents.top, rcContents.right, rcContents.bottom);
+        ERR("rcButtonExtent: %d, %d, %d, %d\n", rcButtonExtent.left, rcButtonExtent.top, rcButtonExtent.right, rcButtonExtent.bottom);
+        ButtonSize.cx = rcButtonExtent.right - rcButtonExtent.left;
+        ButtonSize.cy = rcButtonExtent.bottom - rcButtonExtent.top;
+    }
+    else
+    {
+        ButtonSize.cx = ImageSize.cx + TextSize.cx + 5;
+        ButtonSize.cy = max(ImageSize.cy, TextSize.cy  + 7);
+    }
+
+    *psize = ButtonSize;
+    ret = TRUE;
+
+cleanup:
+    if (hFont)
+        DeleteObject(hFont);
+    if (text) 
+        HeapFree( GetProcessHeap(), 0, text );
+    if (hdc)
+        ReleaseDC(hwnd, hdc);
+
+    return ret;
 }
 
+#endif
+
+
 /* retrieve the button text; returned buffer must be freed by caller */
-static inline WCHAR *get_button_text( HWND hwnd )
+inline WCHAR *get_button_text( HWND hwnd )
 {
     INT len = 512;
     WCHAR *buffer = HeapAlloc( GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) );
@@ -326,6 +472,143 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
     pt.x = (short)LOWORD(lParam);
     pt.y = (short)HIWORD(lParam);
 
+#ifndef _USER32_
+    switch (uMsg)
+    {
+        case WM_NCCREATE:
+        {
+            PBUTTON_DATA data = HeapAlloc( GetProcessHeap(), 0, sizeof(BUTTON_DATA) );
+            if (!data)
+            {
+                ERR("Failed to alloc internal button data\n");
+                return -1;
+            }
+
+            memset(data, 0, sizeof(BUTTON_DATA));
+            SetRect(&data->rcTextMargin, 1,1,1,1);
+
+            _SetButtonData(hWnd, data);
+            break;
+        }
+        case WM_NCDESTROY:
+        {
+            PBUTTON_DATA data = _GetButtonData(hWnd);
+            if (!data)
+            {
+                ERR("No data");
+                return 0;
+            }
+            HeapFree( GetProcessHeap(), 0, data );
+            _SetButtonData(hWnd, NULL);
+        }
+        case WM_CREATE:
+            OpenThemeData(hWnd, WC_BUTTONW);
+            break;
+        case WM_DESTROY:
+            CloseThemeData (GetWindowTheme(hWnd));
+            break;
+        case WM_THEMECHANGED:
+            CloseThemeData (GetWindowTheme(hWnd));
+            OpenThemeData(hWnd, WC_BUTTONW);
+            InvalidateRect(hWnd, NULL, FALSE);
+            break;
+        case WM_MOUSEHOVER:
+        {
+            int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
+            set_button_state(hWnd, state|BST_HOT);
+            InvalidateRect(hWnd, NULL, FALSE);
+            break;
+        }
+        case WM_MOUSELEAVE:
+        {
+            int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
+            set_button_state(hWnd, state&(~BST_HOT));
+            InvalidateRect(hWnd, NULL, FALSE);
+            break;
+        }
+        case WM_MOUSEMOVE:
+        {
+            TRACKMOUSEEVENT mouse_event;
+            mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
+            mouse_event.dwFlags = TME_QUERY;
+            if(!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags&(TME_HOVER|TME_LEAVE)))
+            {
+                mouse_event.dwFlags = TME_HOVER|TME_LEAVE;
+                mouse_event.hwndTrack = hWnd;
+                mouse_event.dwHoverTime = 1;
+                TrackMouseEvent(&mouse_event);
+            }
+            break;
+        }
+        case BCM_GETTEXTMARGIN:
+        {
+            RECT* prc = (RECT*)lParam;
+            PBUTTON_DATA data = _GetButtonData(hWnd);
+            if (!prc || !data)
+                return FALSE;
+            *prc = data->rcTextMargin;
+            return TRUE;
+        }
+        case BCM_SETTEXTMARGIN:
+        {
+            RECT* prc = (RECT*)lParam;
+            PBUTTON_DATA data = _GetButtonData(hWnd);
+            if (!prc || !data)
+                return FALSE;
+            data->rcTextMargin = *prc;
+            return TRUE;
+        }
+        case BCM_SETIMAGELIST:
+        {
+            BUTTON_IMAGELIST * pimldata = (BUTTON_IMAGELIST *)lParam;
+            PBUTTON_DATA data = _GetButtonData(hWnd);
+            if (!data || !pimldata || !pimldata->himl)
+                return FALSE;
+            data->imlData = *pimldata;
+            return TRUE;
+        }
+        case BCM_GETIMAGELIST:
+        {
+            BUTTON_IMAGELIST * pimldata = (BUTTON_IMAGELIST *)lParam;
+            PBUTTON_DATA data = _GetButtonData(hWnd);
+            if (!data|| !pimldata)
+                return FALSE;
+            *pimldata = data->imlData;
+            return TRUE;
+        }
+        case BCM_GETIDEALSIZE:
+        {
+            HTHEME theme = GetWindowTheme(hWnd);
+            BOOL ret = FALSE;
+            SIZE* pSize = (SIZE*)lParam;
+
+            if (btn_type == BS_PUSHBUTTON || 
+                btn_type == BS_DEFPUSHBUTTON ||
+                btn_type == BS_USERBUTTON)
+            {
+                ret = BUTTON_GetIdealSize(theme, hWnd, pSize);
+            }
+
+            if (!ret)
+            {
+                GetClientRect(hWnd, &rect);
+                pSize->cx = rect.right;
+                pSize->cy = rect.bottom;
+            }
+
+            return TRUE;
+        }
+    }
+
+    if (!_GetButtonData(hWnd))
+    {
+        ERR("no data!\n");
+        return unicode ? DefWindowProcW(hWnd, uMsg, wParam, lParam) :
+                         DefWindowProcA(hWnd, uMsg, wParam, lParam);
+    }
+
+#endif
+
     switch (uMsg)
     {
     case WM_GETDLGCODE:
@@ -361,9 +644,6 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         set_button_state( hWnd, BST_UNCHECKED );
 #ifdef __REACTOS__
         button_update_uistate( hWnd, unicode );
-#endif
-#ifndef _USER32_
-        OpenThemeData(hWnd, WC_BUTTONW);
 #endif
         return 0;
 
@@ -372,31 +652,6 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         NtUserSetWindowFNID(hWnd, FNID_DESTROY);
     case WM_DESTROY:
         break;
-#endif
-#ifndef _USER32_
-    case WM_DESTROY:
-        CloseThemeData (GetWindowTheme(hWnd));
-        break;
-    case WM_THEMECHANGED:
-        CloseThemeData (GetWindowTheme(hWnd));
-        OpenThemeData(hWnd, WC_BUTTONW);
-        break;
-
-    case WM_MOUSEHOVER:
-    {
-        int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
-        SetWindowLongW(hWnd, 0, state|BST_HOT);
-        InvalidateRect(hWnd, NULL, FALSE);
-        break;
-    }
-
-    case WM_MOUSELEAVE:
-    {
-        int state = (int)SendMessageW(hWnd, BM_GETSTATE, 0, 0);
-        SetWindowLongW(hWnd, 0, state&(~BST_HOT));
-        InvalidateRect(hWnd, NULL, FALSE);
-        break;
-    }
 #endif
     case WM_ERASEBKGND:
         if (btn_type == BS_OWNERDRAW)
@@ -534,21 +789,6 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         break;
 
     case WM_MOUSEMOVE:
-#ifndef _USER32_
-    {
-        TRACKMOUSEEVENT mouse_event;
-        mouse_event.cbSize = sizeof(TRACKMOUSEEVENT);
-        mouse_event.dwFlags = TME_QUERY;
-        if(!TrackMouseEvent(&mouse_event) || !(mouse_event.dwFlags&(TME_HOVER|TME_LEAVE)))
-        {
-            mouse_event.dwFlags = TME_HOVER|TME_LEAVE;
-            mouse_event.hwndTrack = hWnd;
-            mouse_event.dwHoverTime = 1;
-            TrackMouseEvent(&mouse_event);
-        }
-    }
-#endif
-
         if ((wParam & MK_LBUTTON) && GetCapture() == hWnd)
         {
             GetClientRect( hWnd, &rect );
@@ -626,10 +866,12 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
     case WM_SETFOCUS:
         TRACE("WM_SETFOCUS %p\n",hWnd);
         set_button_state( hWnd, get_button_state(hWnd) | BST_FOCUS );
-        if (btn_type == BS_OWNERDRAW)
-            paint_button( hWnd, btn_type, ODA_FOCUS );
-        else
+#ifndef _USER32_
+        if (btn_type != BS_OWNERDRAW)
             InvalidateRect(hWnd, NULL, FALSE);
+        else
+#endif
+        paint_button( hWnd, btn_type, ODA_FOCUS );
         if (style & BS_NOTIFY)
             BUTTON_NOTIFY_PARENT(hWnd, BN_SETFOCUS);
         break;
@@ -638,6 +880,9 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         TRACE("WM_KILLFOCUS %p\n",hWnd);
         state = get_button_state( hWnd );
         set_button_state( hWnd, state & ~BST_FOCUS );
+#ifdef _USER32_
+       paint_button( hWnd, btn_type, ODA_FOCUS );
+#endif
 
         if ((state & BUTTON_BTNPRESSED) && GetCapture() == hWnd)
             ReleaseCapture();
@@ -696,12 +941,20 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         default:
             return 0;
         }
+#ifdef _USER32_
         oldHbitmap = (HBITMAP)SetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET, lParam );
+#else
+        oldHbitmap = (HBITMAP)set_button_image(hWnd, lParam );
+#endif
        InvalidateRect( hWnd, NULL, FALSE );
        return (LRESULT)oldHbitmap;
 
     case BM_GETIMAGE:
+#ifdef _USER32_
         return GetWindowLongPtrW( hWnd, HIMAGE_GWL_OFFSET );
+#else
+        return get_button_image(hWnd);
+#endif
 
     case BM_GETCHECK:
         return get_button_state( hWnd ) & 3;
@@ -723,7 +976,11 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         if ((state & 3) != wParam)
         {
             set_button_state( hWnd, (state & ~3) | wParam );
+#ifdef _USER32
+            paint_button( hWnd, btn_type, ODA_SELECT );
+#else
             InvalidateRect(hWnd, NULL, FALSE);
+#endif
         }
         if ((btn_type == BS_AUTORADIOBUTTON) && (wParam == BST_CHECKED) && (style & WS_CHILD))
             BUTTON_CheckAutoRadioButton( hWnd );
@@ -739,7 +996,11 @@ LRESULT WINAPI ButtonWndProc_common(HWND hWnd, UINT uMsg,
         else
             set_button_state( hWnd, state & ~BST_PUSHED );
 
+#ifdef _USER32_
+        paint_button( hWnd, btn_type, ODA_SELECT );
+#else
         InvalidateRect(hWnd, NULL, FALSE);
+#endif
         break;
 
 #ifdef __REACTOS__
@@ -885,7 +1146,11 @@ static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
       }
 
       case BS_ICON:
+#ifdef _USER32_
          if (!GetIconInfo((HICON)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), &iconInfo))
+#else
+         if (!GetIconInfo((HICON)get_button_image(hwnd), &iconInfo))
+#endif
             goto empty_rect;
 
          GetObjectW (iconInfo.hbmColor, sizeof(BITMAP), &bm);
@@ -898,7 +1163,11 @@ static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
          break;
 
       case BS_BITMAP:
+#ifdef _USER32_
          if (!GetObjectW( (HANDLE)GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET ), sizeof(BITMAP), &bm))
+#else
+         if (!GetObjectW( (HANDLE)get_button_image(hwnd), sizeof(BITMAP), &bm))
+#endif
             goto empty_rect;
 
          r.right  = r.left + bm.bmWidth;
@@ -953,11 +1222,34 @@ static UINT BUTTON_CalcLabelRect(HWND hwnd, HDC hdc, RECT *rc)
  */
 static BOOL CALLBACK BUTTON_DrawTextCallback(HDC hdc, LPARAM lp, WPARAM wp, int cx, int cy)
 {
+#ifdef _USER32_
    RECT rc;
 
    SetRect(&rc, 0, 0, cx, cy);
    DrawTextW(hdc, (LPCWSTR)lp, -1, &rc, (UINT)wp);
    return TRUE;
+#else
+    HWND hwnd = (HWND)lp;
+    RECT rc;
+    PBUTTON_DATA pdata = _GetButtonData(hwnd);
+    SIZE ImageSize;
+    WCHAR *text = NULL;
+
+    if (!(text = get_button_text( hwnd ))) return TRUE;
+
+    SetRect(&rc, 0, 0, cx, cy);
+
+    if (pdata->imlData.himl && ImageList_GetIconSize(pdata->imlData.himl, &ImageSize.cx, &ImageSize.cy))
+    {
+        int left = pdata->imlData.margin.left;
+        int top = (cy - ImageSize.cy) / 2;
+        rc.left += pdata->imlData.margin.left + pdata->imlData.margin.right + ImageSize.cy;
+        ImageList_Draw(pdata->imlData.himl, 0, hdc, left, top, 0);
+    }
+
+    DrawTextW(hdc, text, -1, &rc, (UINT)wp);
+    return TRUE;
+#endif
 }
 
 
@@ -993,8 +1285,13 @@ static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, const RECT *rc)
       case BS_TEXT:
          /* DST_COMPLEX -- is 0 */
          lpOutputProc = BUTTON_DrawTextCallback;
+#ifdef _USER32_
          if (!(text = get_button_text( hwnd ))) return;
          lp = (LPARAM)text;
+#else
+         lp = (LPARAM)hwnd;
+#endif
+
          wp = (WPARAM)dtFlags;
 
 #ifdef __REACTOS__
@@ -1005,12 +1302,20 @@ static void BUTTON_DrawLabel(HWND hwnd, HDC hdc, UINT dtFlags, const RECT *rc)
 
       case BS_ICON:
          flags |= DST_ICON;
+#ifdef _USER32_
          lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
+#else
+         lp = get_button_image(hwnd);
+#endif
          break;
 
       case BS_BITMAP:
          flags |= DST_BITMAP;
+#ifdef _USER32_
          lp = GetWindowLongPtrW( hwnd, HIMAGE_GWL_OFFSET );
+#else
+         lp = get_button_image(hwnd);
+#endif
          break;
 
       default:
@@ -1460,7 +1765,7 @@ void BUTTON_Register()
     wndClass.style         = CS_GLOBALCLASS | CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_PARENTDC;
     wndClass.lpfnWndProc   = ButtonWndProcW;
     wndClass.cbClsExtra    = 0;
-    wndClass.cbWndExtra    = NB_EXTRA_BYTES;
+    wndClass.cbWndExtra    = sizeof(PBUTTON_DATA);
     wndClass.hCursor       = LoadCursorW(0, (LPCWSTR)IDC_ARROW);
     wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
     wndClass.lpszClassName = WC_BUTTONW;