1 /*
2  * Trash virtual folder support. The trashing engine is implemented in trash.c
3  *
4  * Copyright (C) 2006 Mikolaj Zalewski
5  * Copyright (C) 2009 Andrew Hill
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(CRecycleBin);
25 
26 typedef struct
27 {
28     ULARGE_INTEGER FreeBytesAvailable;
29     DWORD dwSerial;
30     DWORD dwMaxCapacity;
31     DWORD dwNukeOnDelete;
32 } DRIVE_ITEM_CONTEXT, *PDRIVE_ITEM_CONTEXT;
33 
34 static void toggleNukeOnDeleteOption(HWND hwndDlg, BOOL bEnable)
35 {
36     if (bEnable)
37     {
38         SendDlgItemMessage(hwndDlg, 14001, BM_SETCHECK, BST_UNCHECKED, 0);
39         EnableWindow(GetDlgItem(hwndDlg, 14002), FALSE);
40         SendDlgItemMessage(hwndDlg, 14003, BM_SETCHECK, BST_CHECKED, 0);
41     }
42     else
43     {
44         SendDlgItemMessage(hwndDlg, 14001, BM_SETCHECK, BST_CHECKED, 0);
45         EnableWindow(GetDlgItem(hwndDlg, 14002), TRUE);
46         SendDlgItemMessage(hwndDlg, 14003, BM_SETCHECK, BST_UNCHECKED, 0);
47     }
48 }
49 
50 static VOID
51 InitializeRecycleBinDlg(HWND hwndDlg, WCHAR DefaultDrive)
52 {
53     WCHAR szDrive[] = L"A:\\";
54     DWORD dwDrives;
55     WCHAR szName[100];
56     WCHAR szVolume[100];
57     DWORD MaxComponent, Flags;
58     DWORD dwSerial;
59     LVCOLUMNW lc;
60     HWND hDlgCtrl;
61     LVITEMW li;
62     INT itemCount;
63     ULARGE_INTEGER TotalNumberOfFreeBytes, TotalNumberOfBytes, FreeBytesAvailable;
64     RECT rect;
65     int columnSize;
66     int defIndex = 0;
67     DWORD dwSize;
68     PDRIVE_ITEM_CONTEXT pItem = NULL, pDefault = NULL, pFirst = NULL;
69 
70     hDlgCtrl = GetDlgItem(hwndDlg, 14000);
71 
72     if (!LoadStringW(shell32_hInstance, IDS_RECYCLEBIN_LOCATION, szVolume, _countof(szVolume)))
73         szVolume[0] = 0;
74 
75     GetClientRect(hDlgCtrl, &rect);
76 
77     ZeroMemory(&lc, sizeof(lc));
78     lc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_FMT;
79 
80     columnSize = 140; //FIXME
81     lc.iSubItem   = 0;
82     lc.fmt = LVCFMT_FIXED_WIDTH;
83     lc.cx         = columnSize;
84     lc.cchTextMax = wcslen(szVolume);
85     lc.pszText    = szVolume;
86     (void)SendMessageW(hDlgCtrl, LVM_INSERTCOLUMNW, 0, (LPARAM)&lc);
87 
88     if (!LoadStringW(shell32_hInstance, IDS_RECYCLEBIN_DISKSPACE, szVolume, _countof(szVolume)))
89         szVolume[0] = 0;
90 
91     lc.iSubItem   = 1;
92     lc.cx         = rect.right - rect.left - columnSize;
93     lc.cchTextMax = wcslen(szVolume);
94     lc.pszText    = szVolume;
95     (void)SendMessageW(hDlgCtrl, LVM_INSERTCOLUMNW, 1, (LPARAM)&lc);
96 
97     dwDrives = GetLogicalDrives();
98     itemCount = 0;
99     do
100     {
101         if (dwDrives & 0x1)
102         {
103             UINT Type = GetDriveTypeW(szDrive);
104             if (Type == DRIVE_FIXED) //FIXME
105             {
106                 if (!GetVolumeInformationW(szDrive, szName, _countof(szName), &dwSerial, &MaxComponent, &Flags, NULL, 0))
107                 {
108                     szName[0] = 0;
109                     dwSerial = -1;
110                 }
111 
112                 swprintf(szVolume, L"%s (%c:)", szName, szDrive[0]);
113                 ZeroMemory(&li, sizeof(li));
114                 li.mask = LVIF_TEXT | LVIF_PARAM;
115                 li.iSubItem = 0;
116                 li.pszText = szVolume;
117                 li.iItem = itemCount;
118                 SendMessageW(hDlgCtrl, LVM_INSERTITEMW, 0, (LPARAM)&li);
119                 if (GetDiskFreeSpaceExW(szDrive, &FreeBytesAvailable, &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
120                 {
121                     if (StrFormatByteSizeW(TotalNumberOfFreeBytes.QuadPart, szVolume, _countof(szVolume)))
122                     {
123                         pItem = (DRIVE_ITEM_CONTEXT *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DRIVE_ITEM_CONTEXT));
124                         if (pItem)
125                         {
126                             pItem->FreeBytesAvailable = FreeBytesAvailable;
127                             pItem->dwSerial = dwSerial;
128 
129                             swprintf(szName, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume\\%04X-%04X", LOWORD(dwSerial), HIWORD(dwSerial));
130 
131                             dwSize = sizeof(DWORD);
132                             RegGetValueW(HKEY_CURRENT_USER, szName, L"MaxCapacity", RRF_RT_DWORD, NULL, &pItem->dwMaxCapacity, &dwSize);
133 
134                             /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
135                             FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
136                             pItem->dwMaxCapacity = min(pItem->dwMaxCapacity, FreeBytesAvailable.LowPart);
137 
138                             dwSize = sizeof(DWORD);
139                             RegGetValueW(HKEY_CURRENT_USER, szName, L"NukeOnDelete", RRF_RT_DWORD, NULL, &pItem->dwNukeOnDelete, &dwSize);
140 
141                             li.mask = LVIF_PARAM;
142                             li.lParam = (LPARAM)pItem;
143                             (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
144                             if (szDrive[0] == DefaultDrive)
145                             {
146                                 defIndex = itemCount;
147                                 pDefault = pItem;
148                             }
149                         }
150                         if (!pFirst)
151                             pFirst = pItem;
152 
153                         li.mask = LVIF_TEXT;
154                         li.iSubItem = 1;
155                         li.pszText = szVolume;
156                         li.iItem = itemCount;
157                         (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
158                     }
159                 }
160                 itemCount++;
161             }
162         }
163         szDrive[0]++;
164         dwDrives = (dwDrives >> 1);
165     } while (dwDrives);
166 
167     if (!pDefault)
168         pDefault = pFirst;
169     if (pDefault)
170     {
171         toggleNukeOnDeleteOption(hwndDlg, pDefault->dwNukeOnDelete);
172         SetDlgItemInt(hwndDlg, 14002, pDefault->dwMaxCapacity, FALSE);
173     }
174     ZeroMemory(&li, sizeof(li));
175     li.mask = LVIF_STATE;
176     li.stateMask = (UINT)-1;
177     li.state = LVIS_FOCUSED | LVIS_SELECTED;
178     li.iItem = defIndex;
179     (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
180 }
181 
182 static BOOL StoreDriveSettings(HWND hwndDlg)
183 {
184     int iCount, iIndex;
185     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000);
186     LVITEMW li;
187     PDRIVE_ITEM_CONTEXT pItem;
188     HKEY hKey, hSubKey;
189     WCHAR szSerial[20];
190     DWORD dwSize;
191 
192     if (RegCreateKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
193         return FALSE;
194 
195     iCount = ListView_GetItemCount(hDlgCtrl);
196 
197     ZeroMemory(&li, sizeof(li));
198     li.mask = LVIF_PARAM;
199 
200     for (iIndex = 0; iIndex < iCount; iIndex++)
201     {
202         li.iItem = iIndex;
203         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)&li))
204         {
205             pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
206             swprintf(szSerial, L"%04X-%04X", LOWORD(pItem->dwSerial), HIWORD(pItem->dwSerial));
207             if (RegCreateKeyExW(hKey, szSerial, 0, NULL, 0, KEY_WRITE, NULL, &hSubKey, NULL) == ERROR_SUCCESS)
208             {
209                 dwSize = sizeof(DWORD);
210                 RegSetValueExW(hSubKey, L"MaxCapacity", 0, REG_DWORD, (LPBYTE)&pItem->dwMaxCapacity, dwSize);
211                 dwSize = sizeof(DWORD);
212                 RegSetValueExW(hSubKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&pItem->dwNukeOnDelete, dwSize);
213                 RegCloseKey(hSubKey);
214             }
215         }
216     }
217     RegCloseKey(hKey);
218     return TRUE;
219 }
220 
221 static VOID FreeDriveItemContext(HWND hwndDlg)
222 {
223     int iCount, iIndex;
224     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000);
225     LVITEMW li;
226 
227     iCount = ListView_GetItemCount(hDlgCtrl);
228 
229     ZeroMemory(&li, sizeof(li));
230     li.mask = LVIF_PARAM;
231 
232     for (iIndex = 0; iIndex < iCount; iIndex++)
233     {
234         li.iItem = iIndex;
235         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)&li))
236         {
237             HeapFree(GetProcessHeap(), 0, (PVOID)li.lParam);
238         }
239     }
240 }
241 
242 static INT
243 GetDefaultItem(HWND hwndDlg, LVITEMW* li)
244 {
245     HWND hDlgCtrl;
246     UINT iItemCount, iIndex;
247 
248     hDlgCtrl = GetDlgItem(hwndDlg, 14000);
249     if (!hDlgCtrl)
250         return -1;
251 
252     iItemCount = ListView_GetItemCount(hDlgCtrl);
253     if (!iItemCount)
254         return -1;
255 
256     ZeroMemory(li, sizeof(LVITEMW));
257     li->mask = LVIF_PARAM | LVIF_STATE;
258     li->stateMask = (UINT)-1;
259     for (iIndex = 0; iIndex < iItemCount; iIndex++)
260     {
261         li->iItem = iIndex;
262         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)li))
263         {
264             if (li->state & LVIS_SELECTED)
265                 return iIndex;
266         }
267     }
268     return -1;
269 }
270 
271 static INT_PTR CALLBACK
272 RecycleBinDlg(
273     HWND hwndDlg,
274     UINT uMsg,
275     WPARAM wParam,
276     LPARAM lParam)
277 {
278     LPPSHNOTIFY lppsn;
279     LPNMLISTVIEW lppl;
280     LVITEMW li;
281     PDRIVE_ITEM_CONTEXT pItem;
282     BOOL bSuccess;
283     UINT uResult;
284     PROPSHEETPAGE * page;
285     DWORD dwStyle;
286     ULARGE_INTEGER FreeBytesAvailable;
287 
288     switch(uMsg)
289     {
290         case WM_INITDIALOG:
291             page = (PROPSHEETPAGE*)lParam;
292             InitializeRecycleBinDlg(hwndDlg, (WCHAR)page->lParam);
293             dwStyle = (DWORD) SendDlgItemMessage(hwndDlg, 14000, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
294             dwStyle = dwStyle | LVS_EX_FULLROWSELECT;
295             SendDlgItemMessage(hwndDlg, 14000, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, dwStyle);
296             if (GetDlgCtrlID((HWND)wParam) != 14000)
297             {
298                 SetFocus(GetDlgItem(hwndDlg, 14000));
299                 return FALSE;
300             }
301             return TRUE;
302         case WM_COMMAND:
303             switch(LOWORD(wParam))
304             {
305                 case 14001:
306                     toggleNukeOnDeleteOption(hwndDlg, FALSE);
307                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
308                     break;
309                 case 14002:
310                     if (HIWORD(wParam) == EN_CHANGE)
311                         PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
312                     break;
313                 case 14003:
314                     toggleNukeOnDeleteOption(hwndDlg, TRUE);
315                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
316                     break;
317                 case 14004:
318                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
319                     break;
320             }
321             break;
322         case WM_NOTIFY:
323             lppsn = (LPPSHNOTIFY) lParam;
324             lppl = (LPNMLISTVIEW) lParam;
325             if (lppsn->hdr.code == PSN_APPLY)
326             {
327                 if (GetDefaultItem(hwndDlg, &li) > -1)
328                 {
329                     pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
330                     if (pItem)
331                     {
332                         uResult = GetDlgItemInt(hwndDlg, 14002, &bSuccess, FALSE);
333                         if (bSuccess)
334                         {
335                             /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
336                             FreeBytesAvailable = pItem->FreeBytesAvailable;
337                             FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
338                             pItem->dwMaxCapacity = min(uResult, FreeBytesAvailable.LowPart);
339                             SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
340                         }
341                         if (SendDlgItemMessageW(hwndDlg, 14003, BM_GETCHECK, 0, 0) == BST_CHECKED)
342                             pItem->dwNukeOnDelete = TRUE;
343                         else
344                             pItem->dwNukeOnDelete = FALSE;
345                     }
346                 }
347                 if (StoreDriveSettings(hwndDlg))
348                 {
349                     SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR );
350                     return TRUE;
351                 }
352             }
353             else if (lppl->hdr.code == LVN_ITEMCHANGING)
354             {
355                 ZeroMemory(&li, sizeof(li));
356                 li.mask = LVIF_PARAM;
357                 li.iItem = lppl->iItem;
358                 if (!SendMessageW(lppl->hdr.hwndFrom, LVM_GETITEMW, 0, (LPARAM)&li))
359                     return TRUE;
360 
361                 pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
362                 if (!pItem)
363                     return TRUE;
364 
365                 if (!(lppl->uOldState & LVIS_FOCUSED) && (lppl->uNewState & LVIS_FOCUSED))
366                 {
367                     /* new focused item */
368                     toggleNukeOnDeleteOption(lppl->hdr.hwndFrom, pItem->dwNukeOnDelete);
369                     SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
370                 }
371                 else if ((lppl->uOldState & LVIS_FOCUSED) && !(lppl->uNewState & LVIS_FOCUSED))
372                 {
373                     /* kill focus */
374                     uResult = GetDlgItemInt(hwndDlg, 14002, &bSuccess, FALSE);
375                     if (bSuccess)
376                     {
377                         /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
378                         FreeBytesAvailable = pItem->FreeBytesAvailable;
379                         FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
380                         pItem->dwMaxCapacity = min(uResult, FreeBytesAvailable.LowPart);
381                         SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
382                     }
383                     if (SendDlgItemMessageW(hwndDlg, 14003, BM_GETCHECK, 0, 0) == BST_CHECKED)
384                         pItem->dwNukeOnDelete = TRUE;
385                     else
386                         pItem->dwNukeOnDelete = FALSE;
387                 }
388                 return TRUE;
389 
390             }
391             break;
392         case WM_DESTROY:
393             FreeDriveItemContext(hwndDlg);
394             break;
395     }
396     return FALSE;
397 }
398 
399 BOOL SH_ShowRecycleBinProperties(WCHAR sDrive)
400 {
401     HPROPSHEETPAGE hpsp[1];
402     PROPSHEETHEADERW psh;
403     HPROPSHEETPAGE hprop;
404     INT_PTR ret;
405 
406     ZeroMemory(&psh, sizeof(psh));
407     psh.dwSize = sizeof(psh);
408     psh.dwFlags = PSP_DEFAULT | PSH_PROPTITLE;
409     psh.pszCaption = MAKEINTRESOURCEW(IDS_RECYCLEBIN_FOLDER_NAME);
410     psh.hwndParent = NULL;
411     psh.phpage = hpsp;
412     psh.hInstance = shell32_hInstance;
413 
414     hprop = SH_CreatePropertySheetPage(IDD_RECYCLE_BIN_PROPERTIES, RecycleBinDlg, (LPARAM)sDrive, NULL);
415     if (!hprop)
416     {
417         ERR("Failed to create property sheet\n");
418         return FALSE;
419     }
420     hpsp[psh.nPages] = hprop;
421     psh.nPages++;
422 
423     ret = PropertySheetW(&psh);
424     return (ret >= 0);
425 }
426