winecfg: Tidy up included headers.
[wine/multimedia.git] / programs / winecfg / theme.c
blob3cb0e399eb5809aa9336d3ad8ae18b72b33974a5
1 /*
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
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <stdio.h>
26 #include <windows.h>
27 #include <uxtheme.h>
28 #include <tmschema.h>
29 #include <shlobj.h>
30 #include <shlwapi.h>
31 #include <wine/debug.h>
33 #include "resource.h"
34 #include "winecfg.h"
36 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
38 /* UXTHEME functions not in the headers */
40 typedef struct tagTHEMENAMES
42 WCHAR szName[MAX_PATH+1];
43 WCHAR szDisplayName[MAX_PATH+1];
44 WCHAR szTooltip[MAX_PATH+1];
45 } THEMENAMES, *PTHEMENAMES;
47 typedef void* HTHEMEFILE;
48 typedef BOOL (CALLBACK *EnumThemeProc)(LPVOID lpReserved,
49 LPCWSTR pszThemeFileName,
50 LPCWSTR pszThemeName,
51 LPCWSTR pszToolTip, LPVOID lpReserved2,
52 LPVOID lpData);
54 HRESULT WINAPI EnumThemeColors (LPWSTR pszThemeFileName, LPWSTR pszSizeName,
55 DWORD dwColorNum, PTHEMENAMES pszColorNames);
56 HRESULT WINAPI EnumThemeSizes (LPWSTR pszThemeFileName, LPWSTR pszColorName,
57 DWORD dwSizeNum, PTHEMENAMES pszSizeNames);
58 HRESULT WINAPI ApplyTheme (HTHEMEFILE hThemeFile, char* unknown, HWND hWnd);
59 HRESULT WINAPI OpenThemeFile (LPCWSTR pszThemeFileName, LPCWSTR pszColorName,
60 LPCWSTR pszSizeName, HTHEMEFILE* hThemeFile,
61 DWORD unknown);
62 HRESULT WINAPI CloseThemeFile (HTHEMEFILE hThemeFile);
63 HRESULT WINAPI EnumThemes (LPCWSTR pszThemePath, EnumThemeProc callback,
64 LPVOID lpData);
66 /* A struct to keep both the internal and "fancy" name of a color or size */
67 typedef struct
69 WCHAR* name;
70 WCHAR* fancyName;
71 } ThemeColorOrSize;
73 /* wrapper around DSA that also keeps an item count */
74 typedef struct
76 HDSA dsa;
77 int count;
78 } WrappedDsa;
80 /* Some helper functions to deal with ThemeColorOrSize structs in WrappedDSAs */
82 static void color_or_size_dsa_add (WrappedDsa* wdsa, const WCHAR* name,
83 const WCHAR* fancyName)
85 ThemeColorOrSize item;
87 item.name = HeapAlloc (GetProcessHeap(), 0,
88 (lstrlenW (name) + 1) * sizeof(WCHAR));
89 lstrcpyW (item.name, name);
91 item.fancyName = HeapAlloc (GetProcessHeap(), 0,
92 (lstrlenW (fancyName) + 1) * sizeof(WCHAR));
93 lstrcpyW (item.fancyName, fancyName);
95 DSA_InsertItem (wdsa->dsa, wdsa->count, &item);
96 wdsa->count++;
99 static int CALLBACK dsa_destroy_callback (LPVOID p, LPVOID pData)
101 ThemeColorOrSize* item = (ThemeColorOrSize*)p;
102 HeapFree (GetProcessHeap(), 0, item->name);
103 HeapFree (GetProcessHeap(), 0, item->fancyName);
104 return 1;
107 static void free_color_or_size_dsa (WrappedDsa* wdsa)
109 DSA_DestroyCallback (wdsa->dsa, dsa_destroy_callback, NULL);
112 static void create_color_or_size_dsa (WrappedDsa* wdsa)
114 wdsa->dsa = DSA_Create (sizeof (ThemeColorOrSize), 1);
115 wdsa->count = 0;
118 static ThemeColorOrSize* color_or_size_dsa_get (WrappedDsa* wdsa, int index)
120 return (ThemeColorOrSize*)DSA_GetItemPtr (wdsa->dsa, index);
123 static int color_or_size_dsa_find (WrappedDsa* wdsa, const WCHAR* name)
125 int i = 0;
126 for (; i < wdsa->count; i++)
128 ThemeColorOrSize* item = color_or_size_dsa_get (wdsa, i);
129 if (lstrcmpiW (item->name, name) == 0) break;
131 return i;
134 /* A theme file, contains file name, display name, color and size scheme names */
135 typedef struct
137 WCHAR* themeFileName;
138 WCHAR* fancyName;
139 WrappedDsa colors;
140 WrappedDsa sizes;
141 } ThemeFile;
143 static HDSA themeFiles = NULL;
144 static int themeFilesCount = 0;
146 static int CALLBACK theme_dsa_destroy_callback (LPVOID p, LPVOID pData)
148 ThemeFile* item = (ThemeFile*)p;
149 HeapFree (GetProcessHeap(), 0, item->themeFileName);
150 HeapFree (GetProcessHeap(), 0, item->fancyName);
151 free_color_or_size_dsa (&item->colors);
152 free_color_or_size_dsa (&item->sizes);
153 return 1;
156 /* Free memory occupied by the theme list */
157 static void free_theme_files(void)
159 if (themeFiles == NULL) return;
161 DSA_DestroyCallback (themeFiles , theme_dsa_destroy_callback, NULL);
162 themeFiles = NULL;
163 themeFilesCount = 0;
166 typedef HRESULT (WINAPI * EnumTheme) (LPWSTR, LPWSTR, DWORD, PTHEMENAMES);
168 /* fill a string list with either colors or sizes of a theme */
169 static void fill_theme_string_array (const WCHAR* filename,
170 WrappedDsa* wdsa,
171 EnumTheme enumTheme)
173 DWORD index = 0;
174 THEMENAMES names;
176 WINE_TRACE ("%s %p %p\n", wine_dbgstr_w (filename), wdsa, enumTheme);
178 while (SUCCEEDED (enumTheme ((WCHAR*)filename, NULL, index++, &names)))
180 WINE_TRACE ("%s: %s\n", wine_dbgstr_w (names.szName),
181 wine_dbgstr_w (names.szDisplayName));
182 color_or_size_dsa_add (wdsa, names.szName, names.szDisplayName);
186 /* Theme enumeration callback, adds theme to theme list */
187 static BOOL CALLBACK myEnumThemeProc (LPVOID lpReserved,
188 LPCWSTR pszThemeFileName,
189 LPCWSTR pszThemeName,
190 LPCWSTR pszToolTip,
191 LPVOID lpReserved2, LPVOID lpData)
193 ThemeFile newEntry;
195 /* fill size/color lists */
196 create_color_or_size_dsa (&newEntry.colors);
197 fill_theme_string_array (pszThemeFileName, &newEntry.colors, EnumThemeColors);
198 create_color_or_size_dsa (&newEntry.sizes);
199 fill_theme_string_array (pszThemeFileName, &newEntry.sizes, EnumThemeSizes);
201 newEntry.themeFileName = HeapAlloc (GetProcessHeap(), 0,
202 (lstrlenW (pszThemeFileName) + 1) * sizeof(WCHAR));
203 lstrcpyW (newEntry.themeFileName, pszThemeFileName);
205 newEntry.fancyName = HeapAlloc (GetProcessHeap(), 0,
206 (lstrlenW (pszThemeName) + 1) * sizeof(WCHAR));
207 lstrcpyW (newEntry.fancyName, pszThemeName);
209 /*list_add_tail (&themeFiles, &newEntry->entry);*/
210 DSA_InsertItem (themeFiles, themeFilesCount, &newEntry);
211 themeFilesCount++;
213 return TRUE;
216 /* Scan for themes */
217 static void scan_theme_files(void)
219 static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
220 WCHAR themesPath[MAX_PATH];
222 free_theme_files();
224 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
225 SHGFP_TYPE_CURRENT, themesPath))) return;
227 themeFiles = DSA_Create (sizeof (ThemeFile), 1);
228 lstrcatW (themesPath, themesSubdir);
230 EnumThemes (themesPath, myEnumThemeProc, 0);
233 /* fill the color & size combo boxes for a given theme */
234 static void fill_color_size_combos (ThemeFile* theme, HWND comboColor,
235 HWND comboSize)
237 int i;
239 SendMessageW (comboColor, CB_RESETCONTENT, 0, 0);
240 for (i = 0; i < theme->colors.count; i++)
242 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->colors, i);
243 SendMessageW (comboColor, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
246 SendMessageW (comboSize, CB_RESETCONTENT, 0, 0);
247 for (i = 0; i < theme->sizes.count; i++)
249 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->sizes, i);
250 SendMessageW (comboSize, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
254 /* Select the item of a combo box that matches a theme's color and size
255 * scheme. */
256 static void select_color_and_size (ThemeFile* theme,
257 const WCHAR* colorName, HWND comboColor,
258 const WCHAR* sizeName, HWND comboSize)
260 SendMessageW (comboColor, CB_SETCURSEL,
261 color_or_size_dsa_find (&theme->colors, colorName), 0);
262 SendMessageW (comboSize, CB_SETCURSEL,
263 color_or_size_dsa_find (&theme->sizes, sizeName), 0);
266 /* Fill theme, color and sizes combo boxes with the know themes and select
267 * the entries matching the currently active theme. */
268 static BOOL fill_theme_list (HWND comboTheme, HWND comboColor, HWND comboSize)
270 WCHAR textNoTheme[256];
271 int themeIndex = 0;
272 BOOL ret = TRUE;
273 int i;
274 WCHAR currentTheme[MAX_PATH];
275 WCHAR currentColor[MAX_PATH];
276 WCHAR currentSize[MAX_PATH];
277 ThemeFile* theme = NULL;
279 LoadStringW (GetModuleHandle (NULL), IDS_NOTHEME, textNoTheme,
280 sizeof(textNoTheme) / sizeof(WCHAR));
282 SendMessageW (comboTheme, CB_RESETCONTENT, 0, 0);
283 SendMessageW (comboTheme, CB_ADDSTRING, 0, (LPARAM)textNoTheme);
285 for (i = 0; i < themeFilesCount; i++)
287 ThemeFile* item = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
288 SendMessageW (comboTheme, CB_ADDSTRING, 0,
289 (LPARAM)item->fancyName);
292 if (IsThemeActive () && SUCCEEDED (GetCurrentThemeName (currentTheme,
293 sizeof(currentTheme) / sizeof(WCHAR),
294 currentColor, sizeof(currentColor) / sizeof(WCHAR),
295 currentSize, sizeof(currentSize) / sizeof(WCHAR))))
297 /* Determine the index of the currently active theme. */
298 BOOL found = FALSE;
299 for (i = 0; i < themeFilesCount; i++)
301 theme = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
302 if (lstrcmpiW (theme->themeFileName, currentTheme) == 0)
304 found = TRUE;
305 themeIndex = i+1;
306 break;
309 if (!found)
311 /* Current theme not found?... add to the list, then... */
312 WINE_TRACE("Theme %s not in list of enumerated themes",
313 wine_dbgstr_w (currentTheme));
314 myEnumThemeProc (NULL, currentTheme, currentTheme,
315 currentTheme, NULL, NULL);
316 themeIndex = themeFilesCount;
317 theme = (ThemeFile*)DSA_GetItemPtr (themeFiles,
318 themeFilesCount-1);
320 fill_color_size_combos (theme, comboColor, comboSize);
321 select_color_and_size (theme, currentColor, comboColor,
322 currentSize, comboSize);
324 else
326 /* No theme selected */
327 ret = FALSE;
330 SendMessageW (comboTheme, CB_SETCURSEL, themeIndex, 0);
331 return ret;
334 /* Update the color & size combo boxes when the selection of the theme
335 * combo changed. Selects the current color and size scheme if the theme
336 * is currently active, otherwise the first color and size. */
337 static BOOL update_color_and_size (int themeIndex, HWND comboColor,
338 HWND comboSize)
340 if (themeIndex == 0)
342 return FALSE;
344 else
346 WCHAR currentTheme[MAX_PATH];
347 WCHAR currentColor[MAX_PATH];
348 WCHAR currentSize[MAX_PATH];
349 ThemeFile* theme =
350 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex - 1);
352 fill_color_size_combos (theme, comboColor, comboSize);
354 if ((SUCCEEDED (GetCurrentThemeName (currentTheme,
355 sizeof(currentTheme) / sizeof(WCHAR),
356 currentColor, sizeof(currentColor) / sizeof(WCHAR),
357 currentSize, sizeof(currentSize) / sizeof(WCHAR))))
358 && (lstrcmpiW (currentTheme, theme->themeFileName) == 0))
360 select_color_and_size (theme, currentColor, comboColor,
361 currentSize, comboSize);
363 else
365 SendMessageW (comboColor, CB_SETCURSEL, 0, 0);
366 SendMessageW (comboSize, CB_SETCURSEL, 0, 0);
369 return TRUE;
372 /* Apply a theme from a given theme, color and size combo box item index. */
373 static void do_apply_theme (int themeIndex, int colorIndex, int sizeIndex)
375 static char b[] = "\0";
377 if (themeIndex == 0)
379 /* no theme */
380 ApplyTheme (NULL, b, NULL);
382 else
384 ThemeFile* theme =
385 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex-1);
386 const WCHAR* themeFileName = theme->themeFileName;
387 const WCHAR* colorName = NULL;
388 const WCHAR* sizeName = NULL;
389 HTHEMEFILE hTheme;
390 ThemeColorOrSize* item;
392 item = color_or_size_dsa_get (&theme->colors, colorIndex);
393 colorName = item->name;
395 item = color_or_size_dsa_get (&theme->sizes, sizeIndex);
396 sizeName = item->name;
398 if (SUCCEEDED (OpenThemeFile (themeFileName, colorName, sizeName,
399 &hTheme, 0)))
401 ApplyTheme (hTheme, b, NULL);
402 CloseThemeFile (hTheme);
404 else
406 ApplyTheme (NULL, b, NULL);
411 int updating_ui;
412 BOOL theme_dirty;
414 static void enable_size_and_color_controls (HWND dialog, BOOL enable)
416 EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), enable);
417 EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORTEXT), enable);
418 EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), enable);
419 EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZETEXT), enable);
422 static void init_dialog (HWND dialog)
424 updating_ui = TRUE;
426 scan_theme_files();
427 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
428 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
429 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
431 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
432 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
433 enable_size_and_color_controls (dialog, FALSE);
435 else
437 enable_size_and_color_controls (dialog, TRUE);
439 theme_dirty = FALSE;
441 updating_ui = FALSE;
444 static void on_theme_changed(HWND dialog) {
445 int index = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
446 CB_GETCURSEL, 0, 0);
447 if (!update_color_and_size (index, GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
448 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
450 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
451 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
452 enable_size_and_color_controls (dialog, FALSE);
454 else
456 enable_size_and_color_controls (dialog, TRUE);
458 theme_dirty = TRUE;
461 static void apply_theme(HWND dialog)
463 int themeIndex, colorIndex, sizeIndex;
465 if (!theme_dirty) return;
467 themeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
468 CB_GETCURSEL, 0, 0);
469 colorIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
470 CB_GETCURSEL, 0, 0);
471 sizeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO),
472 CB_GETCURSEL, 0, 0);
474 do_apply_theme (themeIndex, colorIndex, sizeIndex);
475 theme_dirty = FALSE;
478 static void on_theme_install(HWND dialog)
480 static const WCHAR filterMask[] = {0,'*','.','m','s','s','t','y','l','e','s',0,0};
481 const int filterMaskLen = sizeof(filterMask)/sizeof(filterMask[0]);
482 OPENFILENAMEW ofn;
483 WCHAR filetitle[MAX_PATH];
484 WCHAR file[MAX_PATH];
485 WCHAR filter[100];
486 WCHAR title[100];
488 LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE,
489 filter, sizeof (filter) / sizeof (filter[0]) - filterMaskLen);
490 memcpy (filter + lstrlenW (filter), filterMask,
491 filterMaskLen * sizeof (WCHAR));
492 LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE_SELECT,
493 title, sizeof (title) / sizeof (title[0]));
495 ofn.lStructSize = sizeof(OPENFILENAMEW);
496 ofn.hwndOwner = 0;
497 ofn.hInstance = 0;
498 ofn.lpstrFilter = filter;
499 ofn.lpstrCustomFilter = NULL;
500 ofn.nMaxCustFilter = 0;
501 ofn.nFilterIndex = 0;
502 ofn.lpstrFile = file;
503 ofn.lpstrFile[0] = '\0';
504 ofn.nMaxFile = sizeof(file)/sizeof(filetitle[0]);
505 ofn.lpstrFileTitle = filetitle;
506 ofn.lpstrFileTitle[0] = '\0';
507 ofn.nMaxFileTitle = sizeof(filetitle)/sizeof(filetitle[0]);
508 ofn.lpstrInitialDir = NULL;
509 ofn.lpstrTitle = title;
510 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
511 ofn.nFileOffset = 0;
512 ofn.nFileExtension = 0;
513 ofn.lpstrDefExt = NULL;
514 ofn.lCustData = 0;
515 ofn.lpfnHook = NULL;
516 ofn.lpTemplateName = NULL;
518 if (GetOpenFileNameW(&ofn))
520 static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
521 static const WCHAR backslash[] = { '\\',0 };
522 WCHAR themeFilePath[MAX_PATH];
523 SHFILEOPSTRUCTW shfop;
525 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
526 SHGFP_TYPE_CURRENT, themeFilePath))) return;
528 PathRemoveExtensionW (filetitle);
530 /* Construct path into which the theme file goes */
531 lstrcatW (themeFilePath, themesSubdir);
532 lstrcatW (themeFilePath, backslash);
533 lstrcatW (themeFilePath, filetitle);
535 /* Create the directory */
536 SHCreateDirectoryExW (dialog, themeFilePath, NULL);
538 /* Append theme file name itself */
539 lstrcatW (themeFilePath, backslash);
540 lstrcatW (themeFilePath, PathFindFileNameW (file));
541 /* SHFileOperation() takes lists as input, so double-nullterminate */
542 themeFilePath[lstrlenW (themeFilePath)+1] = 0;
543 file[lstrlenW (file)+1] = 0;
545 /* Do the copying */
546 WINE_TRACE("copying: %s -> %s\n", wine_dbgstr_w (file),
547 wine_dbgstr_w (themeFilePath));
548 shfop.hwnd = dialog;
549 shfop.wFunc = FO_COPY;
550 shfop.pFrom = file;
551 shfop.pTo = themeFilePath;
552 shfop.fFlags = FOF_NOCONFIRMMKDIR;
553 if (SHFileOperationW (&shfop) == 0)
555 scan_theme_files();
556 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
557 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
558 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
560 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
561 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
562 enable_size_and_color_controls (dialog, FALSE);
564 else
566 enable_size_and_color_controls (dialog, TRUE);
569 else
570 WINE_TRACE("copy operation failed\n");
572 else WINE_TRACE("user cancelled\n");
575 INT_PTR CALLBACK
576 ThemeDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
578 switch (uMsg) {
579 case WM_INITDIALOG:
580 break;
582 case WM_DESTROY:
583 free_theme_files();
584 break;
586 case WM_SHOWWINDOW:
587 set_window_title(hDlg);
588 break;
590 case WM_COMMAND:
591 switch(HIWORD(wParam)) {
592 case CBN_SELCHANGE: {
593 if (updating_ui) break;
594 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
595 switch (LOWORD(wParam))
597 case IDC_THEME_THEMECOMBO: on_theme_changed(hDlg); break;
598 case IDC_THEME_COLORCOMBO: /* fall through */
599 case IDC_THEME_SIZECOMBO: theme_dirty = TRUE; break;
601 break;
604 default:
605 break;
607 switch (LOWORD(wParam))
609 case IDC_THEME_INSTALL:
610 if (HIWORD(wParam) != BN_CLICKED) break;
611 on_theme_install (hDlg);
612 break;
614 default:
615 break;
617 break;
620 case WM_NOTIFY:
621 switch (((LPNMHDR)lParam)->code) {
622 case PSN_KILLACTIVE: {
623 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
624 break;
626 case PSN_APPLY: {
627 apply();
628 apply_theme(hDlg);
629 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
630 break;
632 case PSN_SETACTIVE: {
633 init_dialog (hDlg);
634 break;
637 break;
639 default:
640 break;
642 return FALSE;