Add an "Install theme" button to the appearance page to allow
[wine] / programs / winecfg / theme.c
1 /*
2  * Theme configuration code
3  *
4  * Copyright (c) 2005 by Frank Richter
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
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25
26 #include <windef.h>
27 #include <winbase.h>
28 #include <wingdi.h>
29 #include <winuser.h>
30 #include <uxtheme.h>
31 #include <tmschema.h>
32 #include <shlobj.h>
33 #include <shlwapi.h>
34 #include <wine/debug.h>
35
36 #include "resource.h"
37 #include "winecfg.h"
38
39 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
40
41 /* UXTHEME functions not in the headers */
42 typedef void* HTHEMEFILE;
43 typedef BOOL (CALLBACK *EnumThemeProc)(LPVOID lpReserved, 
44                                        LPCWSTR pszThemeFileName,
45                                        LPCWSTR pszThemeName, 
46                                        LPCWSTR pszToolTip, LPVOID lpReserved2,
47                                        LPVOID lpData);
48
49 HRESULT WINAPI EnumThemeColors (LPWSTR pszThemeFileName, LPWSTR pszSizeName,
50                                 DWORD dwColorNum, LPWSTR pszColorName);
51 HRESULT WINAPI EnumThemeSizes (LPWSTR pszThemeFileName, LPWSTR pszColorName,
52                                DWORD dwSizeNum, LPWSTR pszSizeName);
53 HRESULT WINAPI ApplyTheme (HTHEMEFILE hThemeFile, char* unknown, HWND hWnd);
54 HRESULT WINAPI OpenThemeFile (LPCWSTR pszThemeFileName, LPCWSTR pszColorName,
55                               LPCWSTR pszSizeName, HTHEMEFILE* hThemeFile,
56                               DWORD unknown);
57 HRESULT WINAPI CloseThemeFile (HTHEMEFILE hThemeFile);
58 HRESULT WINAPI EnumThemes (LPCWSTR pszThemePath, EnumThemeProc callback,
59                            LPVOID lpData);
60
61 /* A struct to keep both the internal and "fancy" name of a color or size */
62 typedef struct
63 {
64   WCHAR* name;
65   WCHAR* fancyName;
66 } ThemeColorOrSize;
67
68 /* wrapper around DSA that also keeps an item count */
69 typedef struct
70 {
71   HDSA dsa;
72   int count;
73 } WrappedDsa;
74
75 /* Some helper functions to deal with ThemeColorOrSize structs in WrappedDSAs */
76
77 static void color_or_size_dsa_add (WrappedDsa* wdsa, const WCHAR* name,
78                                    const WCHAR* fancyName)
79 {
80     ThemeColorOrSize item;
81     
82     item.name = HeapAlloc (GetProcessHeap(), 0, 
83         (lstrlenW (name) + 1) * sizeof(WCHAR));
84     lstrcpyW (item.name, name);
85
86     item.fancyName = HeapAlloc (GetProcessHeap(), 0, 
87         (lstrlenW (fancyName) + 1) * sizeof(WCHAR));
88     lstrcpyW (item.fancyName, fancyName);
89
90     DSA_InsertItem (wdsa->dsa, wdsa->count, &item);
91     wdsa->count++;
92 }
93
94 static int CALLBACK dsa_destroy_callback (LPVOID p, LPVOID pData)
95 {
96     ThemeColorOrSize* item = (ThemeColorOrSize*)p;
97     HeapFree (GetProcessHeap(), 0, item->name);
98     HeapFree (GetProcessHeap(), 0, item->fancyName);
99     return 1;
100 }
101
102 static void free_color_or_size_dsa (WrappedDsa* wdsa)
103 {
104     DSA_DestroyCallback (wdsa->dsa, dsa_destroy_callback, NULL);
105 }
106
107 static void create_color_or_size_dsa (WrappedDsa* wdsa)
108 {
109     wdsa->dsa = DSA_Create (sizeof (ThemeColorOrSize), 1);
110     wdsa->count = 0;
111 }
112
113 static ThemeColorOrSize* color_or_size_dsa_get (WrappedDsa* wdsa, int index)
114 {
115     return (ThemeColorOrSize*)DSA_GetItemPtr (wdsa->dsa, index);
116 }
117
118 static int color_or_size_dsa_find (WrappedDsa* wdsa, const WCHAR* name)
119 {
120     int i = 0;
121     for (; i < wdsa->count; i++)
122     {
123         ThemeColorOrSize* item = color_or_size_dsa_get (wdsa, i);
124         if (lstrcmpiW (item->name, name) == 0) break;
125     }
126     return i;
127 }
128
129 /* A theme file, contains file name, display name, color and size scheme names */
130 typedef struct
131 {
132     WCHAR* themeFileName;
133     WCHAR* fancyName;
134     WrappedDsa colors;
135     WrappedDsa sizes;
136 } ThemeFile;
137
138 static HDSA themeFiles = NULL;
139 static int themeFilesCount = 0;
140
141 static int CALLBACK theme_dsa_destroy_callback (LPVOID p, LPVOID pData)
142 {
143     ThemeFile* item = (ThemeFile*)p;
144     HeapFree (GetProcessHeap(), 0, item->themeFileName);
145     HeapFree (GetProcessHeap(), 0, item->fancyName);
146     free_color_or_size_dsa (&item->colors);
147     free_color_or_size_dsa (&item->sizes);
148     return 1;
149 }
150
151 /* Free memory occupied by the theme list */
152 static void free_theme_files(void)
153 {
154     if (themeFiles == NULL) return;
155       
156     DSA_DestroyCallback (themeFiles , theme_dsa_destroy_callback, NULL);
157     themeFiles = NULL;
158     themeFilesCount = 0;
159 }
160
161 typedef HRESULT (WINAPI * EnumTheme) (LPWSTR, LPWSTR, DWORD, LPWSTR);
162
163 /* fill a string list with either colors or sizes of a theme */
164 static void fill_theme_string_array (const WCHAR* filename, 
165                                      WrappedDsa* wdsa,
166                                      EnumTheme enumTheme)
167 {
168     DWORD index = 0;
169     WCHAR name[MAX_PATH];
170
171     WINE_TRACE ("%s %p %p\n", wine_dbgstr_w (filename), wdsa, enumTheme);
172
173     while (SUCCEEDED (enumTheme ((WCHAR*)filename, NULL, index++, name)))
174     {
175         WINE_TRACE ("%s\n", wine_dbgstr_w (name));
176         color_or_size_dsa_add (wdsa, name, name);
177     }
178 }
179
180 /* Theme enumeration callback, adds theme to theme list */
181 static BOOL CALLBACK myEnumThemeProc (LPVOID lpReserved, 
182                                       LPCWSTR pszThemeFileName,
183                                       LPCWSTR pszThemeName, 
184                                       LPCWSTR pszToolTip, 
185                                       LPVOID lpReserved2, LPVOID lpData)
186 {
187     ThemeFile newEntry;
188
189     /* fill size/color lists */
190     create_color_or_size_dsa (&newEntry.colors);
191     fill_theme_string_array (pszThemeFileName, &newEntry.colors, EnumThemeColors);
192     create_color_or_size_dsa (&newEntry.sizes);
193     fill_theme_string_array (pszThemeFileName, &newEntry.sizes, EnumThemeSizes);
194
195     newEntry.themeFileName = HeapAlloc (GetProcessHeap(), 0, 
196         (lstrlenW (pszThemeFileName) + 1) * sizeof(WCHAR));
197     lstrcpyW (newEntry.themeFileName, pszThemeFileName);
198   
199     newEntry.fancyName = HeapAlloc (GetProcessHeap(), 0, 
200         (lstrlenW (pszThemeName) + 1) * sizeof(WCHAR));
201     lstrcpyW (newEntry.fancyName, pszThemeName);
202   
203     /*list_add_tail (&themeFiles, &newEntry->entry);*/
204     DSA_InsertItem (themeFiles, themeFilesCount, &newEntry);
205     themeFilesCount++;
206
207     return TRUE;
208 }
209
210 /* Scan for themes */
211 static void scan_theme_files(void)
212 {
213     static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
214     WCHAR themesPath[MAX_PATH];
215
216     free_theme_files();
217
218     if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL, 
219         SHGFP_TYPE_CURRENT, themesPath))) return;
220
221     themeFiles = DSA_Create (sizeof (ThemeFile), 1);
222     lstrcatW (themesPath, themesSubdir);
223
224     EnumThemes (themesPath, myEnumThemeProc, 0);
225 }
226
227 /* fill the color & size combo boxes for a given theme */
228 static void fill_color_size_combos (ThemeFile* theme, HWND comboColor, 
229                                     HWND comboSize)
230 {
231     int i;
232
233     SendMessageW (comboColor, CB_RESETCONTENT, 0, 0);
234     for (i = 0; i < theme->colors.count; i++)
235     {
236         ThemeColorOrSize* item = color_or_size_dsa_get (&theme->colors, i);
237         SendMessageW (comboColor, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
238     }
239
240     SendMessageW (comboSize, CB_RESETCONTENT, 0, 0);
241     for (i = 0; i < theme->sizes.count; i++)
242     {
243         ThemeColorOrSize* item = color_or_size_dsa_get (&theme->sizes, i);
244         SendMessageW (comboSize, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
245     }
246 }
247
248 /* Select the item of a combo box that matches a theme's color and size 
249  * scheme. */
250 static void select_color_and_size (ThemeFile* theme, 
251                             const WCHAR* colorName, HWND comboColor, 
252                             const WCHAR* sizeName, HWND comboSize)
253 {
254     SendMessageW (comboColor, CB_SETCURSEL, 
255         color_or_size_dsa_find (&theme->colors, colorName), 0);
256     SendMessageW (comboSize, CB_SETCURSEL, 
257         color_or_size_dsa_find (&theme->sizes, sizeName), 0);
258 }
259
260 /* Fill theme, color and sizes combo boxes with the know themes and select
261  * the entries matching the currently active theme. */
262 static BOOL fill_theme_list (HWND comboTheme, HWND comboColor, HWND comboSize)
263 {
264     WCHAR textNoTheme[256];
265     int themeIndex = 0;
266     BOOL ret = TRUE;
267     int i;
268     WCHAR currentTheme[MAX_PATH];
269     WCHAR currentColor[MAX_PATH];
270     WCHAR currentSize[MAX_PATH];
271     ThemeFile* theme = NULL;
272
273     LoadStringW (GetModuleHandle (NULL), IDS_NOTHEME, textNoTheme,
274         sizeof(textNoTheme) / sizeof(WCHAR));
275
276     SendMessageW (comboTheme, CB_RESETCONTENT, 0, 0);
277     SendMessageW (comboTheme, CB_ADDSTRING, 0, (LPARAM)textNoTheme);
278
279     for (i = 0; i < themeFilesCount; i++)
280     {
281         ThemeFile* item = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
282         SendMessageW (comboTheme, CB_ADDSTRING, 0, 
283             (LPARAM)item->fancyName);
284     }
285   
286     if (IsThemeActive () && SUCCEEDED (GetCurrentThemeName (currentTheme, 
287             sizeof(currentTheme) / sizeof(WCHAR),
288             currentColor, sizeof(currentColor) / sizeof(WCHAR),
289             currentSize, sizeof(currentSize) / sizeof(WCHAR))))
290     {
291         /* Determine the index of the currently active theme. */
292         BOOL found = FALSE;
293         for (i = 0; i < themeFilesCount; i++)
294         {
295             theme = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
296             if (lstrcmpiW (theme->themeFileName, currentTheme) == 0)
297             {
298                 found = TRUE;
299                 themeIndex = i+1;
300                 break;
301             }
302         }
303         if (!found)
304         {
305             /* Current theme not found?... add to the list, then... */
306             WINE_TRACE("Theme %s not in list of enumerated themes",
307                 wine_dbgstr_w (currentTheme));
308             myEnumThemeProc (NULL, currentTheme, currentTheme, 
309                 currentTheme, NULL, NULL);
310             themeIndex = themeFilesCount;
311             theme = (ThemeFile*)DSA_GetItemPtr (themeFiles, 
312                 themeFilesCount-1);
313         }
314         fill_color_size_combos (theme, comboColor, comboSize);
315         select_color_and_size (theme, currentColor, comboColor,
316             currentSize, comboSize);
317     }
318     else
319     {
320         /* No theme selected */
321         ret = FALSE;
322     }
323
324     SendMessageW (comboTheme, CB_SETCURSEL, themeIndex, 0);
325     return ret;
326 }
327
328 /* Update the color & size combo boxes when the selection of the theme
329  * combo changed. Selects the current color and size scheme if the theme
330  * is currently active, otherwise the first color and size. */
331 static BOOL update_color_and_size (int themeIndex, HWND comboColor, 
332                                    HWND comboSize)
333 {
334     if (themeIndex == 0)
335     {
336         return FALSE;
337     }
338     else
339     {
340         WCHAR currentTheme[MAX_PATH];
341         WCHAR currentColor[MAX_PATH];
342         WCHAR currentSize[MAX_PATH];
343         ThemeFile* theme = 
344             (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex - 1);
345     
346         fill_color_size_combos (theme, comboColor, comboSize);
347       
348         if ((SUCCEEDED (GetCurrentThemeName (currentTheme, 
349             sizeof(currentTheme) / sizeof(WCHAR),
350             currentColor, sizeof(currentColor) / sizeof(WCHAR),
351             currentSize, sizeof(currentSize) / sizeof(WCHAR))))
352             && (lstrcmpiW (currentTheme, theme->themeFileName) == 0))
353         {
354             select_color_and_size (theme, currentColor, comboColor,
355                 currentSize, comboSize);
356         }
357         else
358         {
359             SendMessageW (comboColor, CB_SETCURSEL, 0, 0);
360             SendMessageW (comboSize, CB_SETCURSEL, 0, 0);
361         }
362     }
363     return TRUE;
364 }
365
366 /* Apply a theme from a given theme, color and size combo box item index. */
367 static void do_apply_theme (int themeIndex, int colorIndex, int sizeIndex)
368 {
369     static char b[] = "\0";
370
371     if (themeIndex == 0)
372     {
373         /* no theme */
374         ApplyTheme (NULL, b, NULL);
375     }
376     else
377     {
378         ThemeFile* theme = 
379             (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex-1);
380         const WCHAR* themeFileName = theme->themeFileName;
381         const WCHAR* colorName = NULL;
382         const WCHAR* sizeName = NULL;
383         HTHEMEFILE hTheme;
384         ThemeColorOrSize* item;
385     
386         item = color_or_size_dsa_get (&theme->colors, colorIndex);
387         colorName = item->name;
388         
389         item = color_or_size_dsa_get (&theme->sizes, sizeIndex);
390         sizeName = item->name;
391         
392         if (SUCCEEDED (OpenThemeFile (themeFileName, colorName, sizeName,
393             &hTheme, 0)))
394         {
395             ApplyTheme (hTheme, b, NULL);
396             CloseThemeFile (hTheme);
397         }
398         else
399         {
400             ApplyTheme (NULL, b, NULL);
401         }
402     }
403 }
404
405 int updating_ui;
406 BOOL theme_dirty;
407
408 static void enable_size_and_color_controls (HWND dialog, BOOL enable)
409 {
410     EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), enable);
411     EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORTEXT), enable);
412     EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), enable);
413     EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZETEXT), enable);
414 }
415   
416 static void init_dialog (HWND dialog)
417 {
418     updating_ui = TRUE;
419     
420     scan_theme_files();
421     if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
422         GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
423         GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
424     {
425         SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
426         SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
427         enable_size_and_color_controls (dialog, FALSE);
428     }
429     else
430     {
431         enable_size_and_color_controls (dialog, TRUE);
432     }
433     theme_dirty = FALSE;
434     
435     updating_ui = FALSE;
436 }
437
438 static void on_theme_changed(HWND dialog) {
439     int index = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
440         CB_GETCURSEL, 0, 0);
441     if (!update_color_and_size (index, GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
442         GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
443     {
444         SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
445         SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
446         enable_size_and_color_controls (dialog, FALSE);
447     }
448     else
449     {
450         enable_size_and_color_controls (dialog, TRUE);
451     }
452     theme_dirty = TRUE;
453 }
454
455 static void apply_theme(HWND dialog)
456 {
457     int themeIndex, colorIndex, sizeIndex;
458
459     if (!theme_dirty) return;
460
461     themeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
462         CB_GETCURSEL, 0, 0);
463     colorIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
464         CB_GETCURSEL, 0, 0);
465     sizeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO),
466         CB_GETCURSEL, 0, 0);
467
468     do_apply_theme (themeIndex, colorIndex, sizeIndex);
469     theme_dirty = FALSE;
470 }
471
472 static void on_theme_install(HWND dialog)
473 {
474   static const WCHAR filterMask[] = {0,'*','.','m','s','s','t','y','l','e','s',0,0};
475   const int filterMaskLen = sizeof(filterMask)/sizeof(filterMask[0]);
476   OPENFILENAMEW ofn;
477   WCHAR filetitle[MAX_PATH];
478   WCHAR file[MAX_PATH];
479   WCHAR filter[100];
480   WCHAR title[100];
481
482   LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE, 
483       filter, sizeof (filter) / sizeof (filter[0]) - filterMaskLen);
484   memcpy (filter + lstrlenW (filter), filterMask, 
485       filterMaskLen * sizeof (WCHAR));
486   LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE_SELECT, 
487       title, sizeof (title) / sizeof (title[0]));
488
489   ofn.lStructSize = sizeof(OPENFILENAMEW);
490   ofn.hwndOwner = 0;
491   ofn.hInstance = 0;
492   ofn.lpstrFilter = filter;
493   ofn.lpstrCustomFilter = NULL;
494   ofn.nMaxCustFilter = 0;
495   ofn.nFilterIndex = 0;
496   ofn.lpstrFile = file;
497   ofn.lpstrFile[0] = '\0';
498   ofn.nMaxFile = sizeof(file)/sizeof(filetitle[0]);
499   ofn.lpstrFileTitle = filetitle;
500   ofn.lpstrFileTitle[0] = '\0';
501   ofn.nMaxFileTitle = sizeof(filetitle)/sizeof(filetitle[0]);
502   ofn.lpstrInitialDir = NULL;
503   ofn.lpstrTitle = title;
504   ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
505   ofn.nFileOffset = 0;
506   ofn.nFileExtension = 0;
507   ofn.lpstrDefExt = NULL;
508   ofn.lCustData = 0;
509   ofn.lpfnHook = NULL;
510   ofn.lpTemplateName = NULL;
511
512   if (GetOpenFileNameW(&ofn))
513   {
514       static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
515       static const WCHAR backslash[] = { '\\',0 };
516       WCHAR themeFilePath[MAX_PATH];
517       SHFILEOPSTRUCTW shfop;
518
519       if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL, 
520           SHGFP_TYPE_CURRENT, themeFilePath))) return;
521
522       PathRemoveExtensionW (filetitle);
523
524       /* Construct path into which the theme file goes */
525       lstrcatW (themeFilePath, themesSubdir);
526       lstrcatW (themeFilePath, backslash);
527       lstrcatW (themeFilePath, filetitle);
528
529       /* Create the directory */
530       SHCreateDirectoryExW (dialog, themeFilePath, NULL);
531
532       /* Append theme file name itself */
533       lstrcatW (themeFilePath, backslash);
534       lstrcatW (themeFilePath, PathFindFileNameW (file));
535       /* SHFileOperation() takes lists as input, so double-nullterminate */
536       themeFilePath[lstrlenW (themeFilePath)+1] = 0;
537       file[lstrlenW (file)+1] = 0;
538
539       /* Do the copying */
540       WINE_TRACE("copying: %s -> %s\n", wine_dbgstr_w (file), 
541           wine_dbgstr_w (themeFilePath));
542       shfop.hwnd = dialog;
543       shfop.wFunc = FO_COPY;
544       shfop.pFrom = file;
545       shfop.pTo = themeFilePath;
546       shfop.fFlags = FOF_NOCONFIRMMKDIR;
547       if (SHFileOperationW (&shfop) == 0)
548       {
549           scan_theme_files();
550           if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
551               GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
552               GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
553           {
554               SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
555               SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
556               enable_size_and_color_controls (dialog, FALSE);
557           }
558           else
559           {
560               enable_size_and_color_controls (dialog, TRUE);
561           }
562       }
563       else
564           WINE_TRACE("copy operation failed\n");
565   }
566   else WINE_TRACE("user cancelled\n");
567 }
568
569 INT_PTR CALLBACK
570 ThemeDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
571 {
572     switch (uMsg) {
573         case WM_INITDIALOG:
574             break;
575         
576         case WM_DESTROY:
577             free_theme_files();
578             break;
579
580         case WM_SHOWWINDOW:
581             set_window_title(hDlg);
582             break;
583             
584         case WM_COMMAND:
585             switch(HIWORD(wParam)) {
586                 case CBN_SELCHANGE: {
587                     if (updating_ui) break;
588                     SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
589                     switch (LOWORD(wParam))
590                     {
591                         case IDC_THEME_THEMECOMBO: on_theme_changed(hDlg); break;
592                         case IDC_THEME_COLORCOMBO: /* fall through */
593                         case IDC_THEME_SIZECOMBO: theme_dirty = TRUE; break;
594                     }
595                     break;
596                 }
597                     
598                 default:
599                     break;
600             }
601             switch (LOWORD(wParam))
602             {
603                 case IDC_THEME_INSTALL:
604                     if (HIWORD(wParam) != BN_CLICKED) break;
605                     on_theme_install (hDlg);
606                     break;
607                     
608                 default:
609                     break;
610             }
611             break;
612         
613         
614         case WM_NOTIFY:
615             switch (((LPNMHDR)lParam)->code) {
616                 case PSN_KILLACTIVE: {
617                     SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
618                     break;
619                 }
620                 case PSN_APPLY: {
621                     apply();
622                     apply_theme(hDlg);
623                     SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
624                     break;
625                 }
626                 case PSN_SETACTIVE: {
627                     init_dialog (hDlg);
628                     break;
629                 }
630             }
631             break;
632
633         default:
634             break;
635     }
636     return FALSE;
637 }