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
38 #include <wine/debug.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
,
58 LPCWSTR pszToolTip
, LPVOID lpReserved2
,
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
,
69 HRESULT WINAPI
CloseThemeFile (HTHEMEFILE hThemeFile
);
70 HRESULT WINAPI
EnumThemes (LPCWSTR pszThemePath
, EnumThemeProc callback
,
73 /* A struct to keep both the internal and "fancy" name of a color or size */
80 /* wrapper around DSA that also keeps an item count */
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
);
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
);
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);
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
)
133 for (; i
< wdsa
->count
; i
++)
135 ThemeColorOrSize
* item
= color_or_size_dsa_get (wdsa
, i
);
136 if (lstrcmpiW (item
->name
, name
) == 0) break;
141 /* A theme file, contains file name, display name, color and size scheme names */
144 WCHAR
* themeFileName
;
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
);
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
);
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
,
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
,
198 LPVOID lpReserved2
, LPVOID lpData
)
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
);
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
];
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
,
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
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];
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. */
306 for (i
= 0; i
< themeFilesCount
; i
++)
308 theme
= (ThemeFile
*)DSA_GetItemPtr (themeFiles
, i
);
309 if (lstrcmpiW (theme
->themeFileName
, currentTheme
) == 0)
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
,
327 fill_color_size_combos (theme
, comboColor
, comboSize
);
328 select_color_and_size (theme
, currentColor
, comboColor
,
329 currentSize
, comboSize
);
333 /* No theme selected */
337 SendMessageW (comboTheme
, CB_SETCURSEL
, themeIndex
, 0);
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
,
353 WCHAR currentTheme
[MAX_PATH
];
354 WCHAR currentColor
[MAX_PATH
];
355 WCHAR currentSize
[MAX_PATH
];
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
);
372 SendMessageW (comboColor
, CB_SETCURSEL
, 0, 0);
373 SendMessageW (comboSize
, CB_SETCURSEL
, 0, 0);
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";
387 ApplyTheme (NULL
, b
, NULL
);
392 (ThemeFile
*)DSA_GetItemPtr (themeFiles
, themeIndex
-1);
393 const WCHAR
* themeFileName
= theme
->themeFileName
;
394 const WCHAR
* colorName
= NULL
;
395 const WCHAR
* sizeName
= NULL
;
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
,
408 ApplyTheme (hTheme
, b
, NULL
);
409 CloseThemeFile (hTheme
);
413 ApplyTheme (NULL
, b
, NULL
);
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
)
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
);
444 enable_size_and_color_controls (dialog
, TRUE
);
451 static void on_theme_changed(HWND dialog
) {
452 int index
= SendMessageW (GetDlgItem (dialog
, IDC_THEME_THEMECOMBO
),
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
);
463 enable_size_and_color_controls (dialog
, 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
),
476 colorIndex
= SendMessageW (GetDlgItem (dialog
, IDC_THEME_COLORCOMBO
),
478 sizeIndex
= SendMessageW (GetDlgItem (dialog
, IDC_THEME_SIZECOMBO
),
481 do_apply_theme (themeIndex
, colorIndex
, sizeIndex
);
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]);
490 WCHAR filetitle
[MAX_PATH
];
491 WCHAR file
[MAX_PATH
];
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
);
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
;
519 ofn
.nFileExtension
= 0;
520 ofn
.lpstrDefExt
= 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;
553 WINE_TRACE("copying: %s -> %s\n", wine_dbgstr_w (file
),
554 wine_dbgstr_w (themeFilePath
));
556 shfop
.wFunc
= FO_COPY
;
558 shfop
.pTo
= themeFilePath
;
559 shfop
.fFlags
= FOF_NOCONFIRMMKDIR
;
560 if (SHFileOperationW (&shfop
) == 0)
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
);
573 enable_size_and_color_controls (dialog
, TRUE
);
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
{
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
) {
603 char szShellFolder
[64] = "Shell Folder";
604 char szLinksTo
[64] = "Links to";
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
];
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
);
638 char *pszUnixPath
= wine_get_unix_file_name(wszPath
);
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
) {
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
];
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
);
667 LPSHELLFOLDER psfParent
;
668 LPCITEMIDLIST pidlLast
;
669 hr
= SHBindToParent(pidlCurrent
, &IID_IShellFolder
, (LPVOID
*)&psfParent
, &pidlLast
);
672 hr
= IShellFolder_GetDisplayNameOf(psfParent
, pidlLast
, SHGDN_FORADDRESSBAR
, &strRet
);
674 hr
= StrRetToBufA(&strRet
, pidlLast
, buffer
, 256);
676 IShellFolder_Release(psfParent
);
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. */
684 hr
= SHGetFolderPath(dialog
, asfiInfo
[i
].nFolder
|CSIDL_FLAG_DONT_VERIFY
, NULL
,
685 SHGFP_TYPE_CURRENT
, buffer
);
688 item
.mask
= LVIF_TEXT
| LVIF_PARAM
;
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
;
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
,
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
);
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
), "");
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
) {
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
);
750 strncpy(psfiSelected
->szLinkTarget
, text
, FILENAME_MAX
);
751 HeapFree(GetProcessHeap(), 0, text
);
753 item
.mask
= LVIF_TEXT
;
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
;
766 struct stat statPath
;
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
))
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. */
795 if (!*asfiInfo
[i
].szLinkTarget
) {
796 continue; /* We are done. Old was real folder, as new shall be. */
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
);
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
);
812 mkdir(szUnixPath
, S_IRWXU
|S_IRGRP
|S_IXGRP
|S_IROTH
|S_IXOTH
);
819 ThemeDlgProc (HWND hDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
823 read_shell_folder_link_targets();
824 init_shell_folder_listview_headers(hDlg
);
825 update_shell_folder_listview(hDlg
);
833 set_window_title(hDlg
);
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;
850 if (LOWORD(wParam
) == IDC_EDIT_SFPATH
)
851 on_shell_folder_edit_changed(hDlg
);
855 switch (LOWORD(wParam
))
857 case IDC_THEME_INSTALL
:
858 on_theme_install (hDlg
);
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);
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);
874 CheckDlgButton(hDlg
, IDC_LINK_SFPATH
, BST_UNCHECKED
);
877 psfiSelected
->szLinkTarget
[0] = '\0';
878 update_shell_folder_listview(hDlg
);
879 SendMessage(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
888 switch (((LPNMHDR
)lParam
)->code
) {
889 case PSN_KILLACTIVE
: {
890 SetWindowLongPtr(hDlg
, DWLP_MSGRESULT
, FALSE
);
896 apply_shell_folder_changes();
897 read_shell_folder_link_targets();
898 update_shell_folder_listview(hDlg
);
899 SetWindowLongPtr(hDlg
, DWLP_MSGRESULT
, PSNRET_NOERROR
);
902 case LVN_ITEMCHANGED
: {
903 if (wParam
== IDC_LIST_SFPATHS
)
904 on_shell_folder_selection_changed(hDlg
, (LPNMLISTVIEW
)lParam
);
907 case PSN_SETACTIVE
: {