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 WINE_DEFAULT_DEBUG_CHANNEL(shell);
25 
26 EXTERN_C BOOL PathIsExeW(LPCWSTR lpszPath);
27 
28 BOOL CFileVersionInfo::Load(LPCWSTR pwszPath)
29 {
30     ULONG cbInfo = GetFileVersionInfoSizeW(pwszPath, NULL);
31     if (!cbInfo)
32     {
33         WARN("GetFileVersionInfoSize %ls failed\n", pwszPath);
34         return FALSE;
35     }
36 
37     m_pInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbInfo);
38     if (!m_pInfo)
39     {
40         ERR("HeapAlloc failed bytes %x\n", cbInfo);
41         return FALSE;
42     }
43 
44     if (!GetFileVersionInfoW(pwszPath, 0, cbInfo, m_pInfo))
45     {
46         ERR("GetFileVersionInfoW failed\n");
47         return FALSE;
48     }
49 
50     LPLANGANDCODEPAGE lpLangCode;
51     UINT cBytes;
52     if (!VerQueryValueW(m_pInfo, L"\\VarFileInfo\\Translation", (LPVOID *)&lpLangCode, &cBytes) || cBytes < sizeof(LANGANDCODEPAGE))
53     {
54         ERR("VerQueryValueW failed\n");
55         return FALSE;
56     }
57 
58     /* FIXME: find language from current locale / if not available,
59      * default to english
60      * for now default to first available language
61      */
62     m_wLang = lpLangCode->wLang;
63     m_wCode = lpLangCode->wCode;
64     TRACE("Lang %hx Code %hu\n", m_wLang, m_wCode);
65 
66     return TRUE;
67 }
68 
69 LPCWSTR CFileVersionInfo::GetString(LPCWSTR pwszName)
70 {
71     if (!m_pInfo)
72         return NULL;
73 
74     WCHAR wszBuf[256];
75     swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\%s", m_wLang, m_wCode, pwszName);
76 
77     /* Query string in version block */
78     LPCWSTR pwszResult = NULL;
79     UINT cBytes = 0;
80     if (!VerQueryValueW(m_pInfo, wszBuf, (LPVOID *)&pwszResult, &cBytes))
81         pwszResult = NULL;
82 
83     if (!m_wLang && !m_wCode)
84     {
85         /* Try US English */
86         swprintf(wszBuf, L"\\StringFileInfo\\%04x%04x\\%s", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 1252, pwszName);
87         if (!VerQueryValueW(m_pInfo, wszBuf, (LPVOID *)&pwszResult, &cBytes))
88             pwszResult = NULL;
89     }
90 
91     if (!pwszResult)
92         ERR("VerQueryValueW %ls failed\n", pwszName);
93     else
94         TRACE("%ls: %ls\n", pwszName, pwszResult);
95 
96     return pwszResult;
97 }
98 
99 VS_FIXEDFILEINFO *CFileVersionInfo::GetFixedInfo()
100 {
101     if (!m_pInfo)
102         return NULL;
103 
104     VS_FIXEDFILEINFO *pInfo;
105     UINT cBytes;
106     if (!VerQueryValueW(m_pInfo, L"\\", (PVOID*)&pInfo, &cBytes))
107         return NULL;
108     return pInfo;
109 }
110 
111 LPCWSTR CFileVersionInfo::GetLangName()
112 {
113     if (!m_pInfo)
114         return NULL;
115 
116     if (!m_wszLang[0])
117     {
118         if (!VerLanguageNameW(m_wLang, m_wszLang, _countof(m_wszLang)))
119             ERR("VerLanguageNameW failed\n");
120     }
121 
122     return m_wszLang;
123 }
124 
125 UINT
126 SH_FormatInteger(LONGLONG Num, LPWSTR pwszResult, UINT cchResultMax)
127 {
128     // Print the number in uniform mode
129     WCHAR wszNumber[24];
130     swprintf(wszNumber, L"%I64u", Num);
131 
132     // Get system strings for decimal and thousand separators.
133     WCHAR wszDecimalSep[8], wszThousandSep[8];
134     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, wszDecimalSep, _countof(wszDecimalSep));
135     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, wszThousandSep, _countof(wszThousandSep));
136 
137     // Initialize format for printing the number in bytes
138     NUMBERFMTW nf;
139     ZeroMemory(&nf, sizeof(nf));
140     nf.lpDecimalSep = wszDecimalSep;
141     nf.lpThousandSep = wszThousandSep;
142 
143     // Get system string for groups separator
144     WCHAR wszGrouping[12];
145     INT cchGrouping = GetLocaleInfoW(LOCALE_USER_DEFAULT,
146                                      LOCALE_SGROUPING,
147                                      wszGrouping,
148                                      _countof(wszGrouping));
149 
150     // Convert grouping specs from string to integer
151     for (INT i = 0; i < cchGrouping; i++)
152     {
153         WCHAR wch = wszGrouping[i];
154 
155         if (wch >= L'0' && wch <= L'9')
156             nf.Grouping = nf.Grouping * 10 + (wch - L'0');
157         else if (wch != L';')
158             break;
159     }
160 
161     if ((nf.Grouping % 10) == 0)
162         nf.Grouping /= 10;
163     else
164         nf.Grouping *= 10;
165 
166     // Format the number
167     INT cchResult = GetNumberFormatW(LOCALE_USER_DEFAULT,
168                                     0,
169                                     wszNumber,
170                                     &nf,
171                                     pwszResult,
172                                     cchResultMax);
173 
174     if (!cchResult)
175         return 0;
176 
177     // GetNumberFormatW returns number of characters including UNICODE_NULL
178     return cchResult - 1;
179 }
180 
181 UINT
182 SH_FormatByteSize(LONGLONG cbSize, LPWSTR pwszResult, UINT cchResultMax)
183 {
184     /* Write formated bytes count */
185     INT cchWritten = SH_FormatInteger(cbSize, pwszResult, cchResultMax);
186     if (!cchWritten)
187         return 0;
188 
189     /* Copy " bytes" to buffer */
190     LPWSTR pwszEnd = pwszResult + cchWritten;
191     size_t cchRemaining = cchResultMax - cchWritten;
192     StringCchCopyExW(pwszEnd, cchRemaining, L" ", &pwszEnd, &cchRemaining, 0);
193     cchWritten = LoadStringW(shell32_hInstance, IDS_BYTES_FORMAT, pwszEnd, cchRemaining);
194     cchRemaining -= cchWritten;
195 
196     return cchResultMax - cchRemaining;
197 }
198 
199 /*************************************************************************
200  *
201  * SH_FormatFileSizeWithBytes
202  *
203  * Format a size in bytes to string.
204  *
205  * lpQwSize = Pointer to 64bit large integer to format
206  * pszBuf   = Buffer to fill with output string
207  * cchBuf   = size of pszBuf in characters
208  *
209  */
210 
211 LPWSTR
212 SH_FormatFileSizeWithBytes(const PULARGE_INTEGER lpQwSize, LPWSTR pwszResult, UINT cchResultMax)
213 {
214     /* Format bytes in KBs, MBs etc */
215     if (StrFormatByteSizeW(lpQwSize->QuadPart, pwszResult, cchResultMax) == NULL)
216         return NULL;
217 
218     /* If there is less bytes than 1KB, we have nothing to do */
219     if (lpQwSize->QuadPart < 1024)
220         return pwszResult;
221 
222     /* Concatenate " (" */
223     UINT cchWritten = wcslen(pwszResult);
224     LPWSTR pwszEnd = pwszResult + cchWritten;
225     size_t cchRemaining = cchResultMax - cchWritten;
226     StringCchCopyExW(pwszEnd, cchRemaining, L" (", &pwszEnd, &cchRemaining, 0);
227 
228     /* Write formated bytes count */
229     cchWritten = SH_FormatByteSize(lpQwSize->QuadPart, pwszEnd, cchRemaining);
230     pwszEnd += cchWritten;
231     cchRemaining -= cchWritten;
232 
233     /* Copy ")" to the buffer */
234     StringCchCopyW(pwszEnd, cchRemaining, L")");
235 
236     return pwszResult;
237 }
238 
239 /*************************************************************************
240  *
241  * SH_CreatePropertySheetPage [Internal]
242  *
243  * creates a property sheet page from a resource id
244  *
245  */
246 
247 HPROPSHEETPAGE
248 SH_CreatePropertySheetPage(WORD wDialogId, DLGPROC pfnDlgProc, LPARAM lParam, LPCWSTR pwszTitle)
249 {
250     PROPSHEETPAGEW Page;
251 
252     memset(&Page, 0x0, sizeof(PROPSHEETPAGEW));
253     Page.dwSize = sizeof(PROPSHEETPAGEW);
254     Page.dwFlags = PSP_DEFAULT;
255     Page.hInstance = shell32_hInstance;
256     Page.pszTemplate = MAKEINTRESOURCE(wDialogId);
257     Page.pfnDlgProc = pfnDlgProc;
258     Page.lParam = lParam;
259     Page.pszTitle = pwszTitle;
260 
261     if (pwszTitle)
262         Page.dwFlags |= PSP_USETITLE;
263 
264     return CreatePropertySheetPageW(&Page);
265 }
266 
267 VOID
268 CFileDefExt::InitOpensWithField(HWND hwndDlg)
269 {
270     WCHAR wszBuf[MAX_PATH] = L"";
271     WCHAR wszPath[MAX_PATH] = L"";
272     DWORD dwSize = sizeof(wszBuf);
273     BOOL bUnknownApp = TRUE;
274     LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
275 
276     if (RegGetValueW(HKEY_CLASSES_ROOT, pwszExt, L"", RRF_RT_REG_SZ, NULL, wszBuf, &dwSize) == ERROR_SUCCESS)
277     {
278         bUnknownApp = FALSE;
279         StringCbCatW(wszBuf, sizeof(wszBuf), L"\\shell\\open\\command");
280         dwSize = sizeof(wszPath);
281         if (RegGetValueW(HKEY_CLASSES_ROOT, wszBuf, L"", RRF_RT_REG_SZ, NULL, wszPath, &dwSize) == ERROR_SUCCESS)
282         {
283             /* Get path from command line */
284             ExpandEnvironmentStringsW(wszPath, wszBuf, _countof(wszBuf));
285             PathRemoveArgs(wszBuf);
286             PathUnquoteSpacesW(wszBuf);
287             PathSearchAndQualify(wszBuf, wszPath, _countof(wszPath));
288 
289             HICON hIcon;
290             if (ExtractIconExW(wszPath, 0, NULL, &hIcon, 1))
291             {
292                 HWND hIconCtrl = GetDlgItem(hwndDlg, 14025);
293                 HWND hDescrCtrl = GetDlgItem(hwndDlg, 14007);
294                 ShowWindow(hIconCtrl, SW_SHOW);
295                 RECT rcIcon, rcDescr;
296                 GetWindowRect(hIconCtrl, &rcIcon);
297 
298                 rcIcon.right = rcIcon.left + GetSystemMetrics(SM_CXSMICON);
299                 rcIcon.bottom = rcIcon.top + GetSystemMetrics(SM_CYSMICON);
300 
301                 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rcIcon, 2);
302                 GetWindowRect(hDescrCtrl, &rcDescr);
303                 MapWindowPoints(NULL, hwndDlg, (LPPOINT)&rcDescr, 2);
304                 INT cxOffset = rcIcon.right + 2 - rcDescr.left;
305                 SetWindowPos(hDescrCtrl, NULL,
306                              rcDescr.left + cxOffset, rcDescr.top,
307                              rcDescr.right - rcDescr.left - cxOffset, rcDescr.bottom - rcDescr.top,
308                              SWP_NOZORDER);
309                 SendMessageW(hIconCtrl, STM_SETICON, (WPARAM)hIcon, 0);
310             } else
311                 ERR("Failed to extract icon\n");
312 
313             if (PathFileExistsW(wszPath))
314             {
315                 /* Get file description */
316                 CFileVersionInfo VerInfo;
317                 VerInfo.Load(wszPath);
318                 LPCWSTR pwszDescr = VerInfo.GetString(L"FileDescription");
319                 if (pwszDescr)
320                     SetDlgItemTextW(hwndDlg, 14007, pwszDescr);
321                 else
322                 {
323                     /* File has no description - display filename */
324                     LPWSTR pwszFilename = PathFindFileNameW(wszPath);
325                     PathRemoveExtension(pwszFilename);
326                     pwszFilename[0] = towupper(pwszFilename[0]);
327                     SetDlgItemTextW(hwndDlg, 14007, pwszFilename);
328                 }
329             }
330             else
331                 bUnknownApp = TRUE;
332         } else
333             WARN("RegGetValueW %ls failed\n", wszBuf);
334     } else
335         WARN("RegGetValueW %ls failed\n", pwszExt);
336 
337     if (bUnknownApp)
338     {
339         /* Unknown application */
340         LoadStringW(shell32_hInstance, IDS_UNKNOWN_APP, wszBuf, _countof(wszBuf));
341         SetDlgItemTextW(hwndDlg, 14007, wszBuf);
342     }
343 }
344 
345 /*************************************************************************
346  *
347  * SH_FileGeneralFileType [Internal]
348  *
349  * retrieves file extension description from registry and sets it in dialog
350  *
351  * TODO: retrieve file extension default icon and load it
352  *       find executable name from registry, retrieve description from executable
353  */
354 
355 BOOL
356 CFileDefExt::InitFileType(HWND hwndDlg)
357 {
358     TRACE("path %s\n", debugstr_w(m_wszPath));
359 
360     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14005);
361     if (hDlgCtrl == NULL)
362         return FALSE;
363 
364     /* Get file information */
365     SHFILEINFOW fi;
366     if (!SHGetFileInfoW(m_wszPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME|SHGFI_ICON))
367     {
368         ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_wszPath, GetLastError());
369         fi.szTypeName[0] = L'\0';
370         fi.hIcon = NULL;
371     }
372 
373     LPCWSTR pwszExt = PathFindExtensionW(m_wszPath);
374     if (pwszExt[0])
375     {
376         WCHAR wszBuf[256];
377 
378         if (!fi.szTypeName[0])
379         {
380             /* The file type is unknown, so default to string "FileExtension File" */
381             size_t cchRemaining = 0;
382             LPWSTR pwszEnd = NULL;
383 
384             StringCchPrintfExW(wszBuf, _countof(wszBuf), &pwszEnd, &cchRemaining, 0, L"%s ", pwszExt + 1);
385             SendMessageW(hDlgCtrl, WM_GETTEXT, (WPARAM)cchRemaining, (LPARAM)pwszEnd);
386 
387             SendMessageW(hDlgCtrl, WM_SETTEXT, (WPARAM)NULL, (LPARAM)wszBuf);
388         }
389         else
390         {
391             /* Update file type */
392             StringCbPrintfW(wszBuf, sizeof(wszBuf), L"%s (%s)", fi.szTypeName, pwszExt);
393             SendMessageW(hDlgCtrl, WM_SETTEXT, (WPARAM)NULL, (LPARAM)wszBuf);
394         }
395     }
396 
397     /* Update file icon */
398     if (fi.hIcon)
399         SendDlgItemMessageW(hwndDlg, 14000, STM_SETICON, (WPARAM)fi.hIcon, 0);
400     else
401         ERR("No icon %ls\n", m_wszPath);
402 
403     return TRUE;
404 }
405 
406 /*************************************************************************
407  *
408  * CFileDefExt::InitFilePath [Internal]
409  *
410  * sets file path string and filename string
411  *
412  */
413 
414 BOOL
415 CFileDefExt::InitFilePath(HWND hwndDlg)
416 {
417     /* Find the filename */
418     WCHAR *pwszFilename = PathFindFileNameW(m_wszPath);
419 
420     if (pwszFilename > m_wszPath)
421     {
422         /* Location field */
423         WCHAR wszLocation[MAX_PATH];
424         StringCchCopyNW(wszLocation, _countof(wszLocation), m_wszPath, pwszFilename - m_wszPath);
425         PathRemoveBackslashW(wszLocation);
426 
427         SetDlgItemTextW(hwndDlg, 14009, wszLocation);
428     }
429 
430     /* Filename field */
431     SetDlgItemTextW(hwndDlg, 14001, pwszFilename);
432 
433     return TRUE;
434 }
435 
436 /*************************************************************************
437  *
438  * CFileDefExt::GetFileTimeString [Internal]
439  *
440  * formats a given LPFILETIME struct into readable user format
441  */
442 
443 BOOL
444 CFileDefExt::GetFileTimeString(LPFILETIME lpFileTime, LPWSTR pwszResult, UINT cchResult)
445 {
446     FILETIME ft;
447     SYSTEMTIME st;
448 
449     if (!FileTimeToLocalFileTime(lpFileTime, &ft) || !FileTimeToSystemTime(&ft, &st))
450         return FALSE;
451 
452     size_t cchRemaining = cchResult;
453     LPWSTR pwszEnd = pwszResult;
454     int cchWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, pwszEnd, cchRemaining);
455     if (cchWritten)
456         --cchWritten; // GetDateFormatW returns count with terminating zero
457     else
458         ERR("GetDateFormatW failed\n");
459     cchRemaining -= cchWritten;
460     pwszEnd += cchWritten;
461 
462     StringCchCopyExW(pwszEnd, cchRemaining, L", ", &pwszEnd, &cchRemaining, 0);
463 
464     cchWritten = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, pwszEnd, cchRemaining);
465     if (cchWritten)
466         --cchWritten; // GetTimeFormatW returns count with terminating zero
467     else
468         ERR("GetTimeFormatW failed\n");
469     TRACE("result %s\n", debugstr_w(pwszResult));
470     return TRUE;
471 }
472 
473 /*************************************************************************
474  *
475  * CFileDefExt::InitFileAttr [Internal]
476  *
477  * retrieves file information from file and sets in dialog
478  *
479  */
480 
481 BOOL
482 CFileDefExt::InitFileAttr(HWND hwndDlg)
483 {
484     BOOL Success;
485     WIN32_FIND_DATAW FileInfo; // WIN32_FILE_ATTRIBUTE_DATA
486     WCHAR wszBuf[MAX_PATH];
487 
488     TRACE("InitFileAttr %ls\n", m_wszPath);
489 
490     /*
491      * There are situations where GetFileAttributes(Ex) can fail even if the
492      * specified path represents a file. This happens when e.g. the file is a
493      * locked system file, such as C:\pagefile.sys . In this case, the function
494      * returns INVALID_FILE_ATTRIBUTES and GetLastError returns ERROR_SHARING_VIOLATION.
495      * (this would allow us to distinguish between this failure and a failure
496      * due to the fact that the path actually refers to a directory).
497      *
498      * Because we really want to retrieve the file attributes/size/date&time,
499      * we do the following trick:
500      * - First we call GetFileAttributesEx. If it succeeds we know we have
501      *   a file or a directory, and we have retrieved its attributes.
502      * - If GetFileAttributesEx fails, we call FindFirstFile on the full path.
503      *   While we could have called FindFirstFile at first and skip GetFileAttributesEx
504      *   altogether, we do it after GetFileAttributesEx because it performs more
505      *   work to retrieve the file attributes. However it actually works even
506      *   for locked system files.
507      * - If FindFirstFile succeeds we have retrieved its attributes.
508      * - Otherwise (FindFirstFile has failed), we do not retrieve anything.
509      *
510      * The following code also relies on the fact that the first 6 members
511      * of WIN32_FIND_DATA are *exactly* the same as the WIN32_FILE_ATTRIBUTE_DATA
512      * structure. Therefore it is safe to use a single WIN32_FIND_DATA
513      * structure for both the GetFileAttributesEx and FindFirstFile calls.
514      */
515 
516     Success = GetFileAttributesExW(m_wszPath,
517                                    GetFileExInfoStandard,
518                                    (LPWIN32_FILE_ATTRIBUTE_DATA)&FileInfo);
519     if (!Success)
520     {
521         HANDLE hFind = FindFirstFileW(m_wszPath, &FileInfo);
522         Success = (hFind != INVALID_HANDLE_VALUE);
523         if (Success)
524             FindClose(hFind);
525     }
526 
527     if (Success)
528     {
529         /* Update attribute checkboxes */
530         if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
531             SendDlgItemMessage(hwndDlg, 14021, BM_SETCHECK, BST_CHECKED, 0);
532         if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
533             SendDlgItemMessage(hwndDlg, 14022, BM_SETCHECK, BST_CHECKED, 0);
534         if (FileInfo.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
535             SendDlgItemMessage(hwndDlg, 14023, BM_SETCHECK, BST_CHECKED, 0);
536 
537         /* Update creation time */
538         if (GetFileTimeString(&FileInfo.ftCreationTime, wszBuf, _countof(wszBuf)))
539             SetDlgItemTextW(hwndDlg, 14015, wszBuf);
540 
541         /* For files display last access and last write time */
542         if (!m_bDir)
543         {
544             if (GetFileTimeString(&FileInfo.ftLastAccessTime, wszBuf, _countof(wszBuf)))
545                 SetDlgItemTextW(hwndDlg, 14019, wszBuf);
546 
547             if (GetFileTimeString(&FileInfo.ftLastWriteTime, wszBuf, _countof(wszBuf)))
548                 SetDlgItemTextW(hwndDlg, 14017, wszBuf);
549 
550             /* Update size of file */
551             ULARGE_INTEGER FileSize;
552             FileSize.u.LowPart = FileInfo.nFileSizeLow;
553             FileSize.u.HighPart = FileInfo.nFileSizeHigh;
554             if (SH_FormatFileSizeWithBytes(&FileSize, wszBuf, _countof(wszBuf)))
555                 SetDlgItemTextW(hwndDlg, 14011, wszBuf);
556         }
557     }
558 
559     if (m_bDir)
560     {
561         /* For directories files have to be counted */
562 
563         _CountFolderAndFilesData *data = static_cast<_CountFolderAndFilesData*>(HeapAlloc(GetProcessHeap(), 0, sizeof(_CountFolderAndFilesData)));
564         data->This = this;
565         data->pwszBuf = static_cast<LPWSTR>(HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * MAX_PATH));
566         data->cchBufMax = MAX_PATH;
567         data->hwndDlg = hwndDlg;
568         this->AddRef();
569         StringCchCopyW(data->pwszBuf, MAX_PATH, m_wszPath);
570 
571         SHCreateThread(CFileDefExt::_CountFolderAndFilesThreadProc, data, NULL, NULL);
572 
573         /* Update size field */
574         if (SH_FormatFileSizeWithBytes(&m_DirSize, wszBuf, _countof(wszBuf)))
575             SetDlgItemTextW(hwndDlg, 14011, wszBuf);
576 
577         /* Display files and folders count */
578         WCHAR wszFormat[256];
579         LoadStringW(shell32_hInstance, IDS_FILE_FOLDER, wszFormat, _countof(wszFormat));
580         StringCchPrintfW(wszBuf, _countof(wszBuf), wszFormat, m_cFiles, m_cFolders);
581         SetDlgItemTextW(hwndDlg, 14027, wszBuf);
582     }
583 
584     /* Hide Advanced button. TODO: Implement advanced dialog and enable this button if filesystem supports compression or encryption */
585     ShowWindow(GetDlgItem(hwndDlg, 14028), SW_HIDE);
586 
587     return TRUE;
588 }
589 
590 /*************************************************************************
591  *
592  * CFileDefExt::InitGeneralPage [Internal]
593  *
594  * sets all file general properties in dialog
595  */
596 
597 BOOL
598 CFileDefExt::InitGeneralPage(HWND hwndDlg)
599 {
600     /* Set general text properties filename filelocation and icon */
601     InitFilePath(hwndDlg);
602 
603     /* Set file type and icon */
604     InitFileType(hwndDlg);
605 
606     /* Set open with application */
607     if (!m_bDir)
608     {
609         if (!PathIsExeW(m_wszPath))
610             InitOpensWithField(hwndDlg);
611         else
612         {
613             WCHAR wszBuf[MAX_PATH];
614             LoadStringW(shell32_hInstance, IDS_EXE_DESCRIPTION, wszBuf, _countof(wszBuf));
615             SetDlgItemTextW(hwndDlg, 14006, wszBuf);
616             ShowWindow(GetDlgItem(hwndDlg, 14024), SW_HIDE);
617             LPCWSTR pwszDescr = m_VerInfo.GetString(L"FileDescription");
618             if (pwszDescr)
619                 SetDlgItemTextW(hwndDlg, 14007, pwszDescr);
620             else
621             {
622                 StringCbCopyW(wszBuf, sizeof(wszBuf), PathFindFileNameW(m_wszPath));
623                 PathRemoveExtension(wszBuf);
624                 SetDlgItemTextW(hwndDlg, 14007, wszBuf);
625             }
626         }
627     }
628 
629     /* Set file created/modfied/accessed time, size and attributes */
630     InitFileAttr(hwndDlg);
631 
632     return TRUE;
633 }
634 
635 /*************************************************************************
636  *
637  * CFileDefExt::GeneralPageProc
638  *
639  * wnd proc of 'General' property sheet page
640  *
641  */
642 
643 INT_PTR CALLBACK
644 CFileDefExt::GeneralPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
645 {
646     switch (uMsg)
647     {
648         case WM_INITDIALOG:
649         {
650             LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam;
651 
652             if (ppsp == NULL || !ppsp->lParam)
653                 break;
654 
655             TRACE("WM_INITDIALOG hwnd %p lParam %p ppsplParam %S\n", hwndDlg, lParam, ppsp->lParam);
656 
657             CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
658             SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pFileDefExt);
659             pFileDefExt->InitGeneralPage(hwndDlg);
660             break;
661         }
662         case WM_COMMAND:
663             if (LOWORD(wParam) == 14024) /* Opens With - Change */
664             {
665                 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
666                 OPENASINFO oainfo;
667                 oainfo.pcszFile = pFileDefExt->m_wszPath;
668                 oainfo.pcszClass = NULL;
669                 oainfo.oaifInFlags = OAIF_REGISTER_EXT|OAIF_FORCE_REGISTRATION;
670                 return SUCCEEDED(SHOpenWithDialog(hwndDlg, &oainfo));
671             }
672             else if (LOWORD(wParam) == 14021 || LOWORD(wParam) == 14022 || LOWORD(wParam) == 14023) /* checkboxes */
673                 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
674             else if (LOWORD(wParam) == 14001) /* Name */
675             {
676                 if (HIWORD(wParam) == EN_CHANGE)
677                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
678             }
679             break;
680         case WM_NOTIFY:
681         {
682             LPPSHNOTIFY lppsn = (LPPSHNOTIFY)lParam;
683             if (lppsn->hdr.code == PSN_APPLY)
684             {
685                 CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
686 
687                 /* Update attributes first */
688                 DWORD dwAttr = GetFileAttributesW(pFileDefExt->m_wszPath);
689                 if (dwAttr)
690                 {
691                     dwAttr &= ~(FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_ARCHIVE);
692 
693                     if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14021, BM_GETCHECK, 0, 0))
694                         dwAttr |= FILE_ATTRIBUTE_READONLY;
695                     if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14022, BM_GETCHECK, 0, 0))
696                         dwAttr |= FILE_ATTRIBUTE_HIDDEN;
697                     if (BST_CHECKED == SendDlgItemMessageW(hwndDlg, 14023, BM_GETCHECK, 0, 0))
698                         dwAttr |= FILE_ATTRIBUTE_ARCHIVE;
699 
700                     if (!SetFileAttributesW(pFileDefExt->m_wszPath, dwAttr))
701                         ERR("SetFileAttributesW failed\n");
702                 }
703 
704                 /* Update filename now */
705                 WCHAR wszBuf[MAX_PATH];
706                 StringCchCopyW(wszBuf, _countof(wszBuf), pFileDefExt->m_wszPath);
707                 LPWSTR pwszFilename = PathFindFileNameW(wszBuf);
708                 UINT cchFilenameMax = _countof(wszBuf) - (pwszFilename - wszBuf);
709                 if (GetDlgItemTextW(hwndDlg, 14001, pwszFilename, cchFilenameMax))
710                 {
711                     if (!MoveFileW(pFileDefExt->m_wszPath, wszBuf))
712                         ERR("MoveFileW failed\n");
713                 }
714 
715                 SetWindowLongPtr(hwndDlg, DWL_MSGRESULT, PSNRET_NOERROR);
716                 return TRUE;
717             }
718             break;
719         }
720         default:
721             break;
722     }
723 
724     return FALSE;
725 }
726 
727 /*************************************************************************
728  *
729  * CFileDefExt::InitVersionPage [Internal]
730  *
731  * sets all file version properties in dialog
732  */
733 
734 BOOL
735 CFileDefExt::InitVersionPage(HWND hwndDlg)
736 {
737     /* Get fixed info */
738     VS_FIXEDFILEINFO *pInfo = m_VerInfo.GetFixedInfo();
739     if (pInfo)
740     {
741         WCHAR wszVersion[256];
742         swprintf(wszVersion, L"%u.%u.%u.%u", HIWORD(pInfo->dwFileVersionMS),
743                  LOWORD(pInfo->dwFileVersionMS),
744                  HIWORD(pInfo->dwFileVersionLS),
745                  LOWORD(pInfo->dwFileVersionLS));
746         TRACE("MS %x LS %x ver %s \n", pInfo->dwFileVersionMS, pInfo->dwFileVersionLS, debugstr_w(wszVersion));
747         SetDlgItemTextW(hwndDlg, 14001, wszVersion);
748     }
749 
750     /* Update labels */
751     SetVersionLabel(hwndDlg, 14003, L"FileDescription");
752     SetVersionLabel(hwndDlg, 14005, L"LegalCopyright");
753 
754     /* Add items to listbox */
755     AddVersionString(hwndDlg, L"CompanyName");
756     LPCWSTR pwszLang = m_VerInfo.GetLangName();
757     if (pwszLang)
758     {
759         HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
760         UINT Index = SendMessageW(hDlgCtrl, LB_ADDSTRING, (WPARAM)-1, (LPARAM)L"Language");
761         SendMessageW(hDlgCtrl, LB_SETITEMDATA, (WPARAM)Index, (LPARAM)(WCHAR *)pwszLang);
762     }
763     AddVersionString(hwndDlg, L"ProductName");
764     AddVersionString(hwndDlg, L"InternalName");
765     AddVersionString(hwndDlg, L"OriginalFilename");
766     AddVersionString(hwndDlg, L"FileVersion");
767     AddVersionString(hwndDlg, L"ProductVersion");
768 
769     /* Attach file version to dialog window */
770     SetWindowLongPtr(hwndDlg, DWL_USER, (LONG_PTR)this);
771 
772     /* Select first item */
773     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
774     SendMessageW(hDlgCtrl, LB_SETCURSEL, 0, 0);
775     LPCWSTR pwszText = (LPCWSTR)SendMessageW(hDlgCtrl, LB_GETITEMDATA, (WPARAM)0, (LPARAM)NULL);
776     if (pwszText && pwszText != (LPCWSTR)LB_ERR)
777         SetDlgItemTextW(hwndDlg, 14010, pwszText);
778 
779     return TRUE;
780 }
781 
782 /*************************************************************************
783  *
784  * CFileDefExt::SetVersionLabel [Internal]
785  *
786  * retrieves a version string and uses it to set label text
787  */
788 
789 BOOL
790 CFileDefExt::SetVersionLabel(HWND hwndDlg, DWORD idCtrl, LPCWSTR pwszName)
791 {
792     if (hwndDlg == NULL || pwszName == NULL)
793         return FALSE;
794 
795     LPCWSTR pwszValue = m_VerInfo.GetString(pwszName);
796     if (pwszValue)
797     {
798         /* file description property */
799         TRACE("%s :: %s\n", debugstr_w(pwszName), debugstr_w(pwszValue));
800         SetDlgItemTextW(hwndDlg, idCtrl, pwszValue);
801         return TRUE;
802     }
803 
804     return FALSE;
805 }
806 
807 /*************************************************************************
808  *
809  * CFileDefExt::AddVersionString [Internal]
810  *
811  * retrieves a version string and adds it to listbox
812  */
813 
814 BOOL
815 CFileDefExt::AddVersionString(HWND hwndDlg, LPCWSTR pwszName)
816 {
817     TRACE("pwszName %s, hwndDlg %p\n", debugstr_w(pwszName), hwndDlg);
818 
819     if (hwndDlg == NULL || pwszName == NULL)
820         return FALSE;
821 
822     LPCWSTR pwszValue = m_VerInfo.GetString(pwszName);
823     if (pwszValue)
824     {
825         /* listbox name property */
826         HWND hDlgCtrl = GetDlgItem(hwndDlg, 14009);
827         TRACE("%s :: %s\n", debugstr_w(pwszName), debugstr_w(pwszValue));
828         UINT Index = SendMessageW(hDlgCtrl, LB_ADDSTRING, (WPARAM) -1, (LPARAM)pwszName);
829         SendMessageW(hDlgCtrl, LB_SETITEMDATA, (WPARAM)Index, (LPARAM)(WCHAR *)pwszValue);
830         return TRUE;
831     }
832 
833     return FALSE;
834 }
835 
836 /*************************************************************************
837  *
838  * CFileDefExt::VersionPageProc
839  *
840  * wnd proc of 'Version' property sheet page
841  */
842 
843 INT_PTR CALLBACK
844 CFileDefExt::VersionPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
845 {
846     switch (uMsg)
847     {
848         case WM_INITDIALOG:
849         {
850             LPPROPSHEETPAGE ppsp = (LPPROPSHEETPAGE)lParam;
851 
852             if (ppsp == NULL || !ppsp->lParam)
853                 break;
854 
855             TRACE("WM_INITDIALOG hwnd %p lParam %p ppsplParam %x\n", hwndDlg, lParam, ppsp->lParam);
856 
857             CFileDefExt *pFileDefExt = reinterpret_cast<CFileDefExt *>(ppsp->lParam);
858             return pFileDefExt->InitVersionPage(hwndDlg);
859         }
860         case WM_COMMAND:
861             if (LOWORD(wParam) == 14009 && HIWORD(wParam) == LBN_SELCHANGE)
862             {
863                 HWND hDlgCtrl = (HWND)lParam;
864 
865                 LRESULT Index = SendMessageW(hDlgCtrl, LB_GETCURSEL, (WPARAM)NULL, (LPARAM)NULL);
866                 if (Index == LB_ERR)
867                     break;
868 
869                 LPCWSTR pwszData = (LPCWSTR)SendMessageW(hDlgCtrl, LB_GETITEMDATA, (WPARAM)Index, (LPARAM)NULL);
870                 if (pwszData == NULL)
871                     break;
872 
873                 TRACE("hDlgCtrl %x string %s\n", hDlgCtrl, debugstr_w(pwszData));
874                 SetDlgItemTextW(hwndDlg, 14010, pwszData);
875 
876                 return TRUE;
877             }
878             break;
879         case WM_DESTROY:
880             break;
881         default:
882             break;
883     }
884 
885     return FALSE;
886 }
887 
888 CFileDefExt::CFileDefExt():
889     m_bDir(FALSE), m_cFiles(0), m_cFolders(0)
890 {
891     m_wszPath[0] = L'\0';
892     m_DirSize.QuadPart = 0ull;
893 }
894 
895 CFileDefExt::~CFileDefExt()
896 {
897 
898 }
899 
900 HRESULT WINAPI
901 CFileDefExt::Initialize(LPCITEMIDLIST pidlFolder, IDataObject *pDataObj, HKEY hkeyProgID)
902 {
903     FORMATETC format;
904     STGMEDIUM stgm;
905     HRESULT hr;
906 
907     TRACE("%p %p %p %p\n", this, pidlFolder, pDataObj, hkeyProgID);
908 
909     if (!pDataObj)
910         return E_FAIL;
911 
912     format.cfFormat = CF_HDROP;
913     format.ptd = NULL;
914     format.dwAspect = DVASPECT_CONTENT;
915     format.lindex = -1;
916     format.tymed = TYMED_HGLOBAL;
917 
918     hr = pDataObj->GetData(&format, &stgm);
919     if (FAILED(hr))
920         return hr;
921 
922     if (!DragQueryFileW((HDROP)stgm.hGlobal, 0, m_wszPath, _countof(m_wszPath)))
923     {
924         ERR("DragQueryFileW failed\n");
925         ReleaseStgMedium(&stgm);
926         return E_FAIL;
927     }
928 
929     ReleaseStgMedium(&stgm);
930 
931     TRACE("File properties %ls\n", m_wszPath);
932     m_bDir = PathIsDirectoryW(m_wszPath) ? TRUE : FALSE;
933     if (!m_bDir)
934         m_VerInfo.Load(m_wszPath);
935 
936     return S_OK;
937 }
938 
939 HRESULT WINAPI
940 CFileDefExt::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
941 {
942     UNIMPLEMENTED;
943     return E_NOTIMPL;
944 }
945 
946 HRESULT WINAPI
947 CFileDefExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
948 {
949     UNIMPLEMENTED;
950     return E_NOTIMPL;
951 }
952 
953 HRESULT WINAPI
954 CFileDefExt::GetCommandString(UINT_PTR idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax)
955 {
956     UNIMPLEMENTED;
957     return E_NOTIMPL;
958 }
959 
960 HRESULT WINAPI
961 CFileDefExt::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
962 {
963     HPROPSHEETPAGE hPage;
964     WORD wResId = m_bDir ? IDD_FOLDER_PROPERTIES : IDD_FILE_PROPERTIES;
965 
966     hPage = SH_CreatePropertySheetPage(wResId,
967                                        GeneralPageProc,
968                                        (LPARAM)this,
969                                        NULL);
970     if (hPage)
971         pfnAddPage(hPage, lParam);
972 
973     if (!m_bDir && GetFileVersionInfoSizeW(m_wszPath, NULL))
974     {
975         hPage = SH_CreatePropertySheetPage(IDD_FILE_VERSION,
976                                             VersionPageProc,
977                                             (LPARAM)this,
978                                             NULL);
979         if (hPage)
980             pfnAddPage(hPage, lParam);
981     }
982 
983     return S_OK;
984 }
985 
986 HRESULT WINAPI
987 CFileDefExt::ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam)
988 {
989     UNIMPLEMENTED;
990     return E_NOTIMPL;
991 }
992 
993 HRESULT WINAPI
994 CFileDefExt::SetSite(IUnknown *punk)
995 {
996     UNIMPLEMENTED;
997     return E_NOTIMPL;
998 }
999 
1000 HRESULT WINAPI
1001 CFileDefExt::GetSite(REFIID iid, void **ppvSite)
1002 {
1003     UNIMPLEMENTED;
1004     return E_NOTIMPL;
1005 }
1006 
1007 DWORD WINAPI
1008 CFileDefExt::_CountFolderAndFilesThreadProc(LPVOID lpParameter)
1009 {
1010     _CountFolderAndFilesData *data = static_cast<_CountFolderAndFilesData*>(lpParameter);
1011     DWORD ticks = 0;
1012     data->This->CountFolderAndFiles(data->hwndDlg, data->pwszBuf, data->cchBufMax, &ticks);
1013 
1014     //Release the CFileDefExt and data object holds in the copying thread.
1015     data->This->Release();
1016     HeapFree(GetProcessHeap(), 0, data->pwszBuf);
1017     HeapFree(GetProcessHeap(), 0, data);
1018 
1019     return 0;
1020 }
1021 
1022 BOOL
1023 CFileDefExt::CountFolderAndFiles(HWND hwndDlg, LPWSTR pwszBuf, UINT cchBufMax, DWORD *ticks)
1024 {
1025     /* Find filename position */
1026     UINT cchBuf = wcslen(pwszBuf);
1027     WCHAR *pwszFilename = pwszBuf + cchBuf;
1028     size_t cchFilenameMax = cchBufMax - cchBuf;
1029     if (!cchFilenameMax)
1030         return FALSE;
1031     *(pwszFilename++) = '\\';
1032     --cchFilenameMax;
1033 
1034     /* Find all files, FIXME: shouldn't be "*"? */
1035     StringCchCopyW(pwszFilename, cchFilenameMax, L"*");
1036 
1037     WIN32_FIND_DATAW wfd;
1038     HANDLE hFind = FindFirstFileW(pwszBuf, &wfd);
1039     if (hFind == INVALID_HANDLE_VALUE)
1040     {
1041         ERR("FindFirstFileW %ls failed\n", pwszBuf);
1042         return FALSE;
1043     }
1044 
1045     BOOL root = FALSE;
1046     if (*ticks == 0) {
1047         *ticks = GetTickCount();
1048         root = TRUE;
1049     }
1050 
1051     do
1052     {
1053         if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1054         {
1055             /* Don't process "." and ".." items */
1056             if (!wcscmp(wfd.cFileName, L".") || !wcscmp(wfd.cFileName, L".."))
1057                 continue;
1058 
1059             ++m_cFolders;
1060 
1061             StringCchCopyW(pwszFilename, cchFilenameMax, wfd.cFileName);
1062             CountFolderAndFiles(hwndDlg, pwszBuf, cchBufMax, ticks);
1063         }
1064         else
1065         {
1066             m_cFiles++;
1067 
1068             ULARGE_INTEGER FileSize;
1069             FileSize.u.LowPart  = wfd.nFileSizeLow;
1070             FileSize.u.HighPart = wfd.nFileSizeHigh;
1071             m_DirSize.QuadPart += FileSize.QuadPart;
1072         }
1073         if (GetTickCount() - *ticks > (DWORD) 300)
1074         {
1075             /* FIXME Using IsWindow is generally ill advised */
1076             if (IsWindow(hwndDlg))
1077             {
1078                 WCHAR wszBuf[MAX_PATH];
1079 
1080                 if (SH_FormatFileSizeWithBytes(&m_DirSize, wszBuf, _countof(wszBuf)))
1081                     SetDlgItemTextW(hwndDlg, 14011, wszBuf);
1082 
1083                 /* Display files and folders count */
1084                 WCHAR wszFormat[256];
1085                 LoadStringW(shell32_hInstance, IDS_FILE_FOLDER, wszFormat, _countof(wszFormat));
1086                 StringCchPrintfW(wszBuf, _countof(wszBuf), wszFormat, m_cFiles, m_cFolders);
1087                 SetDlgItemTextW(hwndDlg, 14027, wszBuf);
1088                 *ticks = GetTickCount();
1089             }
1090             else
1091                 break;
1092         }
1093     } while(FindNextFileW(hFind, &wfd));
1094 
1095     if (root && IsWindow(hwndDlg))
1096     {
1097         WCHAR wszBuf[MAX_PATH];
1098 
1099         if (SH_FormatFileSizeWithBytes(&m_DirSize, wszBuf, _countof(wszBuf)))
1100             SetDlgItemTextW(hwndDlg, 14011, wszBuf);
1101 
1102         /* Display files and folders count */
1103         WCHAR wszFormat[256];
1104         LoadStringW(shell32_hInstance, IDS_FILE_FOLDER, wszFormat, _countof(wszFormat));
1105         StringCchPrintfW(wszBuf, _countof(wszBuf), wszFormat, m_cFiles, m_cFolders);
1106         SetDlgItemTextW(hwndDlg, 14027, wszBuf);
1107     }
1108 
1109     FindClose(hFind);
1110     return TRUE;
1111 }
1112