/* * tkTreeTheme.c -- * * This module implements platform-specific visual themes. * * Copyright (c) 2005 Tim Baker * * RCS: @(#) $Id: tkTreeTheme.c,v 1.8 2005/05/22 22:19:23 treectrl Exp $ */ #ifdef WIN32 #define WINVER 0x0501 /* Cygwin */ #endif #include "tkTreeCtrl.h" /* These must agree with tkTreeColumn.c */ #define COLUMN_STATE_NORMAL 0 #define COLUMN_STATE_ACTIVE 1 #define COLUMN_STATE_PRESSED 2 #ifdef WIN32 #include "tkWinInt.h" #include #include #include /* Cygwin */ #ifndef TMT_CONTENTMARGINS #define TMT_CONTENTMARGINS 3602 #endif typedef HTHEME (STDAPICALLTYPE OpenThemeDataProc)(HWND hwnd, LPCWSTR pszClassList); typedef HRESULT (STDAPICALLTYPE CloseThemeDataProc)(HTHEME hTheme); typedef HRESULT (STDAPICALLTYPE DrawThemeBackgroundProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, OPTIONAL const RECT *pClipRect); typedef HRESULT (STDAPICALLTYPE DrawThemeBackgroundExProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, DTBGOPTS *PDTBGOPTS); typedef HRESULT (STDAPICALLTYPE DrawThemeParentBackgroundProc)(HWND hwnd, HDC hdc, OPTIONAL const RECT *prc); typedef HRESULT (STDAPICALLTYPE DrawThemeEdgeProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pDestRect, UINT uEdge, UINT uFlags, RECT *pContentRect); typedef HRESULT (STDAPICALLTYPE DrawThemeTextProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect); typedef HRESULT (STDAPICALLTYPE GetThemeBackgroundContentRectProc)( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect); typedef HRESULT (STDAPICALLTYPE GetThemeBackgroundExtentProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pContentRect, RECT *pExtentRect); typedef HRESULT (STDAPICALLTYPE GetThemeMarginsProc)(HTHEME, HDC, int iPartId, int iStateId, int iPropId, OPTIONAL RECT *prc, MARGINS *pMargins); typedef HRESULT (STDAPICALLTYPE GetThemePartSizeProc)(HTHEME, HDC, int iPartId, int iStateId, RECT *prc, enum THEMESIZE eSize, SIZE *psz); typedef HRESULT (STDAPICALLTYPE GetThemeTextExtentProc)(HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, const RECT *pBoundingRect, RECT *pExtentRect); typedef BOOL (STDAPICALLTYPE IsThemeActiveProc)(VOID); typedef BOOL (STDAPICALLTYPE IsThemePartDefinedProc)(HTHEME, int, int); typedef HRESULT (STDAPICALLTYPE IsThemeBackgroundPartiallyTransparentProc)( HTHEME, int, int); typedef struct { OpenThemeDataProc *OpenThemeData; CloseThemeDataProc *CloseThemeData; DrawThemeBackgroundProc *DrawThemeBackground; DrawThemeBackgroundExProc *DrawThemeBackgroundEx; DrawThemeParentBackgroundProc *DrawThemeParentBackground; DrawThemeEdgeProc *DrawThemeEdge; DrawThemeTextProc *DrawThemeText; GetThemeBackgroundContentRectProc *GetThemeBackgroundContentRect; GetThemeBackgroundExtentProc *GetThemeBackgroundExtent; GetThemeMarginsProc *GetThemeMargins; GetThemePartSizeProc *GetThemePartSize; GetThemeTextExtentProc *GetThemeTextExtent; IsThemeActiveProc *IsThemeActive; IsThemePartDefinedProc *IsThemePartDefined; IsThemeBackgroundPartiallyTransparentProc *IsThemeBackgroundPartiallyTransparent; } XPThemeProcs; typedef struct { HINSTANCE hlibrary; XPThemeProcs *procs; int registered; int themeEnabled; } XPThemeData; static XPThemeProcs *procs = NULL; static XPThemeData *themeData = NULL; TCL_DECLARE_MUTEX(themeMutex) /* *---------------------------------------------------------------------- * * LoadXPThemeProcs -- * Initialize XP theming support. * * XP theme support is included in UXTHEME.DLL * We dynamically load this DLL at runtime instead of linking * to it at build-time. * * Returns: * A pointer to an XPThemeProcs table if successful, NULL otherwise. */ static XPThemeProcs * LoadXPThemeProcs(HINSTANCE *phlib) { OSVERSIONINFO os; /* * We have to check whether we are running at least on Windows XP. * In order to determine this we call GetVersionEx directly, although * it would be a good idea to wrap it inside a function similar to * TkWinGetPlatformId... */ ZeroMemory(&os, sizeof(os)); os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&os); if (os.dwMajorVersion >= 5 && os.dwMinorVersion >= 1) { /* * We are running under Windows XP or a newer version. * Load the library "uxtheme.dll", where the native widget * drawing routines are implemented. */ HINSTANCE handle; *phlib = handle = LoadLibrary("uxtheme.dll"); if (handle != 0) { /* * We have successfully loaded the library. Proceed in storing the * addresses of the functions we want to use. */ XPThemeProcs *procs = (XPThemeProcs*)ckalloc(sizeof(XPThemeProcs)); #define LOADPROC(name) \ (0 != (procs->name = (name ## Proc *)GetProcAddress(handle, #name) )) if ( LOADPROC(OpenThemeData) && LOADPROC(CloseThemeData) && LOADPROC(DrawThemeBackground) && LOADPROC(DrawThemeBackgroundEx) && LOADPROC(DrawThemeParentBackground) && LOADPROC(DrawThemeEdge) && LOADPROC(DrawThemeText) && LOADPROC(GetThemeBackgroundContentRect) && LOADPROC(GetThemeBackgroundExtent) && LOADPROC(GetThemeMargins) && LOADPROC(GetThemePartSize) && LOADPROC(GetThemeTextExtent) && LOADPROC(IsThemeActive) && LOADPROC(IsThemePartDefined) && LOADPROC(IsThemeBackgroundPartiallyTransparent) ) { return procs; } #undef LOADPROC ckfree((char*)procs); } } return 0; } int TreeTheme_DrawHeaderItem(TreeCtrl *tree, Drawable drawable, int state, int arrow, int x, int y, int width, int height) { Window win = Tk_WindowId(tree->tkwin); HWND hwnd = Tk_GetHWND(win); HTHEME hTheme; HDC hDC; TkWinDCState dcState; RECT rc; HRESULT hr; int iPartId = HP_HEADERITEM; int iStateId = HIS_NORMAL; switch (state) { case COLUMN_STATE_ACTIVE: iStateId = HIS_HOT; break; case COLUMN_STATE_PRESSED: iStateId = HIS_PRESSED; break; } if (!themeData->themeEnabled || !procs) return TCL_ERROR; hTheme = procs->OpenThemeData(hwnd, L"HEADER"); if (!hTheme) return TCL_ERROR; #if 0 /* Always returns FALSE */ if (!procs->IsThemePartDefined( hTheme, iPartId, iStateId)) { procs->CloseThemeData(hTheme); return TCL_ERROR; } #endif rc.left = x; rc.top = y; rc.right = x + width; rc.bottom = y + height; /* Is transparent for the default XP style. */ if (procs->IsThemeBackgroundPartiallyTransparent( hTheme, iPartId, iStateId)) { #if 1 /* What color should I use? */ Tk_Fill3DRectangle(tree->tkwin, drawable, tree->border, x, y, width, height, 0, TK_RELIEF_FLAT); #else /* This draws nothing, maybe because the parent window is not * themed */ procs->DrawThemeParentBackground( hwnd, hDC, &rc); #endif } hDC = TkWinGetDrawableDC(tree->display, drawable, &dcState); #if 0 { /* Default XP theme gives rect 3 pixels narrower than rc */ RECT contentRect, extentRect; hr = procs->GetThemeBackgroundContentRect( hTheme, hDC, iPartId, iStateId, &rc, &contentRect ); dbwin("GetThemeBackgroundContentRect width=%d height=%d\n", contentRect.right - contentRect.left, contentRect.bottom - contentRect.top); /* Gives rc */ hr = procs->GetThemeBackgroundExtent( hTheme, hDC, iPartId, iStateId, &contentRect, &extentRect ); dbwin("GetThemeBackgroundExtent width=%d height=%d\n", extentRect.right - extentRect.left, extentRect.bottom - extentRect.top); } #endif hr = procs->DrawThemeBackground( hTheme, hDC, iPartId, iStateId, &rc, NULL); procs->CloseThemeData(hTheme); TkWinReleaseDrawableDC(drawable, hDC, &dcState); if (hr != S_OK) return TCL_ERROR; return TCL_OK; } int TreeTheme_GetHeaderContentMargins(TreeCtrl *tree, int state, int arrow, int bounds[4]) { Window win = Tk_WindowId(tree->tkwin); HWND hwnd = Tk_GetHWND(win); HTHEME hTheme; HDC hDC; TkWinDCState dcState; HRESULT hr; MARGINS margins; int iPartId = HP_HEADERITEM; int iStateId = HIS_NORMAL; switch (state) { case COLUMN_STATE_ACTIVE: iStateId = HIS_HOT; break; case COLUMN_STATE_PRESSED: iStateId = HIS_PRESSED; break; } if (!themeData->themeEnabled || !procs) return TCL_ERROR; hTheme = procs->OpenThemeData(hwnd, L"HEADER"); if (!hTheme) return TCL_ERROR; hDC = TkWinGetDrawableDC(tree->display, win, &dcState); /* The default XP themes give 3,0,0,0 which makes little sense since * it is the *right* side that should not be drawn over by text; the * 2-pixel wide header divider is on the right */ hr = procs->GetThemeMargins( hTheme, hDC, iPartId, iStateId, TMT_CONTENTMARGINS, NULL, &margins); procs->CloseThemeData(hTheme); TkWinReleaseDrawableDC(win, hDC, &dcState); if (hr != S_OK) return TCL_ERROR; bounds[0] = margins.cxLeftWidth; bounds[1] = margins.cyTopHeight; bounds[2] = margins.cxRightWidth; bounds[3] = margins.cyBottomHeight; /* dbwin("margins %d %d %d %d\n", bounds[0], bounds[1], bounds[2], bounds[3]); */ return TCL_OK; } int TreeTheme_DrawHeaderArrow(TreeCtrl *tree, Drawable drawable, int up, int x, int y, int width, int height) { /* Doesn't seem that Microsoft actually implemented this */ return TCL_ERROR; #if 0 Window win = Tk_WindowId(tree->tkwin); HWND hwnd = Tk_GetHWND(win); HTHEME hTheme; HDC hDC; TkWinDCState dcState; RECT rc; HRESULT hr; int iPartId = HP_HEADERSORTARROW; int iStateId = up ? HSAS_SORTEDUP : HSAS_SORTEDDOWN; if (!themeData->themeEnabled || !procs) return TCL_ERROR; hTheme = procs->OpenThemeData(hwnd, L"HEADER"); if (!hTheme) return TCL_ERROR; if (!procs->IsThemePartDefined( hTheme, iPartId, iStateId)) { procs->CloseThemeData(hTheme); return TCL_ERROR; } hDC = TkWinGetDrawableDC(tree->display, drawable, &dcState); rc.left = x; rc.top = y; rc.right = x + width; rc.bottom = y + height; hr = procs->DrawThemeBackground( hTheme, hDC, iPartId, iStateId, &rc, NULL); procs->CloseThemeData(hTheme); TkWinReleaseDrawableDC(drawable, hDC, &dcState); return TCL_OK; #endif /* 0 */ } int TreeTheme_DrawButton(TreeCtrl *tree, Drawable drawable, int open, int x, int y, int width, int height) { Window win = Tk_WindowId(tree->tkwin); HWND hwnd = Tk_GetHWND(win); HTHEME hTheme; HDC hDC; TkWinDCState dcState; RECT rc; HRESULT hr; int iPartId, iStateId; if (!themeData->themeEnabled || !procs) return TCL_ERROR; iPartId = TVP_GLYPH; iStateId = open ? GLPS_OPENED : GLPS_CLOSED; hTheme = procs->OpenThemeData(hwnd, L"TREEVIEW"); if (!hTheme) return TCL_ERROR; #if 0 /* Always returns FALSE */ if (!procs->IsThemePartDefined( hTheme, iPartId, iStateId)) { procs->CloseThemeData(hTheme); return TCL_ERROR; } #endif hDC = TkWinGetDrawableDC(tree->display, drawable, &dcState); rc.left = x; rc.top = y; rc.right = x + width; rc.bottom = y + height; hr = procs->DrawThemeBackground( hTheme, hDC, iPartId, iStateId, &rc, NULL); procs->CloseThemeData(hTheme); TkWinReleaseDrawableDC(drawable, hDC, &dcState); if (hr != S_OK) return TCL_ERROR; return TCL_OK; } int TreeTheme_GetButtonSize(TreeCtrl *tree, Drawable drawable, int open, int *widthPtr, int *heightPtr) { Window win = Tk_WindowId(tree->tkwin); HWND hwnd = Tk_GetHWND(win); HTHEME hTheme; HDC hDC; TkWinDCState dcState; HRESULT hr; SIZE size; int iPartId, iStateId; if (!themeData->themeEnabled || !procs) return TCL_ERROR; iPartId = TVP_GLYPH; iStateId = open ? GLPS_OPENED : GLPS_CLOSED; hTheme = procs->OpenThemeData(hwnd, L"TREEVIEW"); if (!hTheme) return TCL_ERROR; #if 0 /* Always returns FALSE */ if (!procs->IsThemePartDefined( hTheme, iPartId, iStateId)) { procs->CloseThemeData(hTheme); return TCL_ERROR; } #endif hDC = TkWinGetDrawableDC(tree->display, drawable, &dcState); /* Returns 9x9 for default XP style */ hr = procs->GetThemePartSize( hTheme, hDC, iPartId, iStateId, NULL, TS_DRAW, &size ); procs->CloseThemeData(hTheme); TkWinReleaseDrawableDC(drawable, hDC, &dcState); if (hr != S_OK) return TCL_ERROR; /* Gave me 0,0 for a non-default theme, even though glyph existed */ if ((size.cx <= 1) && (size.cy <= 1)) return TCL_ERROR; *widthPtr = size.cx; *heightPtr = size.cy; return TCL_OK; } #if !defined(WM_THEMECHANGED) #define WM_THEMECHANGED 0x031A #endif static LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { Tcl_Interp *interp = (Tcl_Interp *)GetWindowLong(hwnd, GWL_USERDATA); switch (msg) { case WM_THEMECHANGED: Tcl_MutexLock(&themeMutex); themeData->themeEnabled = procs->IsThemeActive(); Tcl_MutexUnlock(&themeMutex); Tree_TheWorldHasChanged(interp); break; } return DefWindowProc(hwnd, msg, wp, lp); } static CHAR windowClassName[32] = "TreeCtrlMonitorClass"; static BOOL RegisterThemeMonitorWindowClass(HINSTANCE hinst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hinst; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszMenuName = windowClassName; wc.lpszClassName = windowClassName; return RegisterClassEx(&wc); } static HWND CreateThemeMonitorWindow(HINSTANCE hinst, Tcl_Interp *interp) { CHAR title[32] = "TreeCtrlMonitorWindow"; HWND hwnd; hwnd = CreateWindow(windowClassName, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL); if (!hwnd) return NULL; SetWindowLong(hwnd, GWL_USERDATA, (LONG)interp); ShowWindow(hwnd, SW_HIDE); UpdateWindow(hwnd); return hwnd; } typedef struct PerInterpData PerInterpData; struct PerInterpData { HWND hwnd; }; static void FreeAssocData(ClientData clientData, Tcl_Interp *interp) { PerInterpData *data = (PerInterpData *) clientData; DestroyWindow(data->hwnd); ckfree((char *) data); } int TreeTheme_Init(Tcl_Interp *interp) { HWND hwnd; PerInterpData *data; Tcl_MutexLock(&themeMutex); /* This is done once per-application */ if (themeData == NULL) { themeData = (XPThemeData *) ckalloc(sizeof(XPThemeData)); themeData->procs = LoadXPThemeProcs(&themeData->hlibrary); themeData->registered = FALSE; themeData->themeEnabled = FALSE; procs = themeData->procs; if (themeData->procs) { /* Check this again if WM_THEMECHANGED arrives */ themeData->themeEnabled = procs->IsThemeActive(); themeData->registered = RegisterThemeMonitorWindowClass(Tk_GetHINSTANCE()); } } Tcl_MutexUnlock(&themeMutex); if (!procs || !themeData->registered) return TCL_ERROR; /* Per-interp */ hwnd = CreateThemeMonitorWindow(Tk_GetHINSTANCE(), interp); if (!hwnd) return TCL_ERROR; data = (PerInterpData *) ckalloc(sizeof(PerInterpData)); data->hwnd = hwnd; Tcl_SetAssocData(interp, "TreeCtrlTheme", FreeAssocData, (ClientData) data); return TCL_OK; } #elif defined(MAC_OSX_TK) #include #include "tkMacOSXInt.h" int TreeTheme_DrawHeaderItem(TreeCtrl *tree, Drawable drawable, int state, int arrow, int x, int y, int width, int height) { MacDrawable *macWin = (MacDrawable *) drawable; Rect bounds; ThemeButtonDrawInfo info; CGrafPtr saveWorld; GDHandle saveDevice; GWorldPtr destPort; bounds.left = macWin->xOff + x; bounds.top = macWin->yOff + y; bounds.right = bounds.left + width; bounds.bottom = bounds.top + height; switch (state) { case COLUMN_STATE_ACTIVE: info.state = kThemeStateActive /* kThemeStateRollover */; break; case COLUMN_STATE_PRESSED: info.state = kThemeStatePressed; break; default: info.state = kThemeStateActive; break; } /* Background window */ if (!tree->isActive) info.state = kThemeStateInactive; info.value = (arrow != 0) ? kThemeButtonOn : kThemeButtonOff; info.adornment = (arrow == 1) ? kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone; destPort = TkMacOSXGetDrawablePort(drawable); GetGWorld(&saveWorld, &saveDevice); SetGWorld(destPort, 0); TkMacOSXSetUpClippingRgn(drawable); (void) DrawThemeButton(&bounds, kThemeListHeaderButton, &info, NULL, /*prevInfo*/ NULL, /*eraseProc*/ NULL, /*labelProc*/ NULL); /*userData*/ SetGWorld(saveWorld,saveDevice); return TCL_OK; } /* List headers are a fixed height on Aqua */ int TreeTheme_GetHeaderFixedHeight(TreeCtrl *tree, int *heightPtr) { SInt32 metric; GetThemeMetric(kThemeMetricListHeaderHeight, &metric); *heightPtr = metric; return TCL_OK; } int TreeTheme_GetHeaderContentMargins(TreeCtrl *tree, int state, int arrow, int bounds[4]) { Rect inBounds, outBounds; ThemeButtonDrawInfo info; SInt32 metric; inBounds.left = 0; inBounds.top = 0; inBounds.right = 100; GetThemeMetric(kThemeMetricListHeaderHeight, &metric); inBounds.bottom = metric; switch (state) { case COLUMN_STATE_ACTIVE: info.state = kThemeStateActive /* kThemeStateRollover */; break; case COLUMN_STATE_PRESSED: info.state = kThemeStatePressed; break; default: info.state = kThemeStateActive; break; } /* Background window */ if (!tree->isActive) info.state = kThemeStateInactive; info.value = (arrow != 0) ? kThemeButtonOn : kThemeButtonOff; info.adornment = (arrow == 1) ? kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone; (void) GetThemeButtonContentBounds( &inBounds, kThemeListHeaderButton, &info, &outBounds); bounds[0] = outBounds.left - inBounds.left; bounds[1] = outBounds.top - inBounds.top; bounds[2] = inBounds.right - outBounds.right; bounds[3] = inBounds.bottom - outBounds.bottom; return TCL_OK; } int TreeTheme_DrawHeaderArrow(TreeCtrl *tree, Drawable drawable, int up, int x, int y, int width, int height) { return TCL_ERROR; } int TreeTheme_DrawButton(TreeCtrl *tree, Drawable drawable, int open, int x, int y, int width, int height) { MacDrawable *macWin = (MacDrawable *) drawable; Rect bounds; ThemeButtonDrawInfo info; CGrafPtr saveWorld; GDHandle saveDevice; GWorldPtr destPort; bounds.left = macWin->xOff + x; bounds.top = macWin->yOff + y; bounds.right = bounds.left + width; bounds.bottom = bounds.top + height; info.state = kThemeStateActive; info.value = open ? kThemeDisclosureDown : kThemeDisclosureRight; info.adornment = kThemeAdornmentNone; destPort = TkMacOSXGetDrawablePort(drawable); GetGWorld(&saveWorld, &saveDevice); SetGWorld(destPort, 0); TkMacOSXSetUpClippingRgn(drawable); (void) DrawThemeButton(&bounds, kThemeDisclosureButton, &info, NULL, /*prevInfo*/ NULL, /*eraseProc*/ NULL, /*labelProc*/ NULL); /*userData*/ SetGWorld(saveWorld,saveDevice); return TCL_OK; } int TreeTheme_GetButtonSize(TreeCtrl *tree, Drawable drawable, int open, int *widthPtr, int *heightPtr) { SInt32 metric; (void) GetThemeMetric( kThemeMetricDisclosureTriangleWidth, &metric); *widthPtr = metric; (void) GetThemeMetric( kThemeMetricDisclosureTriangleHeight, &metric); *heightPtr = metric; return TCL_OK; } int TreeTheme_Init(Tcl_Interp *interp) { return TCL_OK; } #else /* MAC_OSX_TK */ int TreeTheme_DrawHeaderItem(TreeCtrl *tree, Drawable drawable, int state, int arrow, int x, int y, int width, int height) { return TCL_ERROR; } int TreeTheme_GetHeaderContentMargins(TreeCtrl *tree, int state, int arrow, int bounds[4]) { return TCL_ERROR; } int TreeTheme_DrawHeaderArrow(TreeCtrl *tree, Drawable drawable, int up, int x, int y, int width, int height) { return TCL_ERROR; } int TreeTheme_DrawButton(TreeCtrl *tree, Drawable drawable, int open, int x, int y, int width, int height) { return TCL_ERROR; } int TreeTheme_GetButtonSize(TreeCtrl *tree, Drawable drawable, int open, int *widthPtr, int *heightPtr) { return TCL_ERROR; } int TreeTheme_Init(Tcl_Interp *interp) { return TCL_ERROR; } #endif /* WIN32 */