2 * Theme configuration code
4 * Copyright (c) 2005 by Frank Richter
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
34 #include <wine/debug.h>
39 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
41 /* UXTHEME functions not in the headers */
42 typedef void* HTHEMEFILE;
43 typedef BOOL (CALLBACK *EnumThemeProc)(LPVOID lpReserved,
44 LPCWSTR pszThemeFileName,
46 LPCWSTR pszToolTip, LPVOID lpReserved2,
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,
57 HRESULT WINAPI CloseThemeFile (HTHEMEFILE hThemeFile);
58 HRESULT WINAPI EnumThemes (LPCWSTR pszThemePath, EnumThemeProc callback,
61 /* A struct to keep both the internal and "fancy" name of a color or size */
68 /* wrapper around DSA that also keeps an item count */
75 /* Some helper functions to deal with ThemeColorOrSize structs in WrappedDSAs */
77 static void color_or_size_dsa_add (WrappedDsa* wdsa, const WCHAR* name,
78 const WCHAR* fancyName)
80 ThemeColorOrSize item;
82 item.name = HeapAlloc (GetProcessHeap(), 0,
83 (lstrlenW (name) + 1) * sizeof(WCHAR));
84 lstrcpyW (item.name, name);
86 item.fancyName = HeapAlloc (GetProcessHeap(), 0,
87 (lstrlenW (fancyName) + 1) * sizeof(WCHAR));
88 lstrcpyW (item.fancyName, fancyName);
90 DSA_InsertItem (wdsa->dsa, wdsa->count, &item);
94 static int CALLBACK dsa_destroy_callback (LPVOID p, LPVOID pData)
96 ThemeColorOrSize* item = (ThemeColorOrSize*)p;
97 HeapFree (GetProcessHeap(), 0, item->name);
98 HeapFree (GetProcessHeap(), 0, item->fancyName);
102 static void free_color_or_size_dsa (WrappedDsa* wdsa)
104 DSA_DestroyCallback (wdsa->dsa, dsa_destroy_callback, NULL);
107 static void create_color_or_size_dsa (WrappedDsa* wdsa)
109 wdsa->dsa = DSA_Create (sizeof (ThemeColorOrSize), 1);
113 static ThemeColorOrSize* color_or_size_dsa_get (WrappedDsa* wdsa, int index)
115 return (ThemeColorOrSize*)DSA_GetItemPtr (wdsa->dsa, index);
118 static int color_or_size_dsa_find (WrappedDsa* wdsa, const WCHAR* name)
121 for (; i < wdsa->count; i++)
123 ThemeColorOrSize* item = color_or_size_dsa_get (wdsa, i);
124 if (lstrcmpiW (item->name, name) == 0) break;
129 /* A theme file, contains file name, display name, color and size scheme names */
132 WCHAR* themeFileName;
138 static HDSA themeFiles = NULL;
139 static int themeFilesCount = 0;
141 static int CALLBACK theme_dsa_destroy_callback (LPVOID p, LPVOID pData)
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);
151 /* Free memory occupied by the theme list */
152 static void free_theme_files(void)
154 if (themeFiles == NULL) return;
156 DSA_DestroyCallback (themeFiles , theme_dsa_destroy_callback, NULL);
161 typedef HRESULT (WINAPI * EnumTheme) (LPWSTR, LPWSTR, DWORD, LPWSTR);
163 /* fill a string list with either colors or sizes of a theme */
164 static void fill_theme_string_array (const WCHAR* filename,
169 WCHAR name[MAX_PATH];
171 WINE_TRACE ("%s %p %p\n", wine_dbgstr_w (filename), wdsa, enumTheme);
173 while (SUCCEEDED (enumTheme ((WCHAR*)filename, NULL, index++, name)))
175 WINE_TRACE ("%s\n", wine_dbgstr_w (name));
176 color_or_size_dsa_add (wdsa, name, name);
180 /* Theme enumeration callback, adds theme to theme list */
181 static BOOL CALLBACK myEnumThemeProc (LPVOID lpReserved,
182 LPCWSTR pszThemeFileName,
183 LPCWSTR pszThemeName,
185 LPVOID lpReserved2, LPVOID lpData)
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);
195 newEntry.themeFileName = HeapAlloc (GetProcessHeap(), 0,
196 (lstrlenW (pszThemeFileName) + 1) * sizeof(WCHAR));
197 lstrcpyW (newEntry.themeFileName, pszThemeFileName);
199 newEntry.fancyName = HeapAlloc (GetProcessHeap(), 0,
200 (lstrlenW (pszThemeName) + 1) * sizeof(WCHAR));
201 lstrcpyW (newEntry.fancyName, pszThemeName);
203 /*list_add_tail (&themeFiles, &newEntry->entry);*/
204 DSA_InsertItem (themeFiles, themeFilesCount, &newEntry);
210 /* Scan for themes */
211 static void scan_theme_files(void)
213 static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
214 WCHAR themesPath[MAX_PATH];
218 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
219 SHGFP_TYPE_CURRENT, themesPath))) return;
221 themeFiles = DSA_Create (sizeof (ThemeFile), 1);
222 lstrcatW (themesPath, themesSubdir);
224 EnumThemes (themesPath, myEnumThemeProc, 0);
227 /* fill the color & size combo boxes for a given theme */
228 static void fill_color_size_combos (ThemeFile* theme, HWND comboColor,
233 SendMessageW (comboColor, CB_RESETCONTENT, 0, 0);
234 for (i = 0; i < theme->colors.count; i++)
236 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->colors, i);
237 SendMessageW (comboColor, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
240 SendMessageW (comboSize, CB_RESETCONTENT, 0, 0);
241 for (i = 0; i < theme->sizes.count; i++)
243 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->sizes, i);
244 SendMessageW (comboSize, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
248 /* Select the item of a combo box that matches a theme's color and size
250 static void select_color_and_size (ThemeFile* theme,
251 const WCHAR* colorName, HWND comboColor,
252 const WCHAR* sizeName, HWND comboSize)
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);
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)
264 WCHAR textNoTheme[256];
268 WCHAR currentTheme[MAX_PATH];
269 WCHAR currentColor[MAX_PATH];
270 WCHAR currentSize[MAX_PATH];
271 ThemeFile* theme = NULL;
273 LoadStringW (GetModuleHandle (NULL), IDS_NOTHEME, textNoTheme,
274 sizeof(textNoTheme) / sizeof(WCHAR));
276 SendMessageW (comboTheme, CB_RESETCONTENT, 0, 0);
277 SendMessageW (comboTheme, CB_ADDSTRING, 0, (LPARAM)textNoTheme);
279 for (i = 0; i < themeFilesCount; i++)
281 ThemeFile* item = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
282 SendMessageW (comboTheme, CB_ADDSTRING, 0,
283 (LPARAM)item->fancyName);
286 if (IsThemeActive () && SUCCEEDED (GetCurrentThemeName (currentTheme,
287 sizeof(currentTheme) / sizeof(WCHAR),
288 currentColor, sizeof(currentColor) / sizeof(WCHAR),
289 currentSize, sizeof(currentSize) / sizeof(WCHAR))))
291 /* Determine the index of the currently active theme. */
293 for (i = 0; i < themeFilesCount; i++)
295 theme = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
296 if (lstrcmpiW (theme->themeFileName, currentTheme) == 0)
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,
314 fill_color_size_combos (theme, comboColor, comboSize);
315 select_color_and_size (theme, currentColor, comboColor,
316 currentSize, comboSize);
320 /* No theme selected */
324 SendMessageW (comboTheme, CB_SETCURSEL, themeIndex, 0);
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,
340 WCHAR currentTheme[MAX_PATH];
341 WCHAR currentColor[MAX_PATH];
342 WCHAR currentSize[MAX_PATH];
344 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex - 1);
346 fill_color_size_combos (theme, comboColor, comboSize);
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))
354 select_color_and_size (theme, currentColor, comboColor,
355 currentSize, comboSize);
359 SendMessageW (comboColor, CB_SETCURSEL, 0, 0);
360 SendMessageW (comboSize, CB_SETCURSEL, 0, 0);
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)
369 static char b[] = "\0";
374 ApplyTheme (NULL, b, NULL);
379 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex-1);
380 const WCHAR* themeFileName = theme->themeFileName;
381 const WCHAR* colorName = NULL;
382 const WCHAR* sizeName = NULL;
384 ThemeColorOrSize* item;
386 item = color_or_size_dsa_get (&theme->colors, colorIndex);
387 colorName = item->name;
389 item = color_or_size_dsa_get (&theme->sizes, sizeIndex);
390 sizeName = item->name;
392 if (SUCCEEDED (OpenThemeFile (themeFileName, colorName, sizeName,
395 ApplyTheme (hTheme, b, NULL);
396 CloseThemeFile (hTheme);
400 ApplyTheme (NULL, b, NULL);
408 static void enable_size_and_color_controls (HWND dialog, BOOL enable)
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);
416 static void init_dialog (HWND dialog)
421 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
422 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
423 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
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);
431 enable_size_and_color_controls (dialog, TRUE);
438 static void on_theme_changed(HWND dialog) {
439 int index = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
441 if (!update_color_and_size (index, GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
442 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
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);
450 enable_size_and_color_controls (dialog, TRUE);
455 static void apply_theme(HWND dialog)
457 int themeIndex, colorIndex, sizeIndex;
459 if (!theme_dirty) return;
461 themeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
463 colorIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
465 sizeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO),
468 do_apply_theme (themeIndex, colorIndex, sizeIndex);
472 static void on_theme_install(HWND dialog)
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]);
477 WCHAR filetitle[MAX_PATH];
478 WCHAR file[MAX_PATH];
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]));
489 ofn.lStructSize = sizeof(OPENFILENAMEW);
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;
506 ofn.nFileExtension = 0;
507 ofn.lpstrDefExt = NULL;
510 ofn.lpTemplateName = NULL;
512 if (GetOpenFileNameW(&ofn))
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;
519 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
520 SHGFP_TYPE_CURRENT, themeFilePath))) return;
522 PathRemoveExtensionW (filetitle);
524 /* Construct path into which the theme file goes */
525 lstrcatW (themeFilePath, themesSubdir);
526 lstrcatW (themeFilePath, backslash);
527 lstrcatW (themeFilePath, filetitle);
529 /* Create the directory */
530 SHCreateDirectoryExW (dialog, themeFilePath, NULL);
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;
540 WINE_TRACE("copying: %s -> %s\n", wine_dbgstr_w (file),
541 wine_dbgstr_w (themeFilePath));
543 shfop.wFunc = FO_COPY;
545 shfop.pTo = themeFilePath;
546 shfop.fFlags = FOF_NOCONFIRMMKDIR;
547 if (SHFileOperationW (&shfop) == 0)
550 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
551 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
552 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
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);
560 enable_size_and_color_controls (dialog, TRUE);
564 WINE_TRACE("copy operation failed\n");
566 else WINE_TRACE("user cancelled\n");
570 ThemeDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
581 set_window_title(hDlg);
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))
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;
601 switch (LOWORD(wParam))
603 case IDC_THEME_INSTALL:
604 if (HIWORD(wParam) != BN_CLICKED) break;
605 on_theme_install (hDlg);
615 switch (((LPNMHDR)lParam)->code) {
616 case PSN_KILLACTIVE: {
617 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
623 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
626 case PSN_SETACTIVE: {