2 * Win32 5.1 msstyles theme format
4 * Copyright (C) 2003 Kevin Koltzau
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #define NO_SHLWAPI_REG
35 #include "uxthemedll.h"
38 #include "wine/debug.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(uxtheme);
42 /***********************************************************************
43 * Defines and global variables
46 BOOL UXTHEME_GetNextInteger(LPCWSTR lpStringStart, LPCWSTR lpStringEnd, LPCWSTR *lpValEnd, int *value);
48 extern HINSTANCE hDllInst;
50 #define MSSTYLES_VERSION 0x0003
52 static const WCHAR szThemesIniResource[] = {
53 't','h','e','m','e','s','_','i','n','i','\0'
56 PTHEME_FILE tfActiveTheme = NULL;
58 /***********************************************************************/
60 /**********************************************************************
61 * MSSTYLES_OpenThemeFile
63 * Load and validate a theme
66 * lpThemeFile Path to theme file to load
67 * pszColorName Color name wanted, can be NULL
68 * pszSizeName Size name wanted, can be NULL
71 * If pszColorName or pszSizeName are NULL, the default color/size will be used.
72 * If one/both are provided, they are validated against valid color/sizes and if
73 * a match is not found, the function fails.
75 HRESULT MSSTYLES_OpenThemeFile(LPCWSTR lpThemeFile, LPCWSTR pszColorName, LPCWSTR pszSizeName, PTHEME_FILE *tf)
80 WCHAR szPackThemVersionResource[] = {
81 'P','A','C','K','T','H','E','M','_','V','E','R','S','I','O','N', '\0'
83 WCHAR szColorNamesResource[] = {
84 'C','O','L','O','R','N','A','M','E','S','\0'
86 WCHAR szSizeNamesResource[] = {
87 'S','I','Z','E','N','A','M','E','S','\0'
93 LPWSTR pszSelectedColor = NULL;
95 LPWSTR pszSelectedSize = NULL;
98 TRACE("Opening %s\n", debugstr_w(lpThemeFile));
100 hTheme = LoadLibraryExW(lpThemeFile, NULL, LOAD_LIBRARY_AS_DATAFILE);
102 /* Validate that this is really a theme */
104 hr = HRESULT_FROM_WIN32(GetLastError());
107 if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szPackThemVersionResource))) {
108 TRACE("No version resource found\n");
109 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
112 if((versize = SizeofResource(hTheme, hrsc)) != 2)
114 TRACE("Version resource found, but wrong size: %ld\n", versize);
115 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
118 version = *(WORD*)LoadResource(hTheme, hrsc);
119 if(version != MSSTYLES_VERSION)
121 TRACE("Version of theme file is unsupported: 0x%04x\n", version);
122 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
126 if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szColorNamesResource))) {
127 TRACE("Color names resource not found\n");
128 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
131 pszColors = (LPWSTR)LoadResource(hTheme, hrsc);
133 if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szSizeNamesResource))) {
134 TRACE("Size names resource not found\n");
135 hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
138 pszSizes = (LPWSTR)LoadResource(hTheme, hrsc);
140 /* Validate requested color against whats available from the theme */
144 if(!lstrcmpiW(pszColorName, tmp)) {
145 pszSelectedColor = tmp;
148 tmp += lstrlenW(tmp)+1;
152 pszSelectedColor = pszColors; /* Use the default color */
154 /* Validate requested size against whats available from the theme */
158 if(!lstrcmpiW(pszSizeName, tmp)) {
159 pszSelectedSize = tmp;
162 tmp += lstrlenW(tmp)+1;
166 pszSelectedSize = pszSizes; /* Use the default size */
168 if(!pszSelectedColor || !pszSelectedSize) {
169 TRACE("Requested color/size (%s/%s) not found in theme\n",
170 debugstr_w(pszColorName), debugstr_w(pszSizeName));
171 hr = E_PROP_ID_UNSUPPORTED;
175 *tf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(THEME_FILE));
176 (*tf)->hTheme = hTheme;
177 (*tf)->pszAvailColors = pszColors;
178 (*tf)->pszAvailSizes = pszSizes;
179 (*tf)->pszSelectedColor = pszSelectedColor;
180 (*tf)->pszSelectedSize = pszSelectedSize;
181 (*tf)->dwRefCount = 1;
185 if(hTheme) FreeLibrary(hTheme);
189 /***********************************************************************
190 * MSSTYLES_CloseThemeFile
192 * Close theme file and free resources
194 void MSSTYLES_CloseThemeFile(PTHEME_FILE tf)
198 if(!tf->dwRefCount) {
199 if(tf->hTheme) FreeLibrary(tf->hTheme);
202 PTHEME_CLASS pcls = tf->classes;
203 tf->classes = pcls->next;
204 while(pcls->partstate) {
205 PTHEME_PARTSTATE ps = pcls->partstate;
206 pcls->partstate = ps->next;
207 HeapFree(GetProcessHeap(), 0, ps);
209 HeapFree(GetProcessHeap(), 0, pcls);
212 HeapFree(GetProcessHeap(), 0, tf);
217 /***********************************************************************
218 * MSSTYLES_SetActiveTheme
220 * Set the current active theme
222 HRESULT MSSTYLES_SetActiveTheme(PTHEME_FILE tf)
225 MSSTYLES_CloseThemeFile(tfActiveTheme);
228 tfActiveTheme->dwRefCount++;
232 /***********************************************************************
233 * MSSTYLES_GetThemeIni
235 * Retrieves themes.ini from a theme
237 PUXINI_FILE MSSTYLES_GetThemeIni(PTHEME_FILE tf)
239 return UXINI_LoadINI(tf->hTheme, szThemesIniResource);
242 /***********************************************************************
243 * MSSTYLES_GetActiveThemeIni
245 * Retrieve the ini file for the selected color/style
247 PUXINI_FILE MSSTYLES_GetActiveThemeIni(PTHEME_FILE tf)
249 WCHAR szFileResNamesResource[] = {
250 'F','I','L','E','R','E','S','N','A','M','E','S','\0'
252 DWORD dwColorCount = 0;
253 DWORD dwSizeCount = 0;
254 DWORD dwColorNum = 0;
257 DWORD dwResourceIndex;
261 /* Count the number of available colors & styles, and determine the index number
262 of the color/style we are interested in
264 tmp = tf->pszAvailColors;
266 if(!lstrcmpiW(tf->pszSelectedColor, tmp))
267 dwColorNum = dwColorCount;
268 tmp += lstrlenW(tmp)+1;
271 tmp = tf->pszAvailSizes;
273 if(!lstrcmpiW(tf->pszSelectedSize, tmp))
274 dwSizeNum = dwSizeCount;
275 tmp += lstrlenW(tmp)+1;
279 if(!(hrsc = FindResourceW(tf->hTheme, MAKEINTRESOURCEW(1), szFileResNamesResource))) {
280 TRACE("FILERESNAMES map not found\n");
283 tmp = (LPWSTR)LoadResource(tf->hTheme, hrsc);
284 dwResourceIndex = (dwSizeCount * dwColorNum) + dwSizeNum;
285 for(i=0; i < dwResourceIndex; i++) {
286 tmp += lstrlenW(tmp)+1;
288 return UXINI_LoadINI(tf->hTheme, tmp);
292 /***********************************************************************
293 * MSSTYLES_ParseIniSectionName
295 * Parse an ini section name into its component parts
300 * [classname.part(state)]
301 * [application::classname]
302 * [application::classname(state)]
303 * [application::classname.part]
304 * [application::classname.part(state)]
307 * lpSection Section name
308 * dwLen Length of section name
309 * szAppName Location to store application name
310 * szClassName Location to store class name
311 * iPartId Location to store part id
312 * iStateId Location to store state id
314 BOOL MSSTYLES_ParseIniSectionName(LPCWSTR lpSection, DWORD dwLen, LPWSTR szAppName, LPWSTR szClassName, int *iPartId, int *iStateId)
317 WCHAR part[60] = {'\0'};
318 WCHAR state[60] = {'\0'};
321 lstrcpynW(sec, lpSection, min(dwLen+1, sizeof(sec)/sizeof(sec[0])));
328 /* Get the application name */
329 tmp = StrChrW(comp, ':');
333 lstrcpynW(szAppName, comp, MAX_THEME_APP_NAME);
337 tmp = StrChrW(comp, '.');
340 lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
342 /* now get the part & state */
343 tmp = StrChrW(comp, '(');
346 lstrcpynW(part, comp, sizeof(part)/sizeof(part[0]));
348 /* now get the state */
349 *StrChrW(comp, ')') = 0;
350 lstrcpynW(state, comp, sizeof(state)/sizeof(state[0]));
353 lstrcpynW(part, comp, sizeof(part)/sizeof(part[0]));
357 tmp = StrChrW(comp, '(');
360 lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
362 /* now get the state */
363 *StrChrW(comp, ')') = 0;
364 lstrcpynW(state, comp, sizeof(state)/sizeof(state[0]));
367 lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
370 if(!*szClassName) return FALSE;
371 return MSSTYLES_LookupPartState(szClassName, part[0]?part:NULL, state[0]?state:NULL, iPartId, iStateId);
374 /***********************************************************************
381 * pszAppName App name to find
382 * pszClassName Class name to find
385 * The class found, or NULL
387 PTHEME_CLASS MSSTYLES_FindClass(PTHEME_FILE tf, LPCWSTR pszAppName, LPCWSTR pszClassName)
389 PTHEME_CLASS cur = tf->classes;
392 if(!*cur->szAppName && !lstrcmpiW(pszClassName, cur->szClassName))
396 if(!lstrcmpiW(pszAppName, cur->szAppName) && !lstrcmpiW(pszClassName, cur->szClassName))
404 /***********************************************************************
407 * Add a class to a theme file
411 * pszAppName App name to add
412 * pszClassName Class name to add
415 * The class added, or a class previously added with the same name
417 PTHEME_CLASS MSSTYLES_AddClass(PTHEME_FILE tf, LPCWSTR pszAppName, LPCWSTR pszClassName)
419 PTHEME_CLASS cur = MSSTYLES_FindClass(tf, pszAppName, pszClassName);
422 cur = HeapAlloc(GetProcessHeap(), 0, sizeof(THEME_CLASS));
423 cur->hTheme = tf->hTheme;
424 lstrcpyW(cur->szAppName, pszAppName);
425 lstrcpyW(cur->szClassName, pszClassName);
426 cur->next = tf->classes;
427 cur->partstate = NULL;
428 cur->overrides = NULL;
433 /***********************************************************************
434 * MSSTYLES_FindPartState
440 * iPartId Part ID to find
441 * iStateId State ID to find
442 * tcNext Receives the next class in the override chain
445 * The part/state found, or NULL
447 PTHEME_PARTSTATE MSSTYLES_FindPartState(PTHEME_CLASS tc, int iPartId, int iStateId, PTHEME_CLASS *tcNext)
449 PTHEME_PARTSTATE cur = tc->partstate;
451 if(cur->iPartId == iPartId && cur->iStateId == iStateId) {
452 if(tcNext) *tcNext = tc->overrides;
457 if(tc->overrides) return MSSTYLES_FindPartState(tc->overrides, iPartId, iStateId, tcNext);
461 /***********************************************************************
462 * MSSTYLES_AddPartState
464 * Add a part/state to a class
468 * iPartId Part ID to add
469 * iStateId State ID to add
472 * The part/state added, or a part/state previously added with the same IDs
474 PTHEME_PARTSTATE MSSTYLES_AddPartState(PTHEME_CLASS tc, int iPartId, int iStateId)
476 PTHEME_PARTSTATE cur = MSSTYLES_FindPartState(tc, iPartId, iStateId, NULL);
479 cur = HeapAlloc(GetProcessHeap(), 0, sizeof(THEME_PARTSTATE));
480 cur->iPartId = iPartId;
481 cur->iStateId = iStateId;
482 cur->properties = NULL;
483 cur->next = tc->partstate;
488 /***********************************************************************
489 * MSSTYLES_PSFindProperty
491 * Find a value within a part/state
494 * ps Part/state to search
495 * iPropertyPrimitive Type of value expected
496 * iPropertyId ID of the required value
499 * The property found, or NULL
501 PTHEME_PROPERTY MSSTYLES_PSFindProperty(PTHEME_PARTSTATE ps, int iPropertyPrimitive, int iPropertyId)
503 PTHEME_PROPERTY cur = ps->properties;
505 if(cur->iPropertyId == iPropertyId) {
506 if(cur->iPrimitiveType == iPropertyPrimitive) {
510 if(!iPropertyPrimitive)
520 /***********************************************************************
521 * MSSTYLES_AddProperty
523 * Add a property to a part/state
527 * iPropertyPrimitive Primitive type of the property
528 * iPropertyId ID of the property
529 * lpValue Raw value (non-NULL terminated)
530 * dwValueLen Length of the value
533 * The property added, or a property previously added with the same IDs
535 PTHEME_PROPERTY MSSTYLES_AddProperty(PTHEME_PARTSTATE ps, int iPropertyPrimitive, int iPropertyId, LPCWSTR lpValue, DWORD dwValueLen, BOOL isGlobal)
537 PTHEME_PROPERTY cur = MSSTYLES_PSFindProperty(ps, iPropertyPrimitive, iPropertyId);
538 /* Should duplicate properties overwrite the original, or be ignored? */
541 cur = HeapAlloc(GetProcessHeap(), 0, sizeof(THEME_PROPERTY));
542 cur->iPrimitiveType = iPropertyPrimitive;
543 cur->iPropertyId = iPropertyId;
544 cur->lpValue = lpValue;
545 cur->dwValueLen = dwValueLen;
548 cur->origin = PO_STATE;
550 cur->origin = PO_PART;
552 cur->origin = PO_GLOBAL;
554 cur->origin = PO_CLASS;
556 cur->next = ps->properties;
557 ps->properties = cur;
561 /***********************************************************************
562 * MSSTYLES_ParseThemeIni
564 * Parse the theme ini for the selected color/style
569 void MSSTYLES_ParseThemeIni(PTHEME_FILE tf)
571 WCHAR szSysMetrics[] = {'S','y','s','M','e','t','r','i','c','s','\0'};
572 WCHAR szGlobals[] = {'g','l','o','b','a','l','s','\0'};
574 PTHEME_CLASS globals;
577 WCHAR szAppName[MAX_THEME_APP_NAME];
578 WCHAR szClassName[MAX_THEME_CLASS_NAME];
579 WCHAR szPropertyName[MAX_THEME_VALUE_NAME];
582 int iPropertyPrimitive;
589 ini = MSSTYLES_GetActiveThemeIni(tf);
591 while((lpName=UXINI_GetNextSection(ini, &dwLen))) {
592 if(CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, lpName, dwLen, szSysMetrics, -1) == CSTR_EQUAL) {
594 int colorElements[TMT_LASTCOLOR-TMT_FIRSTCOLOR];
595 COLORREF colorRgb[TMT_LASTCOLOR-TMT_FIRSTCOLOR];
598 while((lpName=UXINI_GetNextValue(ini, &dwLen, &lpValue, &dwValueLen))) {
599 lstrcpynW(szPropertyName, lpName, min(dwLen+1, sizeof(szPropertyName)/sizeof(szPropertyName[0])));
600 if(MSSTYLES_LookupProperty(szPropertyName, &iPropertyPrimitive, &iPropertyId)) {
601 if(iPropertyId >= TMT_FIRSTCOLOR && iPropertyId <= TMT_LASTCOLOR) {
603 lpValueEnd = lpValue + dwValueLen;
604 UXTHEME_GetNextInteger(lpValue, lpValueEnd, &lpValue, &r);
605 UXTHEME_GetNextInteger(lpValue, lpValueEnd, &lpValue, &g);
606 if(UXTHEME_GetNextInteger(lpValue, lpValueEnd, &lpValue, &b)) {
607 colorElements[colorCount] = iPropertyId - TMT_FIRSTCOLOR;
608 colorRgb[colorCount++] = RGB(r,g,b);
611 FIXME("Invalid color value for %s\n", debugstr_w(szPropertyName));
615 /* FIXME: Handle non-color metrics */
619 TRACE("Unknown system metric %s\n", debugstr_w(szPropertyName));
623 SetSysColors(colorCount, colorElements, colorRgb);
626 if(MSSTYLES_ParseIniSectionName(lpName, dwLen, szAppName, szClassName, &iPartId, &iStateId)) {
627 BOOL isGlobal = FALSE;
628 if(!lstrcmpiW(szClassName, szGlobals)) {
631 cls = MSSTYLES_AddClass(tf, szAppName, szClassName);
632 ps = MSSTYLES_AddPartState(cls, iPartId, iStateId);
634 while((lpName=UXINI_GetNextValue(ini, &dwLen, &lpValue, &dwValueLen))) {
635 lstrcpynW(szPropertyName, lpName, min(dwLen+1, sizeof(szPropertyName)/sizeof(szPropertyName[0])));
636 if(MSSTYLES_LookupProperty(szPropertyName, &iPropertyPrimitive, &iPropertyId)) {
637 MSSTYLES_AddProperty(ps, iPropertyPrimitive, iPropertyId, lpValue, dwValueLen, isGlobal);
640 TRACE("Unknown property %s\n", debugstr_w(szPropertyName));
646 /* App/Class combos override values defined by the base class, map these overrides */
647 globals = MSSTYLES_FindClass(tf, NULL, szGlobals);
650 if(*cls->szAppName) {
651 cls->overrides = MSSTYLES_FindClass(tf, NULL, cls->szClassName);
652 if(!cls->overrides) {
653 TRACE("No overrides found for app %s class %s\n", debugstr_w(cls->szAppName), debugstr_w(cls->szClassName));
656 cls->overrides = globals;
660 /* Everything overrides globals..except globals */
661 if(cls != globals) cls->overrides = globals;
668 ERR("Failed to parse theme ini\n");
672 /***********************************************************************
673 * MSSTYLES_OpenThemeClass
675 * Open a theme class, uses the current active theme
678 * pszAppName Application name, for theme styles specific
679 * to a particular application
680 * pszClassList List of requested classes, semicolon delimited
682 PTHEME_CLASS MSSTYLES_OpenThemeClass(LPCWSTR pszAppName, LPCWSTR pszClassList)
684 PTHEME_CLASS cls = NULL;
685 WCHAR szClassName[MAX_THEME_CLASS_NAME];
691 TRACE("there is no active theme\n");
694 if(!tfActiveTheme->classes) {
695 MSSTYLES_ParseThemeIni(tfActiveTheme);
696 if(!tfActiveTheme->classes)
700 start = pszClassList;
701 while((end = StrChrW(start, ';'))) {
703 lstrcpynW(szClassName, start, min(len+1, sizeof(szClassName)/sizeof(szClassName[0])));
705 cls = MSSTYLES_FindClass(tfActiveTheme, pszAppName, szClassName);
709 lstrcpynW(szClassName, start, sizeof(szClassName)/sizeof(szClassName[0]));
710 cls = MSSTYLES_FindClass(tfActiveTheme, pszAppName, szClassName);
713 TRACE("Opened app %s, class %s from list %s\n", debugstr_w(cls->szAppName), debugstr_w(cls->szClassName), debugstr_w(pszClassList));
718 /***********************************************************************
719 * MSSTYLES_CloseThemeClass
721 * Close a theme class
724 * tc Theme class to close
727 * There is currently no need clean anything up for theme classes,
728 * so do nothing for now
730 HRESULT MSSTYLES_CloseThemeClass(PTHEME_CLASS tc)
735 /***********************************************************************
736 * MSSTYLES_FindProperty
738 * Locate a property in a class. Part and state IDs will be used as a
739 * preference, but may be ignored in the attempt to locate the property.
740 * Will scan the entire chain of overrides for this class.
742 PTHEME_PROPERTY MSSTYLES_FindProperty(PTHEME_CLASS tc, int iPartId, int iStateId, int iPropertyPrimitive, int iPropertyId)
744 PTHEME_CLASS next = tc;
748 TRACE("(%p, %d, %d, %d)\n", tc, iPartId, iStateId, iPropertyId);
749 /* Try and find an exact match on part & state */
750 while(next && (ps = MSSTYLES_FindPartState(next, iPartId, iStateId, &next))) {
751 if((tp = MSSTYLES_PSFindProperty(ps, iPropertyPrimitive, iPropertyId))) {
755 /* If that fails, and we didn't already try it, search for just part */
758 /* As a last ditch attempt..go for just class */
759 else if(iPartId != 0)
764 if((tp = MSSTYLES_FindProperty(tc, iPartId, iStateId, iPropertyPrimitive, iPropertyId)))
769 HBITMAP MSSTYLES_LoadBitmap(HDC hdc, PTHEME_CLASS tc, LPCWSTR lpFilename)
771 WCHAR szFile[MAX_PATH];
773 lstrcpynW(szFile, lpFilename, sizeof(szFile)/sizeof(szFile[0]));
776 if(*tmp == '\\') *tmp = '_';
777 if(*tmp == '/') *tmp = '_';
778 if(*tmp == '.') *tmp = '_';
780 return LoadImageW(tc->hTheme, szFile, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_CREATEDIBSECTION);