Reactos
1/*
2 * Provides default file shell extension
3 *
4 * Copyright 2005 Johannes Anderwald
5 * Copyright 2012 Rafal Harabien
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22#include "precomp.h"
23
24#define NTOS_MODE_USER
25#include <ndk/iofuncs.h>
26#include <ndk/obfuncs.h>
27
28WINE_DEFAULT_DEBUG_CHANNEL(shell);
29
30EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
31
32static BOOL SH32_GetFileFindData(PCWSTR pszPath, WIN32_FIND_DATAW *pWFD)
33{
34 HANDLE hFind = FindFirstFileW(pszPath, pWFD);
35 return hFind != INVALID_HANDLE_VALUE ? FindClose(hFind) : FALSE;
36}
37
38BOOL GetPhysicalFileSize(LPCWSTR pszPath, PULARGE_INTEGER Size)
39{
40 HANDLE hFile = CreateFileW(pszPath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ |
41 FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
42 FILE_FLAG_OPEN_NO_RECALL, NULL);
43 if (hFile == INVALID_HANDLE_VALUE)
44 {
45 ERR("Failed to open file for GetPhysicalFileSize\n");
46 return FALSE;
47 }
48
49 IO_STATUS_BLOCK IoStatusBlock;
50 FILE_STANDARD_INFORMATION FileInfo;
51 NTSTATUS Status = NtQueryInformationFile(hFile, &IoStatusBlock, &FileInfo,
52 sizeof(FileInfo), FileStandardInformation);
53 if (NT_SUCCESS(Status))
54 {
55 Size->QuadPart = FileInfo.AllocationSize.QuadPart;
56 }
57 else
58 {
59 ERR("NtQueryInformationFile failed for %S (Status: %08lX)\n", pszPath, Status);
60 }
61 CloseHandle(hFile);
62 return NT_SUCCESS(Status);
63}
64
65static UINT
66GetFileStats(HANDLE hFile, PULARGE_INTEGER pVirtSize, PULARGE_INTEGER pPhysSize)
67{
68 NTSTATUS Status;
69 IO_STATUS_BLOCK IoStatusBlock;
70 FILE_BASIC_INFORMATION BasicInfo;
71 Status = NtQueryInformationFile(hFile, &IoStatusBlock, &BasicInfo, sizeof(BasicInfo), FileBasicInformation);
72 if (!NT_SUCCESS(Status))
73 return INVALID_FILE_ATTRIBUTES;
74
75 if ((pVirtSize || pPhysSize) && !(BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
76 {
77 FILE_STANDARD_INFORMATION FileInfo;
78 Status = NtQueryInformationFile(hFile, &IoStatusBlock, &FileInfo,
79 sizeof(FileInfo), FileStandardInformation);
80 if (!NT_SUCCESS(Status))
81 return INVALID_FILE_ATTRIBUTES;
82 if (pVirtSize)
83 pVirtSize->QuadPart = FileInfo.EndOfFile.QuadPart;
84 if (pPhysSize)
85 pPhysSize->QuadPart = FileInfo.AllocationSize.QuadPart;
86 }
87 return BasicInfo.FileAttributes;
88}
89
90static UINT
91GetFileStats(PCWSTR pszPath, PULARGE_INTEGER pVirtSize, PULARGE_INTEGER pPhysSize)
92{
93 HANDLE hFile = CreateFileW(pszPath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ |
94 FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
95 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_NO_RECALL, NULL);
96 if (hFile != INVALID_HANDLE_VALUE)
97 {
98 UINT Attrib = GetFileStats(hFile, pVirtSize, pPhysSize);
99 CloseHandle(hFile);
100 return Attrib;
101 }
102
103 WIN32_FIND_DATAW Data;
104 if (!SH32_GetFileFindData(pszPath, &Data))
105 return INVALID_FILE_ATTRIBUTES;
106 if (pVirtSize)
107 {
108 pVirtSize->u.LowPart = Data.nFileSizeLow;
109 pVirtSize->u.HighPart = Data.nFileSizeHigh;
110 }
111 if (pPhysSize)
112 {
113 pPhysSize->u.LowPart = Data.nFileSizeLow;
114 pPhysSize->u.HighPart = Data.nFileSizeHigh;
115 // TODO: Should we round up to cluster size?
116 }
117 return Data.dwFileAttributes;
118}
119
120BOOL CFileVersionInfo::Load(LPCWSTR pwszPath)
121{
122 ULONG cbInfo = GetFileVersionInfoSizeW(pwszPath, NULL);
123 if (!cbInfo)
124 {
125 WARN("GetFileVersionInfoSize %ls failed\n", pwszPath);
126 return FALSE;
127 }
128
129 m_pInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbInfo);
130 if (!m_pInfo)
131 {
132 ERR("HeapAlloc failed bytes %x\n", cbInfo);
133 return FALSE;
134 }
135
136 if (!GetFileVersionInfoW(pwszPath, 0, cbInfo, m_pInfo))
137 {
138 ERR("GetFileVersionInfoW failed\n");
139 return FALSE;
140 }
141
142 LPLANGANDCODEPAGE lpLangCode;
143 UINT cBytes;
144 if (!VerQueryValueW(m_pInfo, L"\\VarFileInfo\\Translation", (LPVOID *)&lpLangCode, &cBytes) || cBytes < sizeof(LANGANDCODEPAGE))
145 {
146 ERR("VerQueryValueW failed\n");
147 return FALSE;
148 }
149
150 /* FIXME: find language from current locale / if not available,
151 * default to english
152 * for now default to first available language
153 */
154 m_wLang = lpLangCode->wLang;
155 m_wCode = lpLangCode->wCode;
156 TRACE("Lang %hx Code %hu\n", m_wLang, m_wCode);
157
158 return TRUE;
159}
160
161LPCWSTR CFileVersionInfo::GetString(LPCWSTR pwszName)
162{
163 if (!m_pInfo)
164 return NULL;
165
166 WCHAR wszBuf[256];
167 swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\%s", m_wLang, m_wCode, pwszName);
168
169 /* Query string in version block */
170 LPCWSTR pwszResult = NULL;
171 UINT cBytes = 0;
172 if (!VerQueryValueW(m_pInfo, wszBuf, (LPVOID *)&pwszResult, &cBytes))
173 pwszResult = NULL;
174
175 if (!pwszResult)
176 {
177 /* Try US English */
178 swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\%s", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 1252, pwszName);
179 if (!VerQueryValueW(m_pInfo, wszBuf, (LPVOID *)&pwszResult, &cBytes))
180 pwszResult = NULL;
181 }
182
183 if (!pwszResult)
184 ERR("VerQueryValueW %ls failed\n", pwszName);
185 else
186 TRACE("%ls: %ls\n", pwszName, pwszResult);
187
188 return pwszResult;
189}
190
191VS_FIXEDFILEINFO *CFileVersionInfo::GetFixedInfo()
192{
193 if (!m_pInfo)
194 return NULL;
195
196 VS_FIXEDFILEINFO *pInfo;
197 UINT cBytes;
198 if (!VerQueryValueW(m_pInfo, L"\\", (PVOID*)&pInfo, &cBytes))
199 return NULL;
200 return pInfo;
201}
202
203LPCWSTR CFileVersionInfo::GetLangName()
204{
205 if (!m_pInfo)
206 return NULL;
207
208 if (!m_wszLang[0])
209 {
210 if (!VerLanguageNameW(m_wLang, m_wszLang, _countof(m_wszLang)))
211 ERR("VerLanguageNameW failed\n");
212 }
213
214 return m_wszLang;
215}
216
217UINT
218SH_FormatInteger(LONGLONG Num, LPWSTR pwszResult, UINT cchResultMax)
219{
220 // Print the number in uniform mode
221 WCHAR wszNumber[24];
222 swprintf(wszNumber, L"%I64u", Num);
223
224 // Get system strings for decimal and thousand separators.
225 WCHAR wszDecimalSep[8], wszThousandSep[8];
226 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, wszDecimalSep, _countof(wszDecimalSep));
227 GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, wszThousandSep, _countof(wszThousandSep));
228
229 // Initialize format for printing the number in bytes
230 NUMBERFMTW nf;
231 ZeroMemory(&nf, sizeof(nf));
232 nf.lpDecimalSep = wszDecimalSep;
233 nf.lpThousandSep = wszThousandSep;
234
235 // Get system string for groups separator
236 WCHAR wszGrouping[12];
237 INT cchGrouping = GetLocaleInfoW(LOCALE_USER_DEFAULT,
238 LOCALE_SGROUPING,
239 wszGrouping,
240 _countof(wszGrouping));
241
242 // Convert grouping specs from string to integer
243 for (INT i = 0; i < cchGrouping; i++)
244 {
245 WCHAR wch = wszGrouping[i];
246
247 if (wch >= L'0' && wch <= L'9')
248 nf.Grouping = nf.Grouping * 10 + (wch - L'0');
249 else if (wch != L';')
250 break;
251 }
252
253 if ((nf.Grouping % 10) == 0)
254 nf.Grouping /= 10;
255 else
256 nf.Grouping *= 10;
257
258 // Format the number
259 INT cchResult = GetNumberFormatW(LOCALE_USER_DEFAULT,
260 0,
261 wszNumber,
262 &nf,
263 pwszResult,
264 cchResultMax);
265
266 if (!cchResult)
267 return 0;
268
269 // GetNumberFormatW returns number of characters including UNICODE_NULL
270 return cchResult - 1;
271}
272
273UINT
274SH_FormatByteSize(LONGLONG cbSize, LPWSTR pwszResult, UINT cchResultMax)
275{
276 /* Write formated bytes count */
277 INT cchWritten = SH_FormatInteger(cbSize, pwszResult, cchResultMax);
278 if (!cchWritten)
279 return 0;
280
281 /* Copy " bytes" to buffer */
282 LPWSTR pwszEnd = pwszResult + cchWritten;
283 size_t cchRemaining = cchResultMax - cchWritten;
284 StringCchCopyExW(pwszEnd, cchRemaining, L" ", &pwszEnd, &cchRemaining, 0);
285 cchWritten = LoadStringW(shell32_hInstance, IDS_BYTES_FORMAT, pwszEnd, cchRemaining);
286 cchRemaining -= cchWritten;
287
288 return cchResultMax - cchRemaining;
289}
290
291/*************************************************************************
292 *
293 * SH_FormatFileSizeWithBytes
294 *
295 * Format a size in bytes to string.
296 *
297 * lpQwSize = Pointer to 64bit large integer to format
298 * pszBuf = Buffer to fill with output string
299 * cchBuf = size of pszBuf in characters
300 *
301 */
302
303LPWSTR
304SH_FormatFileSizeWithBytes(const PULARGE_INTEGER lpQwSize, LPWSTR pwszResult, UINT cchResultMax)
305{
306 /* Format bytes in KBs, MBs etc */
307 if (StrFormatByteSizeW(lpQwSize->QuadPart, pwszResult, cchResultMax) == NULL)
308 return NULL;
309
310 /* If there is less bytes than 1KB, we have nothing to do */
311 if (lpQwSize->QuadPart < 1024)
312 return pwszResult;
313
314 /* Concatenate " (" */
315 UINT cchWritten = wcslen(pwszResult);
316 LPWSTR pwszEnd = pwszResult + cchWritten;
317 size_t cchRemaining = cchResultMax - cchWritten;
318 StringCchCopyExW(pwszEnd, cchRemaining, L" (", &pwszEnd, &cchRemaining, 0);
319
320 /* Write formated bytes count */
321 cchWritten = SH_FormatByteSize(lpQwSize->QuadPart, pwszEnd, cchRemaining);
322 pwszEnd += cchWritten;
323 cchRemaining -= cchWritten;
324
325 /* Copy ")" to the buffer */
326 StringCchCopyW(pwszEnd, cchRemaining, L")");
327
328 return pwszResult;
329}
330
331VOID
332CFileDefExt::InitOpensWithField(HWND hwndDlg)
333{
334 WCHAR wszBuf[MAX_PATH] = L"";
335 WCHAR wszPath[MAX_PATH] = L"";
336 DWORD dwSize = sizeof(wszBuf);
337 BOOL bUnknownApp = TRUE;
338 LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
339
340 // TODO: Use ASSOCSTR_EXECUTABLE with ASSOCF_REMAPRUNDLL | ASSOCF_IGNOREBASECLASS
341 if (RegGetValueW(HKEY_CLASSES_ROOT, pwszExt, L"", RRF_RT_REG_SZ, NULL, wszBuf, &dwSize) == ERROR_SUCCESS)
342 {
343 bUnknownApp = FALSE;
344 StringCbCatW(wszBuf, sizeof(wszBuf), L"\\shell\\open\\command");
345 dwSize = sizeof(wszPath);
346 // FIXME: Missing FileExt check, see COpenWithList::SetDefaultHandler for details
347 // FIXME: Use HCR_GetDefaultVerbW to find the default verb
348 if (RegGetValueW(HKEY_CLASSES_ROOT, wszBuf, L"", RRF_RT_REG_SZ, NULL, wszPath, &dwSize) == ERROR_SUCCESS)
349 {
350 /* Get path from command line */
351 ExpandEnvironmentStringsW(wszPath, wszBuf, _countof(wszBuf));
352 if (SHELL32_GetDllFromRundll32CommandLine(wszBuf, wszBuf, _countof(wszBuf)) != S_OK)
353 {
354 PathRemoveArgs(wszBuf);
355 PathUnquoteSpacesW(wszBuf);
356 }
357 PathSearchAndQualify(wszBuf, wszPath, _countof(wszPath));
358
359 HICON hIcon;
360 if (ExtractIconExW(wszPath, 0, NULL, &hIcon, 1))
361 {
362 HWND hIconCtrl = GetDlgItem(hwndDlg, 14025);
363 HWND hDescrCtrl = GetDlgItem(hwndDlg, 14007);
364 ShowWindow(hIconCtrl, SW_SHOW);
365 RECT rcIcon, rcDescr;
366 GetWindowRect(hIconCtrl, &rcIcon);
367
368 rcIcon.right = rcIcon.left + GetSystemMetrics(SM_CXSMICON);
369 rcIcon.bottom = rcIcon.top + GetSystemMetrics(SM_CYSMICON);
370
371 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rcIcon, 2);
372 GetWindowRect(hDescrCtrl, &rcDescr);
373 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rcDescr, 2);
374 INT cxOffset = rcIcon.right + 2 - rcDescr.left;
375 SetWindowPos(hDescrCtrl, NULL,
376 rcDescr.left + cxOffset, rcDescr.top,
377 rcDescr.right - rcDescr.left - cxOffset, rcDescr.bottom - rcDescr.top,
378 SWP_NOZORDER);
379 SendMessageW(hIconCtrl, STM_SETICON, (WPARAM)hIcon, 0);
380 } else
381 ERR("Failed to extract icon\n");
382
383 if (PathFileExistsW(wszPath))
384 {
385 /* Get file description */
386 CFileVersionInfo VerInfo;
387 VerInfo.Load(wszPath);
388 LPCWSTR pwszDescr = VerInfo.GetString(L"FileDescription");
389 if (pwszDescr)
390 SetDlgItemTextW(hwndDlg, 14007, pwszDescr);
391 else
392 {
393 /* File has no description - display filename */
394 LPWSTR pwszFilename = PathFindFileNameW(wszPath);
395 PathRemoveExtension(pwszFilename);
396 pwszFilename[0] = towupper(pwszFilename[0]);
397 SetDlgItemTextW(hwndDlg, 14007, pwszFilename);
398 }
399 }
400 else
401 bUnknownApp = TRUE;
402 } else
403 WARN("RegGetValueW %ls failed\n", wszBuf);
404 } else
405 WARN("RegGetValueW %ls failed\n", pwszExt);
406
407 if (bUnknownApp)
408 {
409 /* Unknown application */
410 LoadStringW(shell32_hInstance, IDS_UNKNOWN_APP, wszBuf, _countof(wszBuf));
411 SetDlgItemTextW(hwndDlg, 14007, wszBuf);
412 }
413}
414
415/*************************************************************************
416 *
417 * SH_FileGeneralFileType [Internal]
418 *
419 * retrieves file extension description from registry and sets it in dialog
420 *
421 * TODO: retrieve file extension default icon and load it
422 * find executable name from registry, retrieve description from executable
423 */
424
425BOOL
426CFileDefExt::InitFileType(HWND hwndDlg)
427{
428 TRACE("path %s\n", debugstr_w(m_wszPath));
429
430 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14005);
431 if (hDlgCtrl == NULL)
432 return FALSE;
433
434 /* Get file information */
435 SHFILEINFOW fi;
436 if (!SHGetFileInfoW(m_wszPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME|SHGFI_ICON))
437 {
438 ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_wszPath, GetLastError());
439 fi.szTypeName[0] = L'\0';
440 fi.hIcon = NULL;
441 }
442
443 LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
444 if (pwszExt[0])
445 {
446 WCHAR wszBuf[256];
447
448 if (!fi.szTypeName[0])
449 {
450 /* The file type is unknown, so default to string "FileExtension File" */
451 size_t cchRemaining = 0;
452 LPWSTR pwszEnd = NULL;
453
454 StringCchPrintfExW(wszBuf, _countof(wszBuf), &pwszEnd, &cchRemaining, 0, L"%s ", pwszExt + 1);
455 SendMessageW(hDlgCtrl, WM_GETTEXT, (WPARAM)cchRemaining, (LPARAM)pwszEnd);
456
457 SendMessageW(hDlgCtrl, WM_SETTEXT, (WPARAM)NULL, (LPARAM)wszBuf);
458 }
459 else
460 {
461 /* Update file type */
462 StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s (%s)", fi.szTypeName, pwszExt);
463 SendMessageW(hDlgCtrl, WM_SETTEXT, (WPARAM)NULL, (LPARAM)wszBuf);
464 }
465 }
466
467 /* Update file icon */
468 if (fi.hIcon)
469 SendDlgItemMessageW(hwndDlg, 14000, STM_SETICON, (WPARAM)fi.hIcon, 0);
470 else
471 ERR("No icon %ls\n", m_wszPath);
472
473 return TRUE;
474}
475
476/*************************************************************************
477 *
478 * CFileDefExt::InitFilePath [Internal]
479 *
480 * sets file path string and filename string
481 *
482 */
483
484BOOL
485CFileDefExt::InitFilePath(HWND hwndDlg)
486{
487 /* Find the filename */
488 WCHAR *pwszFilename = PathFindFileNameW(m_wszPath);
489
490 if (pwszFilename > m_wszPath)
491 {
492 /* Location field */
493 WCHAR wszLocation[MAX_PATH];
494 StringCchCopyNW(wszLocation, _countof(wszLocation), m_wszPath, pwszFilename - m_wszPath);
495 PathRemoveBackslashW(wszLocation);
496
497 SetDlgItemTextW(hwndDlg, 14009, wszLocation);
498 }
499
500 /* Filename field */
501 SetDlgItemTextW(hwndDlg, 14001, pwszFilename);
502
503 return TRUE;
504}
505
506/*************************************************************************
507 *
508 * CFileDefExt::GetFileTimeString [Internal]
509 *
510 * formats a given LPFILETIME struct into readable user format
511 */
512
513BOOL
514CFileDefExt::GetFileTimeString(LPFILETIME lpFileTime, LPWSTR pwszResult, UINT cchResult)
515{
516 FILETIME ft;
517 SYSTEMTIME st;
518
519 if (!FileTimeToLocalFileTime(lpFileTime, &ft) || !FileTimeToSystemTime(&ft, &st))
520 return FALSE;
521
522 size_t cchRemaining = cchResult;
523 LPWSTR pwszEnd = pwszResult;
524 int cchWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, pwszEnd, cchRemaining);
525 if (cchWritten)
526 --cchWritten; // GetDateFormatW returns count with terminating zero
527 else
528 ERR("GetDateFormatW failed\n");
529 cchRemaining -= cchWritten;
530 pwszEnd += cchWritten;
531
532 StringCchCopyExW(pwszEnd, cchRemaining, L", ", &pwszEnd, &cchRemaining, 0);
533
534 cchWritten = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, pwszEnd, cchRemaining);
535 if (cchWritten)
536 --cchWritten; // GetTimeFormatW returns count with terminating zero
537 else
538 ERR("GetTimeFormatW failed\n");
539 TRACE("result %s\n", debugstr_w(pwszResult));
540 return TRUE;
541}
542
543/*************************************************************************
544 *
545 * CFileDefExt::InitFileAttr [Internal]
546 *
547 * retrieves file information from file and sets in dialog
548 *
549 */
550
551BOOL
552CFileDefExt::InitFileAttr(HWND hwndDlg)
553{
554 BOOL Success;
555 WIN32_FIND_DATAW FileInfo; // WIN32_FILE_ATTRIBUTE_DATA
556 WCHAR wszBuf[MAX_PATH];
557
558 TRACE("InitFileAttr %ls\n", m_wszPath);
559
560 /*
561 * There are situations where GetFileAttributes(Ex) can fail even if the
562 * specified path represents a file. This happens when e.g. the file is a
563 * locked system file, such as C:\pagefile.sys . In this case, the function
564 * returns INVALID_FILE_ATTRIBUTES and GetLastError returns ERROR_SHARING_VIOLATION.
565 * (this would allow us to distinguish between this failure and a failure
566 * due to the fact that the path actually refers to a directory).
567 *
568 * Because we really want to retrieve the file attributes/size/date&time,
569 * we do the following trick:
570 * - First we call GetFileAttributesEx. If it succeeds we know we have
571 * a file or a directory, and we have retrieved its attributes.
572 * - If GetFileAttributesEx fails, we call FindFirstFile on the full path.
573 * While we could have called FindFirstFile at first and skip GetFileAttributesEx
574 * altogether, we do it after GetFileAttributesEx because it performs more
575 * work to retrieve the file attributes. However it actually works even
576 * for locked system files.
577 * - If FindFirstFile succeeds we have retrieved its attributes.
578 * - Otherwise (FindFirstFile has failed), we do not retrieve anything.
579 *
580 * The following code also relies on the fact that the first 6 members
581 * of WIN32_FIND_DATA are *exactly* the same as the WIN32_FILE_ATTRIBUTE_DATA
582 * structure. Therefore it is safe to use a single WIN32_FIND_DATA
583 * structure for both the GetFileAttributesEx and FindFirstFile calls.
584 */
585
586 Success = GetFileAttributesExW(m_wszPath, GetFileExInfoStandard,
587 (LPWIN32_FILE_ATTRIBUTE_DATA)&FileInfo);
588 if (Success || (Success = SH32_GetFileFindData(m_wszPath, &FileInfo)) != FALSE)
589 {
590 /* Update attribute checkboxes */
591 if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
592 SendDlgItemMessage(hwndDlg, 14021, BM_SETCHECK, BST_CHECKED, 0);
593 if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
594 SendDlgItemMessage(hwndDlg, 14022, BM_SETCHECK, BST_CHECKED, 0);
595 if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
596 SendDlgItemMessage(hwndDlg, 14023, BM_SETCHECK, BST_CHECKED, 0);
597
598 /* Update creation time */
599 if (GetFileTimeString(&FileInfo.ftCreationTime, wszBuf, _countof(wszBuf)))
600 SetDlgItemTextW(hwndDlg, 14015, wszBuf);
601
602 /* For files display last access and last write time */
603 if (!m_bDir)
604 {
605 if (GetFileTimeString(&FileInfo.ftLastAccessTime, wszBuf, _countof(wszBuf)))
606 SetDlgItemTextW(hwndDlg, 14019, wszBuf);
607
608 if (GetFileTimeString(&FileInfo.ftLastWriteTime, wszBuf, _countof(wszBuf)))
609 SetDlgItemTextW(hwndDlg, 14017, wszBuf);
610
611 /* Update size of file */
612 ULARGE_INTEGER FileSize;
613 FileSize.u.LowPart = FileInfo.nFileSizeLow;
614 FileSize.u.HighPart = FileInfo.nFileSizeHigh;
615 if (SH_FormatFileSizeWithBytes(&FileSize, wszBuf, _countof(wszBuf)))
616 {
617 SetDlgItemTextW(hwndDlg, 14011, wszBuf);
618
619 // Compute file on disk. If fails, use logical size
620 if (GetPhysicalFileSize(m_wszPath, &FileSize))
621 SH_FormatFileSizeWithBytes(&FileSize, wszBuf, _countof(wszBuf));
622 else
623 ERR("Unreliable size on disk\n");
624
625 SetDlgItemTextW(hwndDlg, 14012, wszBuf);
626 }
627 }
628 }
629
630 if (m_bDir)
631 {
632 // For directories, files have to be counted
633 m_hWndDirStatsDlg = hwndDlg;
634 AddRef();
635 if (!SHCreateThread(_CountFolderAndFilesThreadProc, this, 0, NULL))
636 Release();
637 }
638
639 /* Hide Advanced button. TODO: Implement advanced dialog and enable this button if filesystem supports compression or encryption */
640 ShowWindow(GetDlgItem(hwndDlg, 14028), SW_HIDE);
641
642 return TRUE;
643}
644
645/*************************************************************************
646 *
647 * CFileDefExt::InitGeneralPage [Internal]
648 *
649 * sets all file general properties in dialog
650 */
651
652BOOL
653CFileDefExt::InitGeneralPage(HWND hwndDlg)
654{
655 /* Set general text properties filename filelocation and icon */
656 InitFilePath(hwndDlg);
657
658 /* Set file type and icon */
659 InitFileType(hwndDlg);
660
661 /* Set open with application */
662 if (!m_bDir)
663 {
664 if (!PathIsExeW(m_wszPath))
665 InitOpensWithField(hwndDlg);
666 else
667 {
668 WCHAR wszBuf[MAX_PATH];
669 LoadStringW(shell32_hInstance, IDS_EXE_DESCRIPTION, wszBuf, _countof(wszBuf));
670 SetDlgItemTextW(hwndDlg, 14006, wszBuf);
671 ShowWindow(GetDlgItem(hwndDlg, 14024), SW_HIDE);
672
673 /* hidden button 14024 allows to draw edit 14007 larger than defined in resources , we use edit 14009 as idol */
674 RECT rectIdol, rectToAdjust;
675 GetClientRect(GetDlgItem(hwndDlg, 14009), &rectIdol);
676 GetClientRect(GetDlgItem(hwndDlg, 14007), &rectToAdjust);
677 SetWindowPos(GetDlgItem(hwndDlg, 14007), HWND_TOP, 0, 0,
678 rectIdol.right-rectIdol.left /* make it as wide as its idol */,
679 rectToAdjust.bottom-rectToAdjust.top /* but keep its current height */,
680 SWP_NOMOVE | SWP_NOZORDER );
681
682 LPCWSTR pwszDescr = m_VerInfo.GetString(L"FileDescription");
683 if (pwszDescr)
684 SetDlgItemTextW(hwndDlg, 14007, pwszDescr);
685 else
686 {
687 StringCbCopyW(wszBuf, sizeof(wszBuf), PathFindFileNameW(m_wszPath));
688 PathRemoveExtension(wszBuf);
689 SetDlgItemTextW(hwndDlg, 14007, wszBuf);
690 }
691 }
692 }
693
694 /* Set file created/modfied/accessed time, size and attributes */
695 InitFileAttr(hwndDlg);
696
697 return TRUE;
698}
699
700/*************************************************************************
701 *
702 * CFileDefExt::GeneralPageProc
703 *
704 * wnd proc of 'General' property sheet page
705 *
706 */
707
708INT_PTR CALLBACK
709CFileDefExt::GeneralPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
710{
711 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
712 switch (uMsg)
713 {
714 case WM_INITDIALOG:
715 {
716 LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam;
717
718 if (ppsp == NULL || !ppsp->lParam)
719 break;
720
721 TRACE("WM_INITDIALOG hwnd %p lParam %p ppsplParam %S\n", hwndDlg, lParam, ppsp->lParam);
722
723 pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
724 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pFileDefExt);
725 pFileDefExt->InitGeneralPage(hwndDlg);
726 break;
727 }
728 case WM_COMMAND:
729 if (LOWORD(wParam) == 14024) /* Opens With - Change */
730 {
731 OPENASINFO oainfo;
732 oainfo.pcszFile = pFileDefExt->m_wszPath;
733 oainfo.pcszClass = NULL;
734 oainfo.oaifInFlags = OAIF_REGISTER_EXT | OAIF_FORCE_REGISTRATION;
735 if (SHOpenWithDialog(hwndDlg, &oainfo) == S_OK)
736 {
737 pFileDefExt->InitGeneralPage(hwndDlg);
738 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
739 }
740 break;
741 }
742 else if (LOWORD(wParam) == 14021 || LOWORD(wParam) == 14022 || LOWORD(wParam) == 14023) /* checkboxes */
743 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
744 else if (LOWORD(wParam) == 14001) /* Name */
745 {
746 if (HIWORD(wParam) == EN_CHANGE)
747 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
748 }
749 break;
750 case WM_NOTIFY:
751 {
752 LPPSHNOTIFY lppsn = (LPPSHNOTIFY)lParam;
753 if (lppsn->hdr.code == PSN_APPLY)
754 {
755 /* Update attributes first */
756 DWORD dwAttr = GetFileAttributesW(pFileDefExt->m_wszPath);
757 if (dwAttr)
758 {
759 dwAttr &= ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_ARCHIVE);
760
761 if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14021, BM_GETCHECK, 0, 0))
762 dwAttr |= FILE_ATTRIBUTE_READONLY;
763 if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14022, BM_GETCHECK, 0, 0))
764 dwAttr |= FILE_ATTRIBUTE_HIDDEN;
765 if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14023, BM_GETCHECK, 0, 0))
766 dwAttr |= FILE_ATTRIBUTE_ARCHIVE;
767
768 if (!SetFileAttributesW(pFileDefExt->m_wszPath, dwAttr))
769 ERR("SetFileAttributesW failed\n");
770 }
771
772 /* Update filename now */
773 WCHAR wszBuf[MAX_PATH];
774 StringCchCopyW(wszBuf, _countof(wszBuf), pFileDefExt->m_wszPath);
775 LPWSTR pwszFilename = PathFindFileNameW(wszBuf);
776 UINT cchFilenameMax = _countof(wszBuf) - (pwszFilename - wszBuf);
777 if (GetDlgItemTextW(hwndDlg, 14001, pwszFilename, cchFilenameMax))
778 {
779 if (!MoveFileW(pFileDefExt->m_wszPath, wszBuf))
780 ERR("MoveFileW failed\n");
781 }
782
783 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
784 return TRUE;
785 }
786 break;
787 }
788 case PSM_QUERYSIBLINGS:
789 {
790 // reset icon
791 HWND hIconCtrl = GetDlgItem(hwndDlg, 14025);
792 HICON hIcon = (HICON)SendMessageW(hIconCtrl, STM_GETICON, 0, 0);
793 DestroyIcon(hIcon);
794 hIcon = NULL;
795 SendMessageW(hIconCtrl, STM_SETICON, (WPARAM)hIcon, 0);
796
797 // refresh the page
798 pFileDefExt->InitGeneralPage(hwndDlg);
799 return FALSE; // continue
800 }
801 case WM_DESTROY:
802 InterlockedIncrement(&pFileDefExt->m_Destroyed);
803 break;
804 case WM_UPDATEDIRSTATS:
805 pFileDefExt->UpdateDirStatsResults();
806 break;
807 }
808
809 return FALSE;
810}
811
812struct DIRTREESTATS
813{
814 PDWORD pDirCount, pFileCount;
815 PULARGE_INTEGER pTotVirtSize, pTotPhysSize;
816 UINT nTick;
817 UINT fAttribSet, fAttribAll;
818 BOOL bMultipleTypes;
819 BOOL bDeepRecurse;
820 WCHAR szType[max(100, RTL_FIELD_SIZE(SHFILEINFOA, szTypeName))];
821};
822
823void
824CFileDefExt::UpdateDirStatsResults()
825{
826 WCHAR fmt[200], buf[200];
827 LoadStringW(shell32_hInstance, IDS_FILE_FOLDER, fmt, _countof(fmt));
828 wsprintfW(buf, fmt, m_cFiles, m_cFolders - (m_bMultifile ? 0 : 1));
829 SetDlgItemTextW(m_hWndDirStatsDlg, m_bMultifile ? 14001 : 14027, buf);
830
831 if (SH_FormatFileSizeWithBytes(&m_DirSize, buf, _countof(buf)))
832 SetDlgItemTextW(m_hWndDirStatsDlg, 14011, buf);
833 if (SH_FormatFileSizeWithBytes(&m_DirSizeOnDisc, buf, _countof(buf)))
834 SetDlgItemTextW(m_hWndDirStatsDlg, 14012, buf);
835}
836
837void
838CFileDefExt::InitDirStats(struct DIRTREESTATS *pStats)
839{
840 pStats->pDirCount = &m_cFolders;
841 pStats->pFileCount = &m_cFiles;
842 pStats->pTotVirtSize = &m_DirSize;
843 pStats->pTotPhysSize = &m_DirSizeOnDisc;
844 pStats->nTick = GetTickCount();
845 pStats->fAttribSet = ~0ul;
846 pStats->fAttribAll = 0;
847 pStats->bDeepRecurse = FALSE;
848 pStats->bMultipleTypes = !m_bMultifile; // Only the Multifile page wants to know
849 pStats->szType[0] = UNICODE_NULL;
850}
851
852static UINT
853ProcessDirStatsItem(PCWSTR pszPath, DIRTREESTATS &Stats, WIN32_FIND_DATAW *pWFD)
854{
855 UINT fAttrib;
856 ULARGE_INTEGER nVirtSize, nPhysSize;
857 if (pWFD && (pWFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
858 {
859 fAttrib = pWFD->dwFileAttributes;
860 }
861 else
862 {
863 fAttrib = GetFileStats(pszPath, &nVirtSize, &nPhysSize);
864 if (fAttrib == INVALID_FILE_ATTRIBUTES)
865 return fAttrib;
866 }
867
868 Stats.fAttribSet &= fAttrib;
869 Stats.fAttribAll |= fAttrib;
870 if (fAttrib & FILE_ATTRIBUTE_DIRECTORY)
871 {
872 (*Stats.pDirCount)++;
873 }
874 else
875 {
876 (*Stats.pFileCount)++;
877 Stats.pTotVirtSize->QuadPart += nVirtSize.QuadPart;
878 Stats.pTotPhysSize->QuadPart += nPhysSize.QuadPart;
879 }
880
881 if (!Stats.bMultipleTypes)
882 {
883 SHFILEINFOW shfi;
884 if (!SHGetFileInfoW(pszPath, fAttrib, &shfi, sizeof(shfi),
885 SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES))
886 Stats.bMultipleTypes = TRUE;
887 else if (!Stats.szType[0])
888 wcscpy(Stats.szType, shfi.szTypeName);
889 else
890 Stats.bMultipleTypes = lstrcmpW(Stats.szType, shfi.szTypeName);
891 }
892 return fAttrib;
893}
894
895BOOL
896CFileDefExt::WalkDirTree(PCWSTR pszBase, struct DIRTREESTATS *pStats, WIN32_FIND_DATAW *pWFD)
897{
898 WIN32_FIND_DATAW wfd;
899 wfd.dwFileAttributes = ProcessDirStatsItem(pszBase, *pStats, pWFD);
900 if (wfd.dwFileAttributes == INVALID_FILE_ATTRIBUTES)
901 return FALSE;
902 if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
903 return TRUE;
904
905 BOOL bSuccess = TRUE;
906 SIZE_T cch = lstrlenW(pszBase);
907 PWSTR pszFull = (PWSTR)SHAlloc((cch + MAX_PATH) * sizeof(*pszFull));
908 if (!pszFull)
909 return FALSE;
910 PWSTR pszFile = pszFull + cch;
911 wcscpy(pszFull, pszBase);
912 wcscpy(pszFile++, L"\\*");
913 HANDLE hFind = FindFirstFileW(pszFull, &wfd);
914 if (hFind != INVALID_HANDLE_VALUE)
915 {
916 for (; bSuccess && !IsDestroyed();)
917 {
918 UINT nTickNow = GetTickCount();
919 if (nTickNow - pStats->nTick >= 500)
920 {
921 pStats->nTick = nTickNow;
922 SendMessageW(m_hWndDirStatsDlg, WM_UPDATEDIRSTATS, 0, 0);
923 }
924
925 wcscpy(pszFile, wfd.cFileName);
926 if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
927 {
928 bSuccess = ProcessDirStatsItem(pszFull, *pStats, NULL);
929 }
930 else if (!PathIsDotOrDotDotW(wfd.cFileName))
931 {
932 bSuccess = WalkDirTree(pszFull, pStats, &wfd);
933 pStats->bDeepRecurse |= bSuccess;
934 }
935
936 if (!FindNextFileW(hFind, &wfd))
937 break;
938 }
939 FindClose(hFind);
940 }
941 SHFree(pszFull);
942 return bSuccess;
943}
944
945void
946CFileDefExt::InitMultifilePageThread()
947{
948 DIRTREESTATS Stats;
949 InitDirStats(&Stats);
950
951 // MSDN says CFSTR_SHELLIDLIST and SHGDN_FORPARSING must be supported so that is what we use
952 for (SIZE_T i = 0; i < m_cidl; ++i)
953 {
954 PIDLIST_ABSOLUTE pidl = ILCombine(m_pidlFolder, m_pidls[i]);
955 if (!pidl)
956 return;
957 PWSTR pszAbsPath;
958 HRESULT hr = SHELL_DisplayNameOf(NULL, pidl, SHGDN_FORPARSING, &pszAbsPath);
959 ILFree(pidl);
960 if (FAILED(hr))
961 return;
962 BOOL bSuccess = WalkDirTree(pszAbsPath, &Stats, NULL);
963 SHFree(pszAbsPath);
964 if (!bSuccess)
965 return;
966 }
967
968 UpdateDirStatsResults();
969 if (Stats.bMultipleTypes)
970 LoadStringW(shell32_hInstance, IDS_MULTIPLETYPES, Stats.szType, _countof(Stats.szType));
971 SetDlgItemTextW(m_hWndDirStatsDlg, 14005, Stats.szType);
972
973 PWSTR pszDir = NULL;
974 HRESULT hr = E_FAIL;
975 if (!Stats.bDeepRecurse)
976 hr = SHELL_DisplayNameOf(NULL, m_pidlFolder, SHGDN_FORPARSING | SHGDN_FORADDRESSBAR, &pszDir);
977 if (SUCCEEDED(hr))
978 {
979 SetDlgItemTextW(m_hWndDirStatsDlg, 14009, pszDir);
980 }
981 else
982 {
983 LoadStringW(shell32_hInstance, IDS_VARIOUSFOLDERS, Stats.szType, _countof(Stats.szType));
984 SetDlgItemTextW(m_hWndDirStatsDlg, 14009, Stats.szType);
985 }
986 SHFree(pszDir);
987
988 #define SetMultifileDlgFileAttr(attr, id) do \
989 { \
990 if (Stats.fAttribSet & (attr)) \
991 CheckDlgButton(m_hWndDirStatsDlg, (id), BST_CHECKED); \
992 else if (Stats.fAttribAll & (attr)) \
993 CheckDlgButton(m_hWndDirStatsDlg, (id), BST_INDETERMINATE); \
994 } while (0)
995 SetMultifileDlgFileAttr(FILE_ATTRIBUTE_READONLY, 14021);
996 SetMultifileDlgFileAttr(FILE_ATTRIBUTE_HIDDEN, 14022);
997 SetMultifileDlgFileAttr(FILE_ATTRIBUTE_ARCHIVE, 14023);
998 #undef SetMultifileDlgFileAttr
999}
1000
1001DWORD CALLBACK
1002CFileDefExt::_InitializeMultifileThreadProc(LPVOID lpParameter)
1003{
1004 CFileDefExt *pThis = static_cast<CFileDefExt*>(lpParameter);
1005 pThis->InitMultifilePageThread();
1006 return pThis->Release();
1007}
1008
1009void
1010CFileDefExt::InitMultifilePage(HWND hwndDlg)
1011{
1012 m_hWndDirStatsDlg = hwndDlg;
1013 HICON hIco = LoadIconW(shell32_hInstance, MAKEINTRESOURCEW(IDI_SHELL_MULTIPLE_FILES));
1014 SendDlgItemMessageW(hwndDlg, 14000, STM_SETICON, (WPARAM)hIco, 0);
1015
1016 // We are lazy and just reuse the directory page so we must tweak some controls
1017 HWND hCtrl = GetDlgItem(hwndDlg, 14001);
1018 SetWindowLongPtrW(hCtrl, GWL_STYLE, GetWindowLongPtrW(GetDlgItem(hwndDlg, 14027), GWL_STYLE));
1019 SetWindowLongPtrW(hCtrl, GWL_EXSTYLE, 0);
1020 SendMessageW(hCtrl, EM_SETREADONLY, TRUE, 0);
1021 SetDlgItemTextW(hwndDlg, 14005, NULL);
1022
1023 static const WORD idAttr[] = { 14021, 14022, 14023 };
1024 for (SIZE_T i = 0; i < _countof(idAttr); ++i)
1025 {
1026 hCtrl = GetDlgItem(hwndDlg, idAttr[i]);
1027 SendMessageW(hCtrl, BM_SETSTYLE, BS_AUTO3STATE, TRUE);
1028 EnableWindow(hCtrl, FALSE);
1029 }
1030
1031 static const WORD idUnused[] = { 14026, 14027, 14028, 14014, 14015 };
1032 for (SIZE_T i = 0; i < _countof(idUnused); ++i)
1033 DestroyWindow(GetDlgItem(hwndDlg, idUnused[i]));
1034
1035 if (!m_cidl)
1036 return;
1037
1038 AddRef();
1039 if (!SHCreateThread(_InitializeMultifileThreadProc, this, CTF_COINIT, NULL))
1040 Release();
1041}
1042
1043/*************************************************************************
1044 *
1045 * CFileDefExt::MultifilePageProc
1046 *
1047 */
1048
1049INT_PTR CALLBACK
1050CFileDefExt::MultifilePageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1051{
1052 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
1053 switch (uMsg)
1054 {
1055 case WM_INITDIALOG:
1056 {
1057 LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam;
1058 if (ppsp == NULL || !ppsp->lParam)
1059 break;
1060 pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
1061 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pFileDefExt);
1062 pFileDefExt->InitMultifilePage(hwndDlg);
1063 break;
1064 }
1065 case WM_DESTROY:
1066 InterlockedIncrement(&pFileDefExt->m_Destroyed);
1067 break;
1068 case WM_UPDATEDIRSTATS:
1069 pFileDefExt->UpdateDirStatsResults();
1070 break;
1071 }
1072 return FALSE;
1073}
1074
1075/*************************************************************************
1076 *
1077 * CFileDefExt::InitVersionPage [Internal]
1078 *
1079 * sets all file version properties in dialog
1080 */
1081
1082BOOL
1083CFileDefExt::InitVersionPage(HWND hwndDlg)
1084{
1085 /* Get fixed info */
1086 VS_FIXEDFILEINFO *pInfo = m_VerInfo.GetFixedInfo();
1087 if (pInfo)
1088 {
1089 WCHAR wszVersion[256];
1090 swprintf(wszVersion, L"%u.%u.%u.%u", HIWORD(pInfo->dwFileVersionMS),
1091 LOWORD(pInfo->dwFileVersionMS),
1092 HIWORD(pInfo->dwFileVersionLS),
1093 LOWORD(pInfo->dwFileVersionLS));
1094 TRACE("MS %x LS %x ver %s \n", pInfo->dwFileVersionMS, pInfo->dwFileVersionLS, debugstr_w(wszVersion));
1095 SetDlgItemTextW(hwndDlg, 14001, wszVersion);
1096 }
1097
1098 /* Update labels */
1099 SetVersionLabel(hwndDlg, 14003, L"FileDescription");
1100 SetVersionLabel(hwndDlg, 14005, L"LegalCopyright");
1101
1102 /* Add items to listbox */
1103 AddVersionString(hwndDlg, L"CompanyName");
1104 LPCWSTR pwszLang = m_VerInfo.GetLangName();
1105 if (pwszLang)
1106 {
1107 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
1108 UINT Index = SendMessageW(hDlgCtrl, LB_ADDSTRING, (WPARAM)-1, (LPARAM)L"Language");
1109 SendMessageW(hDlgCtrl, LB_SETITEMDATA, (WPARAM)Index, (LPARAM)(WCHAR *)pwszLang);
1110 }
1111 AddVersionString(hwndDlg, L"ProductName");
1112 AddVersionString(hwndDlg, L"InternalName");
1113 AddVersionString(hwndDlg, L"OriginalFilename");
1114 AddVersionString(hwndDlg, L"FileVersion");
1115 AddVersionString(hwndDlg, L"ProductVersion");
1116 AddVersionString(hwndDlg, L"Comments");
1117 AddVersionString(hwndDlg, L"LegalTrademarks");
1118
1119 if (pInfo && (pInfo->dwFileFlags & VS_FF_PRIVATEBUILD))
1120 AddVersionString(hwndDlg, L"PrivateBuild");
1121
1122 if (pInfo && (pInfo->dwFileFlags & VS_FF_SPECIALBUILD))
1123 AddVersionString(hwndDlg, L"SpecialBuild");
1124
1125 /* Attach file version to dialog window */
1126 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)this);
1127
1128 /* Select first item */
1129 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
1130 SendMessageW(hDlgCtrl, LB_SETCURSEL, 0, 0);
1131 LPCWSTR pwszText = (LPCWSTR)SendMessageW(hDlgCtrl, LB_GETITEMDATA, (WPARAM)0, (LPARAM)NULL);
1132 if (pwszText && pwszText != (LPCWSTR)LB_ERR)
1133 SetDlgItemTextW(hwndDlg, 14010, pwszText);
1134
1135 return TRUE;
1136}
1137
1138/*************************************************************************
1139 *
1140 * CFileDefExt::SetVersionLabel [Internal]
1141 *
1142 * retrieves a version string and uses it to set label text
1143 */
1144
1145BOOL
1146CFileDefExt::SetVersionLabel(HWND hwndDlg, DWORD idCtrl, LPCWSTR pwszName)
1147{
1148 if (hwndDlg == NULL || pwszName == NULL)
1149 return FALSE;
1150
1151 LPCWSTR pwszValue = m_VerInfo.GetString(pwszName);
1152 if (pwszValue)
1153 {
1154 /* file description property */
1155 TRACE("%s :: %s\n", debugstr_w(pwszName), debugstr_w(pwszValue));
1156 SetDlgItemTextW(hwndDlg, idCtrl, pwszValue);
1157 return TRUE;
1158 }
1159
1160 return FALSE;
1161}
1162
1163/*************************************************************************
1164 *
1165 * CFileDefExt::AddVersionString [Internal]
1166 *
1167 * retrieves a version string and adds it to listbox
1168 */
1169
1170BOOL
1171CFileDefExt::AddVersionString(HWND hwndDlg, LPCWSTR pwszName)
1172{
1173 TRACE("pwszName %s, hwndDlg %p\n", debugstr_w(pwszName), hwndDlg);
1174
1175 if (hwndDlg == NULL || pwszName == NULL)
1176 return FALSE;
1177
1178 LPCWSTR pwszValue = m_VerInfo.GetString(pwszName);
1179 if (pwszValue)
1180 {
1181 /* listbox name property */
1182 HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
1183 TRACE("%s :: %s\n", debugstr_w(pwszName), debugstr_w(pwszValue));
1184 UINT Index = SendMessageW(hDlgCtrl, LB_ADDSTRING, (WPARAM) -1, (LPARAM)pwszName);
1185 SendMessageW(hDlgCtrl, LB_SETITEMDATA, (WPARAM)Index, (LPARAM)(WCHAR *)pwszValue);
1186 return TRUE;
1187 }
1188
1189 return FALSE;
1190}
1191
1192/*************************************************************************
1193 *
1194 * CFileDefExt::VersionPageProc
1195 *
1196 * wnd proc of 'Version' property sheet page
1197 */
1198
1199INT_PTR CALLBACK
1200CFileDefExt::VersionPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1201{
1202 switch (uMsg)
1203 {
1204 case WM_INITDIALOG:
1205 {
1206 LPPROPSHEETPAGE ppsp = (LPPROPSHEETPAGE)lParam;
1207
1208 if (ppsp == NULL || !ppsp->lParam)
1209 break;
1210
1211 TRACE("WM_INITDIALOG hwnd %p lParam %p ppsplParam %x\n", hwndDlg, lParam, ppsp->lParam);
1212
1213 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
1214 return pFileDefExt->InitVersionPage(hwndDlg);
1215 }
1216 case WM_COMMAND:
1217 if (LOWORD(wParam) == 14009 && HIWORD(wParam) == LBN_SELCHANGE)
1218 {
1219 HWND hDlgCtrl = (HWND)lParam;
1220
1221 LRESULT Index = SendMessageW(hDlgCtrl, LB_GETCURSEL, (WPARAM)NULL, (LPARAM)NULL);
1222 if (Index == LB_ERR)
1223 break;
1224
1225 LPCWSTR pwszData = (LPCWSTR)SendMessageW(hDlgCtrl, LB_GETITEMDATA, (WPARAM)Index, (LPARAM)NULL);
1226 if (pwszData == NULL)
1227 break;
1228
1229 CString str(pwszData);
1230 str.Trim();
1231
1232 TRACE("hDlgCtrl %x string %s\n", hDlgCtrl, debugstr_w(str));
1233 SetDlgItemTextW(hwndDlg, 14010, str);
1234
1235 return TRUE;
1236 }
1237 break;
1238 }
1239
1240 return FALSE;
1241}
1242
1243/*************************************************************************/
1244/* Folder Customize */
1245
1246static const WCHAR s_szShellClassInfo[] = L".ShellClassInfo";
1247static const WCHAR s_szIconIndex[] = L"IconIndex";
1248static const WCHAR s_szIconFile[] = L"IconFile";
1249static const WCHAR s_szIconResource[] = L"IconResource";
1250
1251// IDD_FOLDER_CUSTOMIZE
1252INT_PTR CALLBACK
1253CFileDefExt::FolderCustomizePageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1254{
1255 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
1256 switch (uMsg)
1257 {
1258 case WM_INITDIALOG:
1259 {
1260 LPPROPSHEETPAGE ppsp = (LPPROPSHEETPAGE)lParam;
1261
1262 if (ppsp == NULL || !ppsp->lParam)
1263 break;
1264
1265 TRACE("WM_INITDIALOG hwnd %p lParam %p ppsplParam %x\n", hwndDlg, lParam, ppsp->lParam);
1266
1267 pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
1268 return pFileDefExt->InitFolderCustomizePage(hwndDlg);
1269 }
1270
1271 case WM_COMMAND:
1272 switch (LOWORD(wParam))
1273 {
1274 case IDC_FOLDERCUST_CHANGE_ICON:
1275 pFileDefExt->OnFolderCustChangeIcon(hwndDlg);
1276 break;
1277
1278 case IDC_FOLDERCUST_CHOOSE_PIC:
1279 // TODO:
1280 break;
1281
1282 case IDC_FOLDERCUST_RESTORE_DEFAULTS:
1283 // TODO:
1284 break;
1285 }
1286 break;
1287
1288 case WM_NOTIFY:
1289 {
1290 LPPSHNOTIFY lppsn = (LPPSHNOTIFY)lParam;
1291 if (lppsn->hdr.code == PSN_APPLY)
1292 {
1293 // apply or not
1294 if (pFileDefExt->OnFolderCustApply(hwndDlg))
1295 {
1296 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
1297 }
1298 else
1299 {
1300 SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
1301 }
1302 return TRUE;
1303 }
1304 break;
1305 }
1306
1307 case PSM_QUERYSIBLINGS:
1308 return FALSE; // continue
1309
1310 case WM_DESTROY:
1311 pFileDefExt->OnFolderCustDestroy(hwndDlg);
1312 break;
1313
1314 default:
1315 break;
1316 }
1317
1318 return FALSE;
1319}
1320
1321// IDD_FOLDER_CUSTOMIZE WM_DESTROY
1322void CFileDefExt::OnFolderCustDestroy(HWND hwndDlg)
1323{
1324 ::DestroyIcon(m_hFolderIcon);
1325 m_hFolderIcon = NULL;
1326
1327 /* Detach the object from dialog window */
1328 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)0);
1329}
1330
1331void CFileDefExt::UpdateFolderIcon(HWND hwndDlg)
1332{
1333 // destroy icon if any
1334 if (m_hFolderIcon)
1335 {
1336 ::DestroyIcon(m_hFolderIcon);
1337 m_hFolderIcon = NULL;
1338 }
1339
1340 // create the icon
1341 if (m_szFolderIconPath[0] == 0 && m_nFolderIconIndex == 0)
1342 {
1343 // Windows ignores shell customization here and uses the default icon
1344 m_hFolderIcon = LoadIconW(shell32_hInstance, MAKEINTRESOURCEW(IDI_SHELL_FOLDER));
1345 }
1346 else
1347 {
1348 ExtractIconExW(m_szFolderIconPath, m_nFolderIconIndex, &m_hFolderIcon, NULL, 1);
1349 }
1350
1351 // set icon
1352 SendDlgItemMessageW(hwndDlg, IDC_FOLDERCUST_ICON, STM_SETICON, (WPARAM)m_hFolderIcon, 0);
1353}
1354
1355// IDD_FOLDER_CUSTOMIZE WM_INITDIALOG
1356BOOL CFileDefExt::InitFolderCustomizePage(HWND hwndDlg)
1357{
1358 /* Attach the object to dialog window */
1359 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)this);
1360
1361 EnableWindow(GetDlgItem(hwndDlg, IDC_FOLDERCUST_COMBOBOX), FALSE);
1362 EnableWindow(GetDlgItem(hwndDlg, IDC_FOLDERCUST_CHECKBOX), FALSE);
1363 EnableWindow(GetDlgItem(hwndDlg, IDC_FOLDERCUST_CHOOSE_PIC), FALSE);
1364 EnableWindow(GetDlgItem(hwndDlg, IDC_FOLDERCUST_RESTORE_DEFAULTS), FALSE);
1365
1366 // build the desktop.ini file path
1367 WCHAR szIniFile[MAX_PATH];
1368 StringCchCopyW(szIniFile, _countof(szIniFile), m_wszPath);
1369 PathAppendW(szIniFile, L"desktop.ini");
1370
1371 // desktop.ini --> m_szFolderIconPath, m_nFolderIconIndex
1372 m_szFolderIconPath[0] = 0;
1373 m_nFolderIconIndex = 0;
1374 if (GetPrivateProfileStringW(s_szShellClassInfo, s_szIconFile, NULL,
1375 m_szFolderIconPath, _countof(m_szFolderIconPath), szIniFile))
1376 {
1377 m_nFolderIconIndex = GetPrivateProfileIntW(s_szShellClassInfo, s_szIconIndex, 0, szIniFile);
1378 }
1379 else if (GetPrivateProfileStringW(s_szShellClassInfo, s_szIconResource, NULL,
1380 m_szFolderIconPath, _countof(m_szFolderIconPath), szIniFile))
1381 {
1382 m_nFolderIconIndex = PathParseIconLocationW(m_szFolderIconPath);
1383 }
1384
1385 // update icon
1386 UpdateFolderIcon(hwndDlg);
1387
1388 return TRUE;
1389}
1390
1391// IDD_FOLDER_CUSTOMIZE IDC_FOLDERCUST_CHANGE_ICON
1392void CFileDefExt::OnFolderCustChangeIcon(HWND hwndDlg)
1393{
1394 WCHAR szPath[MAX_PATH];
1395 INT nIconIndex;
1396
1397 // m_szFolderIconPath, m_nFolderIconIndex --> szPath, nIconIndex
1398 if (m_szFolderIconPath[0])
1399 {
1400 StringCchCopyW(szPath, _countof(szPath), m_szFolderIconPath);
1401 nIconIndex = m_nFolderIconIndex;
1402 }
1403 else
1404 {
1405 szPath[0] = 0;
1406 nIconIndex = 0;
1407 }
1408
1409 // let the user choose the icon
1410 if (PickIconDlg(hwndDlg, szPath, _countof(szPath), &nIconIndex))
1411 {
1412 // changed
1413 m_bFolderIconIsSet = TRUE;
1414 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
1415
1416 // update
1417 StringCchCopyW(m_szFolderIconPath, _countof(m_szFolderIconPath), szPath);
1418 m_nFolderIconIndex = nIconIndex;
1419 UpdateFolderIcon(hwndDlg);
1420 }
1421}
1422
1423// IDD_FOLDER_CUSTOMIZE PSN_APPLY
1424BOOL CFileDefExt::OnFolderCustApply(HWND hwndDlg)
1425{
1426 // build the desktop.ini file path
1427 WCHAR szIniFile[MAX_PATH];
1428 StringCchCopyW(szIniFile, _countof(szIniFile), m_wszPath);
1429 PathAppendW(szIniFile, L"desktop.ini");
1430
1431 if (m_bFolderIconIsSet) // it is set!
1432 {
1433 DWORD attrs;
1434
1435 // change folder attributes (-S -R)
1436 attrs = GetFileAttributesW(m_wszPath);
1437 attrs &= ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY);
1438 SetFileAttributesW(m_wszPath, attrs);
1439
1440 // change desktop.ini attributes (-S -H -R)
1441 attrs = GetFileAttributesW(szIniFile);
1442 attrs &= ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
1443 SetFileAttributesW(szIniFile, attrs);
1444
1445 if (m_szFolderIconPath[0])
1446 {
1447 // write IconFile and IconIndex
1448 WritePrivateProfileStringW(s_szShellClassInfo, s_szIconFile, m_szFolderIconPath, szIniFile);
1449
1450 WCHAR szInt[32];
1451 StringCchPrintfW(szInt, _countof(szInt), L"%d", m_nFolderIconIndex);
1452 WritePrivateProfileStringW(s_szShellClassInfo, s_szIconIndex, szInt, szIniFile);
1453
1454 // flush!
1455 WritePrivateProfileStringW(NULL, NULL, NULL, szIniFile);
1456 }
1457 else
1458 {
1459 // erase three values
1460 WritePrivateProfileStringW(s_szShellClassInfo, s_szIconFile, NULL, szIniFile);
1461 WritePrivateProfileStringW(s_szShellClassInfo, s_szIconIndex, NULL, szIniFile);
1462 WritePrivateProfileStringW(s_szShellClassInfo, s_szIconResource, NULL, szIniFile);
1463
1464 // flush!
1465 WritePrivateProfileStringW(NULL, NULL, NULL, szIniFile);
1466 }
1467
1468 // change desktop.ini attributes (+S +H)
1469 attrs = GetFileAttributesW(szIniFile);
1470 attrs |= FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
1471 SetFileAttributesW(szIniFile, attrs);
1472
1473 // change folder attributes (+R)
1474 attrs = GetFileAttributesW(m_wszPath);
1475 attrs |= FILE_ATTRIBUTE_READONLY;
1476 SetFileAttributesW(m_wszPath, attrs);
1477
1478 // notify to the siblings
1479 PropSheet_QuerySiblings(GetParent(hwndDlg), 0, 0);
1480
1481 // done!
1482 m_bFolderIconIsSet = FALSE;
1483 }
1484
1485 return TRUE;
1486}
1487
1488/*****************************************************************************/
1489
1490CFileDefExt::CFileDefExt():
1491 m_bDir(FALSE), m_bMultifile(FALSE), m_cFiles(0), m_cFolders(0)
1492{
1493 m_wszPath[0] = L'\0';
1494 m_DirSize.QuadPart = 0ull;
1495 m_DirSizeOnDisc.QuadPart = 0ull;
1496
1497 m_szFolderIconPath[0] = 0;
1498 m_nFolderIconIndex = 0;
1499 m_hFolderIcon = NULL;
1500 m_bFolderIconIsSet = FALSE;
1501}
1502
1503CFileDefExt::~CFileDefExt()
1504{
1505 _ILFreeaPidl(m_pidls, m_cidl);
1506 ILFree(m_pidlFolder);
1507}
1508
1509HRESULT WINAPI
1510CFileDefExt::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pDataObj, HKEY hkeyProgID)
1511{
1512 FORMATETC format;
1513 STGMEDIUM stgm;
1514 HRESULT hr;
1515
1516 TRACE("%p %p %p %p\n", this, pidlFolder, pDataObj, hkeyProgID);
1517
1518 if (!pDataObj)
1519 return E_FAIL;
1520
1521 int count = DataObject_GetHIDACount(pDataObj);
1522 m_bMultifile = count > 1;
1523 if (m_bMultifile)
1524 {
1525 CDataObjectHIDA cida(pDataObj);
1526 if (SUCCEEDED(cida.hr()))
1527 {
1528 m_pidls = _ILCopyCidaToaPidl(&m_pidlFolder, cida);
1529 if (m_pidls)
1530 m_cidl = cida->cidl;
1531 }
1532 }
1533
1534 format.cfFormat = CF_HDROP;
1535 format.ptd = NULL;
1536 format.dwAspect = DVASPECT_CONTENT;
1537 format.lindex = -1;
1538 format.tymed = TYMED_HGLOBAL;
1539
1540 hr = pDataObj->GetData(&format, &stgm);
1541 if (FAILED_UNEXPECTEDLY(hr))
1542 return hr;
1543
1544 if (!DragQueryFileW((HDROP)stgm.hGlobal, 0, m_wszPath, _countof(m_wszPath)))
1545 {
1546 ERR("DragQueryFileW failed\n");
1547 ReleaseStgMedium(&stgm);
1548 return E_FAIL;
1549 }
1550
1551 ReleaseStgMedium(&stgm);
1552
1553 TRACE("File properties %ls\n", m_wszPath);
1554 m_bDir = !m_bMultifile && PathIsDirectoryW(m_wszPath);
1555 if (!m_bDir && !m_bMultifile)
1556 m_VerInfo.Load(m_wszPath);
1557
1558 return S_OK;
1559}
1560
1561HRESULT WINAPI
1562CFileDefExt::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
1563{
1564 UNIMPLEMENTED;
1565 return E_NOTIMPL;
1566}
1567
1568HRESULT WINAPI
1569CFileDefExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
1570{
1571 UNIMPLEMENTED;
1572 return E_NOTIMPL;
1573}
1574
1575HRESULT WINAPI
1576CFileDefExt::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax)
1577{
1578 UNIMPLEMENTED;
1579 return E_NOTIMPL;
1580}
1581
1582HRESULT WINAPI
1583CFileDefExt::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
1584{
1585 HPROPSHEETPAGE hPage;
1586 WORD wResId = (m_bDir || m_bMultifile) ? IDD_FOLDER_PROPERTIES : IDD_FILE_PROPERTIES;
1587 DLGPROC pfnFirstPage = m_bMultifile ? MultifilePageProc : GeneralPageProc;
1588
1589 hPage = SH_CreatePropertySheetPageEx(wResId, pfnFirstPage, (LPARAM)this, NULL,
1590 &PropSheetPageLifetimeCallback<CFileDefExt>);
1591 HRESULT hr = AddPropSheetPage(hPage, pfnAddPage, lParam);
1592 if (FAILED_UNEXPECTEDLY(hr))
1593 return hr;
1594 else
1595 AddRef(); // For PropSheetPageLifetimeCallback
1596
1597 if (m_bMultifile)
1598 return S_OK;
1599
1600 if (!m_bDir && GetFileVersionInfoSizeW(m_wszPath, NULL))
1601 {
1602 hPage = SH_CreatePropertySheetPage(IDD_FILE_VERSION,
1603 VersionPageProc,
1604 (LPARAM)this,
1605 NULL);
1606 AddPropSheetPage(hPage, pfnAddPage, lParam);
1607 }
1608
1609 if (m_bDir)
1610 {
1611 hPage = SH_CreatePropertySheetPage(IDD_FOLDER_CUSTOMIZE,
1612 FolderCustomizePageProc,
1613 (LPARAM)this,
1614 NULL);
1615 AddPropSheetPage(hPage, pfnAddPage, lParam);
1616 }
1617
1618 return S_OK;
1619}
1620
1621HRESULT WINAPI
1622CFileDefExt::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam)
1623{
1624 UNIMPLEMENTED;
1625 return E_NOTIMPL;
1626}
1627
1628void
1629CFileDefExt::CountFolderAndFiles()
1630{
1631 DIRTREESTATS Stats;
1632 InitDirStats(&Stats);
1633 WalkDirTree(m_wszPath, &Stats, NULL);
1634 UpdateDirStatsResults();
1635}
1636
1637DWORD CALLBACK
1638CFileDefExt::_CountFolderAndFilesThreadProc(LPVOID lpParameter)
1639{
1640 CFileDefExt *pThis = static_cast<CFileDefExt*>(lpParameter);
1641 pThis->CountFolderAndFiles();
1642 return pThis->Release();
1643}