msi: Make some variables static and/or const.
[wine/multimedia.git] / programs / winecfg / theme.c
blob8c7f8197dcb7ce3d2c70899ac540cc173c3ec36b
1 /*
2 * Desktop Integration
3 * - Theme configuration code
4 * - User Shell Folder mapping
6 * Copyright (c) 2005 by Frank Richter
7 * Copyright (c) 2006 by Michael Jung
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
31 #define COBJMACROS
33 #include <windows.h>
34 #include <uxtheme.h>
35 #include <tmschema.h>
36 #include <shlobj.h>
37 #include <shlwapi.h>
38 #include <wine/debug.h>
40 #include "resource.h"
41 #include "winecfg.h"
43 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
45 /* UXTHEME functions not in the headers */
47 typedef struct tagTHEMENAMES
49 WCHAR szName[MAX_PATH+1];
50 WCHAR szDisplayName[MAX_PATH+1];
51 WCHAR szTooltip[MAX_PATH+1];
52 } THEMENAMES, *PTHEMENAMES;
54 typedef void* HTHEMEFILE;
55 typedef BOOL (CALLBACK *EnumThemeProc)(LPVOID lpReserved,
56 LPCWSTR pszThemeFileName,
57 LPCWSTR pszThemeName,
58 LPCWSTR pszToolTip, LPVOID lpReserved2,
59 LPVOID lpData);
61 HRESULT WINAPI EnumThemeColors (LPWSTR pszThemeFileName, LPWSTR pszSizeName,
62 DWORD dwColorNum, PTHEMENAMES pszColorNames);
63 HRESULT WINAPI EnumThemeSizes (LPWSTR pszThemeFileName, LPWSTR pszColorName,
64 DWORD dwSizeNum, PTHEMENAMES pszSizeNames);
65 HRESULT WINAPI ApplyTheme (HTHEMEFILE hThemeFile, char* unknown, HWND hWnd);
66 HRESULT WINAPI OpenThemeFile (LPCWSTR pszThemeFileName, LPCWSTR pszColorName,
67 LPCWSTR pszSizeName, HTHEMEFILE* hThemeFile,
68 DWORD unknown);
69 HRESULT WINAPI CloseThemeFile (HTHEMEFILE hThemeFile);
70 HRESULT WINAPI EnumThemes (LPCWSTR pszThemePath, EnumThemeProc callback,
71 LPVOID lpData);
73 /* A struct to keep both the internal and "fancy" name of a color or size */
74 typedef struct
76 WCHAR* name;
77 WCHAR* fancyName;
78 } ThemeColorOrSize;
80 /* wrapper around DSA that also keeps an item count */
81 typedef struct
83 HDSA dsa;
84 int count;
85 } WrappedDsa;
87 /* Some helper functions to deal with ThemeColorOrSize structs in WrappedDSAs */
89 static void color_or_size_dsa_add (WrappedDsa* wdsa, const WCHAR* name,
90 const WCHAR* fancyName)
92 ThemeColorOrSize item;
94 item.name = HeapAlloc (GetProcessHeap(), 0,
95 (lstrlenW (name) + 1) * sizeof(WCHAR));
96 lstrcpyW (item.name, name);
98 item.fancyName = HeapAlloc (GetProcessHeap(), 0,
99 (lstrlenW (fancyName) + 1) * sizeof(WCHAR));
100 lstrcpyW (item.fancyName, fancyName);
102 DSA_InsertItem (wdsa->dsa, wdsa->count, &item);
103 wdsa->count++;
106 static int CALLBACK dsa_destroy_callback (LPVOID p, LPVOID pData)
108 ThemeColorOrSize* item = (ThemeColorOrSize*)p;
109 HeapFree (GetProcessHeap(), 0, item->name);
110 HeapFree (GetProcessHeap(), 0, item->fancyName);
111 return 1;
114 static void free_color_or_size_dsa (WrappedDsa* wdsa)
116 DSA_DestroyCallback (wdsa->dsa, dsa_destroy_callback, NULL);
119 static void create_color_or_size_dsa (WrappedDsa* wdsa)
121 wdsa->dsa = DSA_Create (sizeof (ThemeColorOrSize), 1);
122 wdsa->count = 0;
125 static ThemeColorOrSize* color_or_size_dsa_get (WrappedDsa* wdsa, int index)
127 return (ThemeColorOrSize*)DSA_GetItemPtr (wdsa->dsa, index);
130 static int color_or_size_dsa_find (WrappedDsa* wdsa, const WCHAR* name)
132 int i = 0;
133 for (; i < wdsa->count; i++)
135 ThemeColorOrSize* item = color_or_size_dsa_get (wdsa, i);
136 if (lstrcmpiW (item->name, name) == 0) break;
138 return i;
141 /* A theme file, contains file name, display name, color and size scheme names */
142 typedef struct
144 WCHAR* themeFileName;
145 WCHAR* fancyName;
146 WrappedDsa colors;
147 WrappedDsa sizes;
148 } ThemeFile;
150 static HDSA themeFiles = NULL;
151 static int themeFilesCount = 0;
153 static int CALLBACK theme_dsa_destroy_callback (LPVOID p, LPVOID pData)
155 ThemeFile* item = (ThemeFile*)p;
156 HeapFree (GetProcessHeap(), 0, item->themeFileName);
157 HeapFree (GetProcessHeap(), 0, item->fancyName);
158 free_color_or_size_dsa (&item->colors);
159 free_color_or_size_dsa (&item->sizes);
160 return 1;
163 /* Free memory occupied by the theme list */
164 static void free_theme_files(void)
166 if (themeFiles == NULL) return;
168 DSA_DestroyCallback (themeFiles , theme_dsa_destroy_callback, NULL);
169 themeFiles = NULL;
170 themeFilesCount = 0;
173 typedef HRESULT (WINAPI * EnumTheme) (LPWSTR, LPWSTR, DWORD, PTHEMENAMES);
175 /* fill a string list with either colors or sizes of a theme */
176 static void fill_theme_string_array (const WCHAR* filename,
177 WrappedDsa* wdsa,
178 EnumTheme enumTheme)
180 DWORD index = 0;
181 THEMENAMES names;
183 WINE_TRACE ("%s %p %p\n", wine_dbgstr_w (filename), wdsa, enumTheme);
185 while (SUCCEEDED (enumTheme ((WCHAR*)filename, NULL, index++, &names)))
187 WINE_TRACE ("%s: %s\n", wine_dbgstr_w (names.szName),
188 wine_dbgstr_w (names.szDisplayName));
189 color_or_size_dsa_add (wdsa, names.szName, names.szDisplayName);
193 /* Theme enumeration callback, adds theme to theme list */
194 static BOOL CALLBACK myEnumThemeProc (LPVOID lpReserved,
195 LPCWSTR pszThemeFileName,
196 LPCWSTR pszThemeName,
197 LPCWSTR pszToolTip,
198 LPVOID lpReserved2, LPVOID lpData)
200 ThemeFile newEntry;
202 /* fill size/color lists */
203 create_color_or_size_dsa (&newEntry.colors);
204 fill_theme_string_array (pszThemeFileName, &newEntry.colors, EnumThemeColors);
205 create_color_or_size_dsa (&newEntry.sizes);
206 fill_theme_string_array (pszThemeFileName, &newEntry.sizes, EnumThemeSizes);
208 newEntry.themeFileName = HeapAlloc (GetProcessHeap(), 0,
209 (lstrlenW (pszThemeFileName) + 1) * sizeof(WCHAR));
210 lstrcpyW (newEntry.themeFileName, pszThemeFileName);
212 newEntry.fancyName = HeapAlloc (GetProcessHeap(), 0,
213 (lstrlenW (pszThemeName) + 1) * sizeof(WCHAR));
214 lstrcpyW (newEntry.fancyName, pszThemeName);
216 /*list_add_tail (&themeFiles, &newEntry->entry);*/
217 DSA_InsertItem (themeFiles, themeFilesCount, &newEntry);
218 themeFilesCount++;
220 return TRUE;
223 /* Scan for themes */
224 static void scan_theme_files(void)
226 static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
227 WCHAR themesPath[MAX_PATH];
229 free_theme_files();
231 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
232 SHGFP_TYPE_CURRENT, themesPath))) return;
234 themeFiles = DSA_Create (sizeof (ThemeFile), 1);
235 lstrcatW (themesPath, themesSubdir);
237 EnumThemes (themesPath, myEnumThemeProc, 0);
240 /* fill the color & size combo boxes for a given theme */
241 static void fill_color_size_combos (ThemeFile* theme, HWND comboColor,
242 HWND comboSize)
244 int i;
246 SendMessageW (comboColor, CB_RESETCONTENT, 0, 0);
247 for (i = 0; i < theme->colors.count; i++)
249 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->colors, i);
250 SendMessageW (comboColor, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
253 SendMessageW (comboSize, CB_RESETCONTENT, 0, 0);
254 for (i = 0; i < theme->sizes.count; i++)
256 ThemeColorOrSize* item = color_or_size_dsa_get (&theme->sizes, i);
257 SendMessageW (comboSize, CB_ADDSTRING, 0, (LPARAM)item->fancyName);
261 /* Select the item of a combo box that matches a theme's color and size
262 * scheme. */
263 static void select_color_and_size (ThemeFile* theme,
264 const WCHAR* colorName, HWND comboColor,
265 const WCHAR* sizeName, HWND comboSize)
267 SendMessageW (comboColor, CB_SETCURSEL,
268 color_or_size_dsa_find (&theme->colors, colorName), 0);
269 SendMessageW (comboSize, CB_SETCURSEL,
270 color_or_size_dsa_find (&theme->sizes, sizeName), 0);
273 /* Fill theme, color and sizes combo boxes with the know themes and select
274 * the entries matching the currently active theme. */
275 static BOOL fill_theme_list (HWND comboTheme, HWND comboColor, HWND comboSize)
277 WCHAR textNoTheme[256];
278 int themeIndex = 0;
279 BOOL ret = TRUE;
280 int i;
281 WCHAR currentTheme[MAX_PATH];
282 WCHAR currentColor[MAX_PATH];
283 WCHAR currentSize[MAX_PATH];
284 ThemeFile* theme = NULL;
286 LoadStringW (GetModuleHandle (NULL), IDS_NOTHEME, textNoTheme,
287 sizeof(textNoTheme) / sizeof(WCHAR));
289 SendMessageW (comboTheme, CB_RESETCONTENT, 0, 0);
290 SendMessageW (comboTheme, CB_ADDSTRING, 0, (LPARAM)textNoTheme);
292 for (i = 0; i < themeFilesCount; i++)
294 ThemeFile* item = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
295 SendMessageW (comboTheme, CB_ADDSTRING, 0,
296 (LPARAM)item->fancyName);
299 if (IsThemeActive () && SUCCEEDED (GetCurrentThemeName (currentTheme,
300 sizeof(currentTheme) / sizeof(WCHAR),
301 currentColor, sizeof(currentColor) / sizeof(WCHAR),
302 currentSize, sizeof(currentSize) / sizeof(WCHAR))))
304 /* Determine the index of the currently active theme. */
305 BOOL found = FALSE;
306 for (i = 0; i < themeFilesCount; i++)
308 theme = (ThemeFile*)DSA_GetItemPtr (themeFiles, i);
309 if (lstrcmpiW (theme->themeFileName, currentTheme) == 0)
311 found = TRUE;
312 themeIndex = i+1;
313 break;
316 if (!found)
318 /* Current theme not found?... add to the list, then... */
319 WINE_TRACE("Theme %s not in list of enumerated themes",
320 wine_dbgstr_w (currentTheme));
321 myEnumThemeProc (NULL, currentTheme, currentTheme,
322 currentTheme, NULL, NULL);
323 themeIndex = themeFilesCount;
324 theme = (ThemeFile*)DSA_GetItemPtr (themeFiles,
325 themeFilesCount-1);
327 fill_color_size_combos (theme, comboColor, comboSize);
328 select_color_and_size (theme, currentColor, comboColor,
329 currentSize, comboSize);
331 else
333 /* No theme selected */
334 ret = FALSE;
337 SendMessageW (comboTheme, CB_SETCURSEL, themeIndex, 0);
338 return ret;
341 /* Update the color & size combo boxes when the selection of the theme
342 * combo changed. Selects the current color and size scheme if the theme
343 * is currently active, otherwise the first color and size. */
344 static BOOL update_color_and_size (int themeIndex, HWND comboColor,
345 HWND comboSize)
347 if (themeIndex == 0)
349 return FALSE;
351 else
353 WCHAR currentTheme[MAX_PATH];
354 WCHAR currentColor[MAX_PATH];
355 WCHAR currentSize[MAX_PATH];
356 ThemeFile* theme =
357 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex - 1);
359 fill_color_size_combos (theme, comboColor, comboSize);
361 if ((SUCCEEDED (GetCurrentThemeName (currentTheme,
362 sizeof(currentTheme) / sizeof(WCHAR),
363 currentColor, sizeof(currentColor) / sizeof(WCHAR),
364 currentSize, sizeof(currentSize) / sizeof(WCHAR))))
365 && (lstrcmpiW (currentTheme, theme->themeFileName) == 0))
367 select_color_and_size (theme, currentColor, comboColor,
368 currentSize, comboSize);
370 else
372 SendMessageW (comboColor, CB_SETCURSEL, 0, 0);
373 SendMessageW (comboSize, CB_SETCURSEL, 0, 0);
376 return TRUE;
379 /* Apply a theme from a given theme, color and size combo box item index. */
380 static void do_apply_theme (int themeIndex, int colorIndex, int sizeIndex)
382 static char b[] = "\0";
384 if (themeIndex == 0)
386 /* no theme */
387 ApplyTheme (NULL, b, NULL);
389 else
391 ThemeFile* theme =
392 (ThemeFile*)DSA_GetItemPtr (themeFiles, themeIndex-1);
393 const WCHAR* themeFileName = theme->themeFileName;
394 const WCHAR* colorName = NULL;
395 const WCHAR* sizeName = NULL;
396 HTHEMEFILE hTheme;
397 ThemeColorOrSize* item;
399 item = color_or_size_dsa_get (&theme->colors, colorIndex);
400 colorName = item->name;
402 item = color_or_size_dsa_get (&theme->sizes, sizeIndex);
403 sizeName = item->name;
405 if (SUCCEEDED (OpenThemeFile (themeFileName, colorName, sizeName,
406 &hTheme, 0)))
408 ApplyTheme (hTheme, b, NULL);
409 CloseThemeFile (hTheme);
411 else
413 ApplyTheme (NULL, b, NULL);
418 int updating_ui;
419 BOOL theme_dirty;
421 static void enable_size_and_color_controls (HWND dialog, BOOL enable)
423 EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), enable);
424 EnableWindow (GetDlgItem (dialog, IDC_THEME_COLORTEXT), enable);
425 EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), enable);
426 EnableWindow (GetDlgItem (dialog, IDC_THEME_SIZETEXT), enable);
429 static void init_dialog (HWND dialog)
431 updating_ui = TRUE;
433 scan_theme_files();
434 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
435 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
436 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
438 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
439 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
440 enable_size_and_color_controls (dialog, FALSE);
442 else
444 enable_size_and_color_controls (dialog, TRUE);
446 theme_dirty = FALSE;
448 updating_ui = FALSE;
451 static void on_theme_changed(HWND dialog) {
452 int index = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
453 CB_GETCURSEL, 0, 0);
454 if (!update_color_and_size (index, GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
455 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
457 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
458 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
459 enable_size_and_color_controls (dialog, FALSE);
461 else
463 enable_size_and_color_controls (dialog, TRUE);
465 theme_dirty = TRUE;
468 static void apply_theme(HWND dialog)
470 int themeIndex, colorIndex, sizeIndex;
472 if (!theme_dirty) return;
474 themeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
475 CB_GETCURSEL, 0, 0);
476 colorIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
477 CB_GETCURSEL, 0, 0);
478 sizeIndex = SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO),
479 CB_GETCURSEL, 0, 0);
481 do_apply_theme (themeIndex, colorIndex, sizeIndex);
482 theme_dirty = FALSE;
485 static void on_theme_install(HWND dialog)
487 static const WCHAR filterMask[] = {0,'*','.','m','s','s','t','y','l','e','s',0,0};
488 const int filterMaskLen = sizeof(filterMask)/sizeof(filterMask[0]);
489 OPENFILENAMEW ofn;
490 WCHAR filetitle[MAX_PATH];
491 WCHAR file[MAX_PATH];
492 WCHAR filter[100];
493 WCHAR title[100];
495 LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE,
496 filter, sizeof (filter) / sizeof (filter[0]) - filterMaskLen);
497 memcpy (filter + lstrlenW (filter), filterMask,
498 filterMaskLen * sizeof (WCHAR));
499 LoadStringW (GetModuleHandle (NULL), IDS_THEMEFILE_SELECT,
500 title, sizeof (title) / sizeof (title[0]));
502 ofn.lStructSize = sizeof(OPENFILENAMEW);
503 ofn.hwndOwner = 0;
504 ofn.hInstance = 0;
505 ofn.lpstrFilter = filter;
506 ofn.lpstrCustomFilter = NULL;
507 ofn.nMaxCustFilter = 0;
508 ofn.nFilterIndex = 0;
509 ofn.lpstrFile = file;
510 ofn.lpstrFile[0] = '\0';
511 ofn.nMaxFile = sizeof(file)/sizeof(filetitle[0]);
512 ofn.lpstrFileTitle = filetitle;
513 ofn.lpstrFileTitle[0] = '\0';
514 ofn.nMaxFileTitle = sizeof(filetitle)/sizeof(filetitle[0]);
515 ofn.lpstrInitialDir = NULL;
516 ofn.lpstrTitle = title;
517 ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
518 ofn.nFileOffset = 0;
519 ofn.nFileExtension = 0;
520 ofn.lpstrDefExt = NULL;
521 ofn.lCustData = 0;
522 ofn.lpfnHook = NULL;
523 ofn.lpTemplateName = NULL;
525 if (GetOpenFileNameW(&ofn))
527 static const WCHAR themesSubdir[] = { '\\','T','h','e','m','e','s',0 };
528 static const WCHAR backslash[] = { '\\',0 };
529 WCHAR themeFilePath[MAX_PATH];
530 SHFILEOPSTRUCTW shfop;
532 if (FAILED (SHGetFolderPathW (NULL, CSIDL_RESOURCES, NULL,
533 SHGFP_TYPE_CURRENT, themeFilePath))) return;
535 PathRemoveExtensionW (filetitle);
537 /* Construct path into which the theme file goes */
538 lstrcatW (themeFilePath, themesSubdir);
539 lstrcatW (themeFilePath, backslash);
540 lstrcatW (themeFilePath, filetitle);
542 /* Create the directory */
543 SHCreateDirectoryExW (dialog, themeFilePath, NULL);
545 /* Append theme file name itself */
546 lstrcatW (themeFilePath, backslash);
547 lstrcatW (themeFilePath, PathFindFileNameW (file));
548 /* SHFileOperation() takes lists as input, so double-nullterminate */
549 themeFilePath[lstrlenW (themeFilePath)+1] = 0;
550 file[lstrlenW (file)+1] = 0;
552 /* Do the copying */
553 WINE_TRACE("copying: %s -> %s\n", wine_dbgstr_w (file),
554 wine_dbgstr_w (themeFilePath));
555 shfop.hwnd = dialog;
556 shfop.wFunc = FO_COPY;
557 shfop.pFrom = file;
558 shfop.pTo = themeFilePath;
559 shfop.fFlags = FOF_NOCONFIRMMKDIR;
560 if (SHFileOperationW (&shfop) == 0)
562 scan_theme_files();
563 if (!fill_theme_list (GetDlgItem (dialog, IDC_THEME_THEMECOMBO),
564 GetDlgItem (dialog, IDC_THEME_COLORCOMBO),
565 GetDlgItem (dialog, IDC_THEME_SIZECOMBO)))
567 SendMessageW (GetDlgItem (dialog, IDC_THEME_COLORCOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
568 SendMessageW (GetDlgItem (dialog, IDC_THEME_SIZECOMBO), CB_SETCURSEL, (WPARAM)-1, 0);
569 enable_size_and_color_controls (dialog, FALSE);
571 else
573 enable_size_and_color_controls (dialog, TRUE);
576 else
577 WINE_TRACE("copy operation failed\n");
579 else WINE_TRACE("user cancelled\n");
582 /* Information about symbolic link targets of certain User Shell Folders. */
583 struct ShellFolderInfo {
584 int nFolder;
585 char szLinkTarget[FILENAME_MAX];
588 static struct ShellFolderInfo asfiInfo[] = {
589 { CSIDL_DESKTOP, "" },
590 { CSIDL_PERSONAL, "" },
591 { CSIDL_MYPICTURES, "" },
592 { CSIDL_MYMUSIC, "" },
593 { CSIDL_MYVIDEO, "" }
596 static struct ShellFolderInfo *psfiSelected = NULL;
598 #define NUM_ELEMS(x) (sizeof(x)/sizeof(*(x)))
600 static void init_shell_folder_listview_headers(HWND dialog) {
601 LVCOLUMN listColumn;
602 RECT viewRect;
603 char szShellFolder[64] = "Shell Folder";
604 char szLinksTo[64] = "Links to";
605 int width;
607 LoadString(GetModuleHandle(NULL), IDS_SHELL_FOLDER, szShellFolder, sizeof(szShellFolder));
608 LoadString(GetModuleHandle(NULL), IDS_LINKS_TO, szLinksTo, sizeof(szLinksTo));
610 GetClientRect(GetDlgItem(dialog, IDC_LIST_SFPATHS), &viewRect);
611 width = (viewRect.right - viewRect.left) / 4;
613 listColumn.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
614 listColumn.pszText = szShellFolder;
615 listColumn.cchTextMax = lstrlen(listColumn.pszText);
616 listColumn.cx = width;
618 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_INSERTCOLUMN, 0, (LPARAM) &listColumn);
620 listColumn.pszText = szLinksTo;
621 listColumn.cchTextMax = lstrlen(listColumn.pszText);
622 listColumn.cx = viewRect.right - viewRect.left - width - 1;
624 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_INSERTCOLUMN, 1, (LPARAM) &listColumn);
627 /* Reads the currently set shell folder symbol link targets into asfiInfo. */
628 static void read_shell_folder_link_targets() {
629 WCHAR wszPath[MAX_PATH];
630 HRESULT hr;
631 int i;
633 for (i=0; i<NUM_ELEMS(asfiInfo); i++) {
634 asfiInfo[i].szLinkTarget[0] = '\0';
635 hr = SHGetFolderPathW(NULL, asfiInfo[i].nFolder|CSIDL_FLAG_DONT_VERIFY, NULL,
636 SHGFP_TYPE_CURRENT, wszPath);
637 if (SUCCEEDED(hr)) {
638 char *pszUnixPath = wine_get_unix_file_name(wszPath);
639 if (pszUnixPath) {
640 struct stat statPath;
641 if (!lstat(pszUnixPath, &statPath) && S_ISLNK(statPath.st_mode)) {
642 int cLen = readlink(pszUnixPath, asfiInfo[i].szLinkTarget, FILENAME_MAX-1);
643 if (cLen >= 0) asfiInfo[i].szLinkTarget[cLen] = '\0';
645 HeapFree(GetProcessHeap(), 0, pszUnixPath);
651 static void update_shell_folder_listview(HWND dialog) {
652 int i;
653 LVITEM item;
654 LONG lSelected = SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_GETNEXTITEM, (WPARAM)-1,
655 MAKELPARAM(LVNI_SELECTED,0));
657 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_DELETEALLITEMS, 0, 0);
659 for (i=0; i<NUM_ELEMS(asfiInfo); i++) {
660 char buffer[MAX_PATH];
661 HRESULT hr;
662 LPITEMIDLIST pidlCurrent;
664 /* Some acrobatic to get the localized name of the shell folder */
665 hr = SHGetFolderLocation(dialog, asfiInfo[i].nFolder, NULL, 0, &pidlCurrent);
666 if (SUCCEEDED(hr)) {
667 LPSHELLFOLDER psfParent;
668 LPCITEMIDLIST pidlLast;
669 hr = SHBindToParent(pidlCurrent, &IID_IShellFolder, (LPVOID*)&psfParent, &pidlLast);
670 if (SUCCEEDED(hr)) {
671 STRRET strRet;
672 hr = IShellFolder_GetDisplayNameOf(psfParent, pidlLast, SHGDN_FORADDRESSBAR, &strRet);
673 if (SUCCEEDED(hr)) {
674 hr = StrRetToBufA(&strRet, pidlLast, buffer, 256);
676 IShellFolder_Release(psfParent);
678 ILFree(pidlCurrent);
681 /* If there's a dangling symlink for the current shell folder, SHGetFolderLocation
682 * will fail above. We fall back to the (non-verified) path of the shell folder. */
683 if (FAILED(hr)) {
684 hr = SHGetFolderPath(dialog, asfiInfo[i].nFolder|CSIDL_FLAG_DONT_VERIFY, NULL,
685 SHGFP_TYPE_CURRENT, buffer);
688 item.mask = LVIF_TEXT | LVIF_PARAM;
689 item.iItem = i;
690 item.iSubItem = 0;
691 item.pszText = buffer;
692 item.lParam = (LPARAM)&asfiInfo[i];
693 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_INSERTITEM, 0, (LPARAM)&item);
695 item.mask = LVIF_TEXT;
696 item.iItem = i;
697 item.iSubItem = 1;
698 item.pszText = asfiInfo[i].szLinkTarget;
699 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_SETITEM, 0, (LPARAM)&item);
702 /* Ensure that the previously selected item is selected again. */
703 if (lSelected >= 0) {
704 item.mask = LVIF_STATE;
705 item.state = LVIS_SELECTED;
706 item.stateMask = LVIS_SELECTED;
707 SendDlgItemMessage(dialog, IDC_LIST_SFPATHS, LVM_SETITEMSTATE, (WPARAM)lSelected,
708 (LPARAM)&item);
712 static void on_shell_folder_selection_changed(HWND hDlg, LPNMLISTVIEW lpnm) {
713 if (lpnm->uNewState & LVIS_SELECTED) {
714 psfiSelected = (struct ShellFolderInfo *)lpnm->lParam;
715 EnableWindow(GetDlgItem(hDlg, IDC_LINK_SFPATH), 1);
716 if (strlen(psfiSelected->szLinkTarget)) {
717 CheckDlgButton(hDlg, IDC_LINK_SFPATH, BST_CHECKED);
718 EnableWindow(GetDlgItem(hDlg, IDC_EDIT_SFPATH), 1);
719 EnableWindow(GetDlgItem(hDlg, IDC_BROWSE_SFPATH), 1);
720 SetWindowText(GetDlgItem(hDlg, IDC_EDIT_SFPATH), psfiSelected->szLinkTarget);
721 } else {
722 CheckDlgButton(hDlg, IDC_LINK_SFPATH, BST_UNCHECKED);
723 EnableWindow(GetDlgItem(hDlg, IDC_EDIT_SFPATH), 0);
724 EnableWindow(GetDlgItem(hDlg, IDC_BROWSE_SFPATH), 0);
725 SetWindowText(GetDlgItem(hDlg, IDC_EDIT_SFPATH), "");
727 } else {
728 psfiSelected = NULL;
729 CheckDlgButton(hDlg, IDC_LINK_SFPATH, BST_UNCHECKED);
730 SetWindowText(GetDlgItem(hDlg, IDC_EDIT_SFPATH), "");
731 EnableWindow(GetDlgItem(hDlg, IDC_LINK_SFPATH), 0);
732 EnableWindow(GetDlgItem(hDlg, IDC_EDIT_SFPATH), 0);
733 EnableWindow(GetDlgItem(hDlg, IDC_BROWSE_SFPATH), 0);
737 /* Keep the contents of the edit control, the listview control and the symlink
738 * information in sync. */
739 static void on_shell_folder_edit_changed(HWND hDlg) {
740 LVITEM item;
741 char *text = get_text(hDlg, IDC_EDIT_SFPATH);
742 LONG iSel = SendDlgItemMessage(hDlg, IDC_LIST_SFPATHS, LVM_GETNEXTITEM, -1,
743 MAKELPARAM(LVNI_SELECTED,0));
745 if (!text || !psfiSelected || iSel < 0) {
746 HeapFree(GetProcessHeap(), 0, text);
747 return;
750 strncpy(psfiSelected->szLinkTarget, text, FILENAME_MAX);
751 HeapFree(GetProcessHeap(), 0, text);
753 item.mask = LVIF_TEXT;
754 item.iItem = iSel;
755 item.iSubItem = 1;
756 item.pszText = psfiSelected->szLinkTarget;
757 SendDlgItemMessage(hDlg, IDC_LIST_SFPATHS, LVM_SETITEM, 0, (LPARAM)&item);
759 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
762 static void apply_shell_folder_changes() {
763 WCHAR wszPath[MAX_PATH];
764 char szBackupPath[FILENAME_MAX], szUnixPath[FILENAME_MAX], *pszUnixPath = NULL;
765 int i, cUnixPathLen;
766 struct stat statPath;
767 HRESULT hr;
769 for (i=0; i<NUM_ELEMS(asfiInfo); i++) {
770 /* Ignore nonexistent link targets */
771 if (asfiInfo[i].szLinkTarget[0] && stat(asfiInfo[i].szLinkTarget, &statPath))
772 continue;
774 hr = SHGetFolderPathW(NULL, asfiInfo[i].nFolder|CSIDL_FLAG_CREATE, NULL,
775 SHGFP_TYPE_CURRENT, wszPath);
776 if (FAILED(hr)) continue;
778 /* Retrieve the corresponding unix path. */
779 pszUnixPath = wine_get_unix_file_name(wszPath);
780 if (!pszUnixPath) continue;
781 lstrcpyA(szUnixPath, pszUnixPath);
782 HeapFree(GetProcessHeap(), 0, pszUnixPath);
784 /* Derive name for folder backup. */
785 cUnixPathLen = lstrlenA(szUnixPath);
786 lstrcpyA(szBackupPath, szUnixPath);
787 lstrcatA(szBackupPath, ".winecfg");
789 if (lstat(szUnixPath, &statPath)) continue;
791 /* Move old folder/link out of the way. */
792 if (S_ISLNK(statPath.st_mode)) {
793 if (unlink(szUnixPath)) continue; /* Unable to remove link. */
794 } else {
795 if (!*asfiInfo[i].szLinkTarget) {
796 continue; /* We are done. Old was real folder, as new shall be. */
797 } else {
798 if (rename(szUnixPath, szBackupPath)) { /* Move folder out of the way. */
799 continue; /* Unable to move old folder. */
804 /* Create new link/folder. */
805 if (*asfiInfo[i].szLinkTarget) {
806 symlink(asfiInfo[i].szLinkTarget, szUnixPath);
807 } else {
808 /* If there's a backup folder, restore it. Else create new folder. */
809 if (!lstat(szBackupPath, &statPath) && S_ISDIR(statPath.st_mode)) {
810 rename(szBackupPath, szUnixPath);
811 } else {
812 mkdir(szUnixPath, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
818 INT_PTR CALLBACK
819 ThemeDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
821 switch (uMsg) {
822 case WM_INITDIALOG:
823 read_shell_folder_link_targets();
824 init_shell_folder_listview_headers(hDlg);
825 update_shell_folder_listview(hDlg);
826 break;
828 case WM_DESTROY:
829 free_theme_files();
830 break;
832 case WM_SHOWWINDOW:
833 set_window_title(hDlg);
834 break;
836 case WM_COMMAND:
837 switch(HIWORD(wParam)) {
838 case CBN_SELCHANGE: {
839 if (updating_ui) break;
840 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
841 switch (LOWORD(wParam))
843 case IDC_THEME_THEMECOMBO: on_theme_changed(hDlg); break;
844 case IDC_THEME_COLORCOMBO: /* fall through */
845 case IDC_THEME_SIZECOMBO: theme_dirty = TRUE; break;
847 break;
849 case EN_CHANGE: {
850 if (LOWORD(wParam) == IDC_EDIT_SFPATH)
851 on_shell_folder_edit_changed(hDlg);
852 break;
854 case BN_CLICKED:
855 switch (LOWORD(wParam))
857 case IDC_THEME_INSTALL:
858 on_theme_install (hDlg);
859 break;
861 case IDC_BROWSE_SFPATH:
862 if (browse_for_unix_folder(hDlg, psfiSelected->szLinkTarget)) {
863 update_shell_folder_listview(hDlg);
864 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
866 break;
868 case IDC_LINK_SFPATH:
869 if (IsDlgButtonChecked(hDlg, IDC_LINK_SFPATH)) {
870 if (browse_for_unix_folder(hDlg, psfiSelected->szLinkTarget)) {
871 update_shell_folder_listview(hDlg);
872 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
873 } else {
874 CheckDlgButton(hDlg, IDC_LINK_SFPATH, BST_UNCHECKED);
876 } else {
877 psfiSelected->szLinkTarget[0] = '\0';
878 update_shell_folder_listview(hDlg);
879 SendMessage(GetParent(hDlg), PSM_CHANGED, 0, 0);
881 break;
883 break;
885 break;
887 case WM_NOTIFY:
888 switch (((LPNMHDR)lParam)->code) {
889 case PSN_KILLACTIVE: {
890 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
891 break;
893 case PSN_APPLY: {
894 apply();
895 apply_theme(hDlg);
896 apply_shell_folder_changes();
897 read_shell_folder_link_targets();
898 update_shell_folder_listview(hDlg);
899 SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
900 break;
902 case LVN_ITEMCHANGED: {
903 if (wParam == IDC_LIST_SFPATHS)
904 on_shell_folder_selection_changed(hDlg, (LPNMLISTVIEW)lParam);
905 break;
907 case PSN_SETACTIVE: {
908 init_dialog (hDlg);
909 break;
912 break;
914 default:
915 break;
917 return FALSE;