Implement DrawThemeBackgroundEx, DrawThemeText, GetThemeTextExtent.
[wine] / dlls / uxtheme / msstyles.c
1 /*
2  * Win32 5.1 msstyles theme format
3  *
4  * Copyright (C) 2003 Kevin Koltzau
5  *
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.
10  *
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.
15  *
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
19  */
20
21 #include "config.h"
22
23 #include <stdarg.h>
24
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winuser.h"
28 #define NO_SHLWAPI_REG
29 #include "shlwapi.h"
30 #include "winnls.h"
31 #include "wingdi.h"
32 #include "uxtheme.h"
33 #include "tmschema.h"
34
35 #include "uxthemedll.h"
36 #include "msstyles.h"
37
38 #include "wine/debug.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(uxtheme);
41
42 /***********************************************************************
43  * Defines and global variables
44  */
45
46 BOOL UXTHEME_GetNextInteger(LPCWSTR lpStringStart, LPCWSTR lpStringEnd, LPCWSTR *lpValEnd, int *value);
47
48 extern HINSTANCE hDllInst;
49
50 #define MSSTYLES_VERSION 0x0003
51
52 static const WCHAR szThemesIniResource[] = {
53     't','h','e','m','e','s','_','i','n','i','\0'
54 };
55
56 PTHEME_FILE tfActiveTheme = NULL;
57
58 /***********************************************************************/
59
60 /**********************************************************************
61  *      MSSTYLES_OpenThemeFile
62  *
63  * Load and validate a theme
64  *
65  * PARAMS
66  *     lpThemeFile         Path to theme file to load
67  *     pszColorName        Color name wanted, can be NULL
68  *     pszSizeName         Size name wanted, can be NULL
69  *
70  * NOTES
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.
74  */
75 HRESULT MSSTYLES_OpenThemeFile(LPCWSTR lpThemeFile, LPCWSTR pszColorName, LPCWSTR pszSizeName, PTHEME_FILE *tf)
76 {
77     HMODULE hTheme;
78     HRSRC hrsc;
79     HRESULT hr = S_OK;
80     WCHAR szPackThemVersionResource[] = {
81         'P','A','C','K','T','H','E','M','_','V','E','R','S','I','O','N', '\0'
82     };
83     WCHAR szColorNamesResource[] = {
84         'C','O','L','O','R','N','A','M','E','S','\0'
85     };
86     WCHAR szSizeNamesResource[] = {
87         'S','I','Z','E','N','A','M','E','S','\0'
88     };
89
90     WORD version;
91     DWORD versize;
92     LPWSTR pszColors;
93     LPWSTR pszSelectedColor = NULL;
94     LPWSTR pszSizes;
95     LPWSTR pszSelectedSize = NULL;
96     LPWSTR tmp;
97
98     TRACE("Opening %s\n", debugstr_w(lpThemeFile));
99
100     hTheme = LoadLibraryExW(lpThemeFile, NULL, LOAD_LIBRARY_AS_DATAFILE);
101
102     /* Validate that this is really a theme */
103     if(!hTheme) {
104         hr = HRESULT_FROM_WIN32(GetLastError());
105         goto invalid_theme;
106     }
107     if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szPackThemVersionResource))) {
108         TRACE("No version resource found\n");
109         hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
110         goto invalid_theme;
111     }
112     if((versize = SizeofResource(hTheme, hrsc)) != 2)
113     {
114         TRACE("Version resource found, but wrong size: %ld\n", versize);
115         hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
116         goto invalid_theme;
117     }
118     version = *(WORD*)LoadResource(hTheme, hrsc);
119     if(version != MSSTYLES_VERSION)
120     {
121         TRACE("Version of theme file is unsupported: 0x%04x\n", version);
122         hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
123         goto invalid_theme;
124     }
125
126     if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szColorNamesResource))) {
127         TRACE("Color names resource not found\n");
128         hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
129         goto invalid_theme;
130     }
131     pszColors = (LPWSTR)LoadResource(hTheme, hrsc);
132
133     if(!(hrsc = FindResourceW(hTheme, MAKEINTRESOURCEW(1), szSizeNamesResource))) {
134         TRACE("Size names resource not found\n");
135         hr = HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
136         goto invalid_theme;
137     }
138     pszSizes = (LPWSTR)LoadResource(hTheme, hrsc);
139
140     /* Validate requested color against whats available from the theme */
141     if(pszColorName) {
142         tmp = pszColors;
143         while(*tmp) {
144             if(!lstrcmpiW(pszColorName, tmp)) {
145                 pszSelectedColor = tmp;
146                 break;
147             }
148             tmp += lstrlenW(tmp)+1;
149         }
150     }
151     else
152         pszSelectedColor = pszColors; /* Use the default color */
153
154     /* Validate requested size against whats available from the theme */
155     if(pszSizeName) {
156         tmp = pszSizes;
157         while(*tmp) {
158             if(!lstrcmpiW(pszSizeName, tmp)) {
159                 pszSelectedSize = tmp;
160                 break;
161             }
162             tmp += lstrlenW(tmp)+1;
163         }
164     }
165     else
166         pszSelectedSize = pszSizes; /* Use the default size */
167
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;
172         goto invalid_theme;
173     }
174
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;
182     return S_OK;
183
184 invalid_theme:
185     if(hTheme) FreeLibrary(hTheme);
186     return hr;
187 }
188
189 /***********************************************************************
190  *      MSSTYLES_CloseThemeFile
191  *
192  * Close theme file and free resources
193  */
194 void MSSTYLES_CloseThemeFile(PTHEME_FILE tf)
195 {
196     if(tf) {
197         tf->dwRefCount--;
198         if(!tf->dwRefCount) {
199             if(tf->hTheme) FreeLibrary(tf->hTheme);
200             if(tf->classes) {
201                 while(tf->classes) {
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);
208                     }
209                     HeapFree(GetProcessHeap(), 0, pcls);
210                 }
211             }
212             HeapFree(GetProcessHeap(), 0, tf);
213         }
214     }
215 }
216
217 /***********************************************************************
218  *      MSSTYLES_SetActiveTheme
219  *
220  * Set the current active theme
221  */
222 HRESULT MSSTYLES_SetActiveTheme(PTHEME_FILE tf)
223 {
224     if(tfActiveTheme)
225         MSSTYLES_CloseThemeFile(tfActiveTheme);
226     tfActiveTheme = tf;
227     if (tfActiveTheme)
228         tfActiveTheme->dwRefCount++;
229     return S_OK;
230 }
231
232 /***********************************************************************
233  *      MSSTYLES_GetThemeIni
234  *
235  * Retrieves themes.ini from a theme
236  */
237 PUXINI_FILE MSSTYLES_GetThemeIni(PTHEME_FILE tf)
238 {
239     return UXINI_LoadINI(tf->hTheme, szThemesIniResource);
240 }
241
242 /***********************************************************************
243  *      MSSTYLES_GetActiveThemeIni
244  *
245  * Retrieve the ini file for the selected color/style
246  */
247 PUXINI_FILE MSSTYLES_GetActiveThemeIni(PTHEME_FILE tf)
248 {
249     WCHAR szFileResNamesResource[] = {
250         'F','I','L','E','R','E','S','N','A','M','E','S','\0'
251     };
252     DWORD dwColorCount = 0;
253     DWORD dwSizeCount = 0;
254     DWORD dwColorNum = 0;
255     DWORD dwSizeNum = 0;
256     DWORD i;
257     DWORD dwResourceIndex;
258     LPWSTR tmp;
259     HRSRC hrsc;
260
261     /* Count the number of available colors & styles, and determine the index number
262        of the color/style we are interested in
263     */
264     tmp = tf->pszAvailColors;
265     while(*tmp) {
266         if(!lstrcmpiW(tf->pszSelectedColor, tmp))
267             dwColorNum = dwColorCount;
268         tmp += lstrlenW(tmp)+1;
269         dwColorCount++;
270     }
271     tmp = tf->pszAvailSizes;
272     while(*tmp) {
273         if(!lstrcmpiW(tf->pszSelectedSize, tmp))
274             dwSizeNum = dwSizeCount;
275         tmp += lstrlenW(tmp)+1;
276         dwSizeCount++;
277     }
278
279     if(!(hrsc = FindResourceW(tf->hTheme, MAKEINTRESOURCEW(1), szFileResNamesResource))) {
280         TRACE("FILERESNAMES map not found\n");
281         return NULL;
282     }
283     tmp = (LPWSTR)LoadResource(tf->hTheme, hrsc);
284     dwResourceIndex = (dwSizeCount * dwColorNum) + dwSizeNum;
285     for(i=0; i < dwResourceIndex; i++) {
286         tmp += lstrlenW(tmp)+1;
287     }
288     return UXINI_LoadINI(tf->hTheme, tmp);
289 }
290
291
292 /***********************************************************************
293  *      MSSTYLES_ParseIniSectionName
294  *
295  * Parse an ini section name into its component parts
296  * Valid formats are:
297  * [classname]
298  * [classname(state)]
299  * [classname.part]
300  * [classname.part(state)]
301  * [application::classname]
302  * [application::classname(state)]
303  * [application::classname.part]
304  * [application::classname.part(state)]
305  *
306  * PARAMS
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
313  */
314 BOOL MSSTYLES_ParseIniSectionName(LPCWSTR lpSection, DWORD dwLen, LPWSTR szAppName, LPWSTR szClassName, int *iPartId, int *iStateId)
315 {
316     WCHAR sec[255];
317     WCHAR part[60] = {'\0'};
318     WCHAR state[60] = {'\0'};
319     LPWSTR tmp;
320     LPWSTR comp;
321     lstrcpynW(sec, lpSection, min(dwLen+1, sizeof(sec)/sizeof(sec[0])));
322
323     *szAppName = 0;
324     *szClassName = 0;
325     *iPartId = 0;
326     *iStateId = 0;
327     comp = sec;
328     /* Get the application name */
329     tmp = StrChrW(comp, ':');
330     if(tmp) {
331         *tmp++ = 0;
332         tmp++;
333         lstrcpynW(szAppName, comp, MAX_THEME_APP_NAME);
334         comp = tmp;
335     }
336
337     tmp = StrChrW(comp, '.');
338     if(tmp) {
339         *tmp++ = 0;
340         lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
341         comp = tmp;
342         /* now get the part & state */
343         tmp = StrChrW(comp, '(');
344         if(tmp) {
345             *tmp++ = 0;
346             lstrcpynW(part, comp, sizeof(part)/sizeof(part[0]));
347             comp = tmp;
348             /* now get the state */
349             *StrChrW(comp, ')') = 0;
350             lstrcpynW(state, comp, sizeof(state)/sizeof(state[0]));
351         }
352         else {
353             lstrcpynW(part, comp, sizeof(part)/sizeof(part[0]));
354         }
355     }
356     else {
357         tmp = StrChrW(comp, '(');
358         if(tmp) {
359             *tmp++ = 0;
360             lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
361             comp = tmp;
362             /* now get the state */
363             *StrChrW(comp, ')') = 0;
364             lstrcpynW(state, comp, sizeof(state)/sizeof(state[0]));
365         }
366         else {
367             lstrcpynW(szClassName, comp, MAX_THEME_CLASS_NAME);
368         }
369     }
370     if(!*szClassName) return FALSE;
371     return MSSTYLES_LookupPartState(szClassName, part[0]?part:NULL, state[0]?state:NULL, iPartId, iStateId);
372 }
373
374 /***********************************************************************
375  *      MSSTYLES_FindClass
376  *
377  * Find a class
378  *
379  * PARAMS
380  *     tf                  Theme file
381  *     pszAppName          App name to find
382  *     pszClassName        Class name to find
383  *
384  * RETURNS
385  *  The class found, or NULL
386  */
387 PTHEME_CLASS MSSTYLES_FindClass(PTHEME_FILE tf, LPCWSTR pszAppName, LPCWSTR pszClassName)
388 {
389     PTHEME_CLASS cur = tf->classes;
390     while(cur) {
391         if(!pszAppName) {
392             if(!*cur->szAppName && !lstrcmpiW(pszClassName, cur->szClassName))
393                 return cur;
394         }
395         else {
396             if(!lstrcmpiW(pszAppName, cur->szAppName) && !lstrcmpiW(pszClassName, cur->szClassName))
397                 return cur;
398         }
399         cur = cur->next;
400     }
401     return NULL;
402 }
403
404 /***********************************************************************
405  *      MSSTYLES_AddClass
406  *
407  * Add a class to a theme file
408  *
409  * PARAMS
410  *     tf                  Theme file
411  *     pszAppName          App name to add
412  *     pszClassName        Class name to add
413  *
414  * RETURNS
415  *  The class added, or a class previously added with the same name
416  */
417 PTHEME_CLASS MSSTYLES_AddClass(PTHEME_FILE tf, LPCWSTR pszAppName, LPCWSTR pszClassName)
418 {
419     PTHEME_CLASS cur = MSSTYLES_FindClass(tf, pszAppName, pszClassName);
420     if(cur) return cur;
421
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;
429     tf->classes = cur;
430     return cur;
431 }
432
433 /***********************************************************************
434  *      MSSTYLES_FindPartState
435  *
436  * Find a part/state
437  *
438  * PARAMS
439  *     tc                  Class to search
440  *     iPartId             Part ID to find
441  *     iStateId            State ID to find
442  *     tcNext              Receives the next class in the override chain
443  *
444  * RETURNS
445  *  The part/state found, or NULL
446  */
447 PTHEME_PARTSTATE MSSTYLES_FindPartState(PTHEME_CLASS tc, int iPartId, int iStateId, PTHEME_CLASS *tcNext)
448 {
449     PTHEME_PARTSTATE cur = tc->partstate;
450     while(cur) {
451         if(cur->iPartId == iPartId && cur->iStateId == iStateId) {
452             if(tcNext) *tcNext = tc->overrides;
453             return cur;
454         }
455         cur = cur->next;
456     }
457     if(tc->overrides) return MSSTYLES_FindPartState(tc->overrides, iPartId, iStateId, tcNext);
458     return NULL;
459 }
460
461 /***********************************************************************
462  *      MSSTYLES_AddPartState
463  *
464  * Add a part/state to a class
465  *
466  * PARAMS
467  *     tc                  Theme class
468  *     iPartId             Part ID to add
469  *     iStateId            State ID to add
470  *
471  * RETURNS
472  *  The part/state added, or a part/state previously added with the same IDs
473  */
474 PTHEME_PARTSTATE MSSTYLES_AddPartState(PTHEME_CLASS tc, int iPartId, int iStateId)
475 {
476     PTHEME_PARTSTATE cur = MSSTYLES_FindPartState(tc, iPartId, iStateId, NULL);
477     if(cur) return cur;
478
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;
484     tc->partstate = cur;
485     return cur;
486 }
487
488 /***********************************************************************
489  *      MSSTYLES_PSFindProperty
490  *
491  * Find a value within a part/state
492  *
493  * PARAMS
494  *     ps                  Part/state to search
495  *     iPropertyPrimitive  Type of value expected
496  *     iPropertyId         ID of the required value
497  *
498  * RETURNS
499  *  The property found, or NULL
500  */
501 PTHEME_PROPERTY MSSTYLES_PSFindProperty(PTHEME_PARTSTATE ps, int iPropertyPrimitive, int iPropertyId)
502 {
503     PTHEME_PROPERTY cur = ps->properties;
504     while(cur) {
505         if(cur->iPropertyId == iPropertyId) {
506             if(cur->iPrimitiveType == iPropertyPrimitive) {
507                 return cur;
508             }
509             else {
510                 if(!iPropertyPrimitive)
511                     return cur;
512                 return NULL;
513             }
514         }
515         cur = cur->next;
516     }
517     return NULL;
518 }
519
520 /***********************************************************************
521  *      MSSTYLES_AddProperty
522  *
523  * Add a property to a part/state
524  *
525  * PARAMS
526  *     ps                  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
531  *
532  * RETURNS
533  *  The property added, or a property previously added with the same IDs
534  */
535 PTHEME_PROPERTY MSSTYLES_AddProperty(PTHEME_PARTSTATE ps, int iPropertyPrimitive, int iPropertyId, LPCWSTR lpValue, DWORD dwValueLen, BOOL isGlobal)
536 {
537     PTHEME_PROPERTY cur = MSSTYLES_PSFindProperty(ps, iPropertyPrimitive, iPropertyId);
538     /* Should duplicate properties overwrite the original, or be ignored? */
539     if(cur) return cur;
540
541     cur = HeapAlloc(GetProcessHeap(), 0, sizeof(THEME_PROPERTY));
542     cur->iPrimitiveType = iPropertyPrimitive;
543     cur->iPropertyId = iPropertyId;
544     cur->lpValue = lpValue;
545     cur->dwValueLen = dwValueLen;
546
547     if(ps->iStateId)
548         cur->origin = PO_STATE;
549     else if(ps->iPartId)
550         cur->origin = PO_PART;
551     else if(isGlobal)
552         cur->origin = PO_GLOBAL;
553     else
554         cur->origin = PO_CLASS;
555
556     cur->next = ps->properties;
557     ps->properties = cur;
558     return cur;
559 }
560
561 /***********************************************************************
562  *      MSSTYLES_ParseThemeIni
563  *
564  * Parse the theme ini for the selected color/style
565  *
566  * PARAMS
567  *     tf                  Theme to parse
568  */
569 void MSSTYLES_ParseThemeIni(PTHEME_FILE tf)
570 {
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'};
573     PTHEME_CLASS cls;
574     PTHEME_CLASS globals;
575     PTHEME_PARTSTATE ps;
576     PUXINI_FILE ini;
577     WCHAR szAppName[MAX_THEME_APP_NAME];
578     WCHAR szClassName[MAX_THEME_CLASS_NAME];
579     WCHAR szPropertyName[MAX_THEME_VALUE_NAME];
580     int iPartId;
581     int iStateId;
582     int iPropertyPrimitive;
583     int iPropertyId;
584     DWORD dwLen;
585     LPCWSTR lpName;
586     DWORD dwValueLen;
587     LPCWSTR lpValue;
588
589     ini = MSSTYLES_GetActiveThemeIni(tf);
590
591     while((lpName=UXINI_GetNextSection(ini, &dwLen))) {
592         if(CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, lpName, dwLen, szSysMetrics, -1) == CSTR_EQUAL) {
593             int colorCount = 0;
594             int colorElements[TMT_LASTCOLOR-TMT_FIRSTCOLOR];
595             COLORREF colorRgb[TMT_LASTCOLOR-TMT_FIRSTCOLOR];
596             LPCWSTR lpValueEnd;
597
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) {
602                         int r,g,b;
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);
609                         }
610                         else {
611                             FIXME("Invalid color value for %s\n", debugstr_w(szPropertyName));
612                         }
613                     }
614                     else {
615                         /* FIXME: Handle non-color metrics */
616                     }
617                 }
618                 else {
619                     TRACE("Unknown system metric %s\n", debugstr_w(szPropertyName));
620                 }
621             }
622             if(colorCount > 0)
623                 SetSysColors(colorCount, colorElements, colorRgb);
624             continue;
625         }
626         if(MSSTYLES_ParseIniSectionName(lpName, dwLen, szAppName, szClassName, &iPartId, &iStateId)) {
627             BOOL isGlobal = FALSE;
628             if(!lstrcmpiW(szClassName, szGlobals)) {
629                 isGlobal = TRUE;
630             }
631             cls = MSSTYLES_AddClass(tf, szAppName, szClassName);
632             ps = MSSTYLES_AddPartState(cls, iPartId, iStateId);
633
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);
638                 }
639                 else {
640                     TRACE("Unknown property %s\n", debugstr_w(szPropertyName));
641                 }
642             }
643         }
644     }
645
646     /* App/Class combos override values defined by the base class, map these overrides */
647     globals = MSSTYLES_FindClass(tf, NULL, szGlobals);
648     cls = tf->classes;
649     while(cls) {
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));
654             }
655             else {
656                 cls->overrides = globals;
657             }
658         }
659         else {
660             /* Everything overrides globals..except globals */
661             if(cls != globals) cls->overrides = globals;
662         }
663         cls = cls->next;
664     }
665     UXINI_CloseINI(ini);
666
667     if(!tf->classes) {
668         ERR("Failed to parse theme ini\n");
669     }
670 }
671
672 /***********************************************************************
673  *      MSSTYLES_OpenThemeClass
674  *
675  * Open a theme class, uses the current active theme
676  *
677  * PARAMS
678  *     pszAppName          Application name, for theme styles specific
679  *                         to a particular application
680  *     pszClassList        List of requested classes, semicolon delimited
681  */
682 PTHEME_CLASS MSSTYLES_OpenThemeClass(LPCWSTR pszAppName, LPCWSTR pszClassList)
683 {
684     PTHEME_CLASS cls = NULL;
685     WCHAR szClassName[MAX_THEME_CLASS_NAME];
686     LPCWSTR start;
687     LPCWSTR end;
688     DWORD len;
689
690     if(!tfActiveTheme) {
691         TRACE("there is no active theme\n");
692         return NULL;
693     }
694     if(!tfActiveTheme->classes) {
695         MSSTYLES_ParseThemeIni(tfActiveTheme);
696         if(!tfActiveTheme->classes)
697             return NULL;
698     }
699
700     start = pszClassList;
701     while((end = StrChrW(start, ';'))) {
702         len = end-start;
703         lstrcpynW(szClassName, start, min(len+1, sizeof(szClassName)/sizeof(szClassName[0])));
704         start = end+1;
705         cls = MSSTYLES_FindClass(tfActiveTheme, pszAppName, szClassName);
706         if(cls) break;
707     }
708     if(!cls && *start) {
709         lstrcpynW(szClassName, start, sizeof(szClassName)/sizeof(szClassName[0]));
710         cls = MSSTYLES_FindClass(tfActiveTheme, pszAppName, szClassName);
711     }
712     if(cls) {
713         TRACE("Opened app %s, class %s from list %s\n", debugstr_w(cls->szAppName), debugstr_w(cls->szClassName), debugstr_w(pszClassList));
714     }
715     return cls;
716 }
717
718 /***********************************************************************
719  *      MSSTYLES_CloseThemeClass
720  *
721  * Close a theme class
722  *
723  * PARAMS
724  *     tc                  Theme class to close
725  *
726  * NOTES
727  *  There is currently no need clean anything up for theme classes,
728  *  so do nothing for now
729  */
730 HRESULT MSSTYLES_CloseThemeClass(PTHEME_CLASS tc)
731 {
732     return S_OK;
733 }
734
735 /***********************************************************************
736  *      MSSTYLES_FindProperty
737  *
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.
741  */
742 PTHEME_PROPERTY MSSTYLES_FindProperty(PTHEME_CLASS tc, int iPartId, int iStateId, int iPropertyPrimitive, int iPropertyId)
743 {
744     PTHEME_CLASS next = tc;
745     PTHEME_PARTSTATE ps;
746     PTHEME_PROPERTY tp;
747
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))) {
752             return tp;
753         }
754     }
755     /* If that fails, and we didn't already try it, search for just part */
756     if(iStateId != 0)
757         iStateId = 0;
758     /* As a last ditch attempt..go for just class */
759     else if(iPartId != 0)
760         iPartId = 0;
761     else
762         return NULL;
763
764     if((tp = MSSTYLES_FindProperty(tc, iPartId, iStateId, iPropertyPrimitive, iPropertyId)))
765         return tp;
766     return NULL;
767 }
768
769 HBITMAP MSSTYLES_LoadBitmap(HDC hdc, PTHEME_CLASS tc, LPCWSTR lpFilename)
770 {
771     WCHAR szFile[MAX_PATH];
772     LPWSTR tmp;
773     lstrcpynW(szFile, lpFilename, sizeof(szFile)/sizeof(szFile[0]));
774     tmp = szFile;
775     do {
776         if(*tmp == '\\') *tmp = '_';
777         if(*tmp == '/') *tmp = '_';
778         if(*tmp == '.') *tmp = '_';
779     } while(*tmp++);
780     return LoadImageW(tc->hTheme, szFile, IMAGE_BITMAP, 0, 0, LR_SHARED|LR_CREATEDIBSECTION);
781 }