1 // PanelOperations.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../Common/DynamicBuffer.h"
6 #include "../../../Common/StringConvert.h"
7 #include "../../../Common/Wildcard.h"
8
9 #include "../../../Windows/COM.h"
10 #include "../../../Windows/FileName.h"
11 #include "../../../Windows/PropVariant.h"
12
13 #include "ComboDialog.h"
14
15 #include "FSFolder.h"
16 #include "FormatUtils.h"
17 #include "LangUtils.h"
18 #include "Panel.h"
19 #include "UpdateCallback100.h"
20
21 #include "resource.h"
22
23 using namespace NWindows;
24 using namespace NFile;
25 using namespace NName;
26
27 #ifndef _UNICODE
28 extern bool g_IsNT;
29 #endif
30
31 enum EFolderOpType
32 {
33 FOLDER_TYPE_CREATE_FOLDER = 0,
34 FOLDER_TYPE_DELETE = 1,
35 FOLDER_TYPE_RENAME = 2
36 };
37
38 class CThreadFolderOperations: public CProgressThreadVirt
39 {
40 HRESULT ProcessVirt();
41 public:
42 EFolderOpType OpType;
43 UString Name;
44 UInt32 Index;
45 CRecordVector<UInt32> Indices;
46
47 CMyComPtr<IFolderOperations> FolderOperations;
48 CMyComPtr<IProgress> UpdateCallback;
49 CUpdateCallback100Imp *UpdateCallbackSpec;
50
51 HRESULT Result;
52
CThreadFolderOperations(EFolderOpType opType)53 CThreadFolderOperations(EFolderOpType opType): OpType(opType), Result(E_FAIL) {}
54 HRESULT DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError);
55 };
56
ProcessVirt()57 HRESULT CThreadFolderOperations::ProcessVirt()
58 {
59 NCOM::CComInitializer comInitializer;
60 switch (OpType)
61 {
62 case FOLDER_TYPE_CREATE_FOLDER:
63 Result = FolderOperations->CreateFolder(Name, UpdateCallback);
64 break;
65 case FOLDER_TYPE_DELETE:
66 Result = FolderOperations->Delete(&Indices.Front(), Indices.Size(), UpdateCallback);
67 break;
68 case FOLDER_TYPE_RENAME:
69 Result = FolderOperations->Rename(Index, Name, UpdateCallback);
70 break;
71 default:
72 Result = E_FAIL;
73 }
74 return Result;
75 }
76
77
DoOperation(CPanel & panel,const UString & progressTitle,const UString & titleError)78 HRESULT CThreadFolderOperations::DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError)
79 {
80 UpdateCallbackSpec = new CUpdateCallback100Imp;
81 UpdateCallback = UpdateCallbackSpec;
82 UpdateCallbackSpec->ProgressDialog = &ProgressDialog;
83
84 ProgressDialog.WaitMode = true;
85 ProgressDialog.Sync.FinalMessage.ErrorMessage.Title = titleError;
86 Result = S_OK;
87
88 UpdateCallbackSpec->Init();
89
90 if (panel._parentFolders.Size() > 0)
91 {
92 const CFolderLink &fl = panel._parentFolders.Back();
93 UpdateCallbackSpec->PasswordIsDefined = fl.UsePassword;
94 UpdateCallbackSpec->Password = fl.Password;
95 }
96
97
98 ProgressDialog.MainWindow = panel._mainWindow; // panel.GetParent()
99 ProgressDialog.MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE);
100 ProgressDialog.MainAddTitle = progressTitle + L' ';
101
102 RINOK(Create(progressTitle, ProgressDialog.MainWindow));
103 return Result;
104 }
105
106 #ifndef _UNICODE
107 typedef int (WINAPI * SHFileOperationWP)(LPSHFILEOPSTRUCTW lpFileOp);
108 #endif
109
110 /*
111 void CPanel::MessageBoxErrorForUpdate(HRESULT errorCode, UINT resourceID)
112 {
113 if (errorCode == E_NOINTERFACE)
114 MessageBox_Error_UnsupportOperation();
115 else
116 MessageBox_Error_HRESULT_Caption(errorCode, LangString(resourceID));
117 }
118 */
119
DeleteItems(bool NON_CE_VAR (toRecycleBin))120 void CPanel::DeleteItems(bool NON_CE_VAR(toRecycleBin))
121 {
122 CDisableTimerProcessing disableTimerProcessing(*this);
123 CRecordVector<UInt32> indices;
124 GetOperatedItemIndices(indices);
125 if (indices.IsEmpty())
126 return;
127 CSelectedState state;
128 SaveSelectedState(state);
129
130 #ifndef UNDER_CE
131 // WM6 / SHFileOperationW doesn't ask user! So we use internal delete
132 if (IsFSFolder() && toRecycleBin)
133 {
134 bool useInternalDelete = false;
135 #ifndef _UNICODE
136 if (!g_IsNT)
137 {
138 CDynamicBuffer<CHAR> buffer;
139 FOR_VECTOR (i, indices)
140 {
141 const AString path (GetSystemString(GetItemFullPath(indices[i])));
142 buffer.AddData(path, path.Len() + 1);
143 }
144 *buffer.GetCurPtrAndGrow(1) = 0;
145 SHFILEOPSTRUCTA fo;
146 fo.hwnd = GetParent();
147 fo.wFunc = FO_DELETE;
148 fo.pFrom = (const CHAR *)buffer;
149 fo.pTo = 0;
150 fo.fFlags = 0;
151 if (toRecycleBin)
152 fo.fFlags |= FOF_ALLOWUNDO;
153 // fo.fFlags |= FOF_NOCONFIRMATION;
154 // fo.fFlags |= FOF_NOERRORUI;
155 // fo.fFlags |= FOF_SILENT;
156 // fo.fFlags |= FOF_WANTNUKEWARNING;
157 fo.fAnyOperationsAborted = FALSE;
158 fo.hNameMappings = 0;
159 fo.lpszProgressTitle = 0;
160 /* int res = */ ::SHFileOperationA(&fo);
161 }
162 else
163 #endif
164 {
165 CDynamicBuffer<WCHAR> buffer;
166 unsigned maxLen = 0;
167 const UString prefix = GetFsPath();
168 FOR_VECTOR (i, indices)
169 {
170 // L"\\\\?\\") doesn't work here.
171 const UString path = prefix + GetItemRelPath2(indices[i]);
172 if (path.Len() > maxLen)
173 maxLen = path.Len();
174 buffer.AddData(path, path.Len() + 1);
175 }
176 *buffer.GetCurPtrAndGrow(1) = 0;
177 if (maxLen >= MAX_PATH)
178 {
179 if (toRecycleBin)
180 {
181 MessageBox_Error_LangID(IDS_ERROR_LONG_PATH_TO_RECYCLE);
182 return;
183 }
184 useInternalDelete = true;
185 }
186 else
187 {
188 SHFILEOPSTRUCTW fo;
189 fo.hwnd = GetParent();
190 fo.wFunc = FO_DELETE;
191 fo.pFrom = (const WCHAR *)buffer;
192 fo.pTo = 0;
193 fo.fFlags = 0;
194 if (toRecycleBin)
195 fo.fFlags |= FOF_ALLOWUNDO;
196 fo.fAnyOperationsAborted = FALSE;
197 fo.hNameMappings = 0;
198 fo.lpszProgressTitle = 0;
199 // int res;
200 #ifdef _UNICODE
201 /* res = */ ::SHFileOperationW(&fo);
202 #else
203 SHFileOperationWP shFileOperationW = (SHFileOperationWP)
204 ::GetProcAddress(::GetModuleHandleW(L"shell32.dll"), "SHFileOperationW");
205 if (shFileOperationW == 0)
206 return;
207 /* res = */ shFileOperationW(&fo);
208 #endif
209 }
210 }
211 /*
212 if (fo.fAnyOperationsAborted)
213 MessageBox_Error_HRESULT_Caption(result, LangString(IDS_ERROR_DELETING));
214 */
215 if (!useInternalDelete)
216 {
217 RefreshListCtrl(state);
218 return;
219 }
220 }
221 #endif
222
223 // DeleteItemsInternal
224
225 if (!CheckBeforeUpdate(IDS_ERROR_DELETING))
226 return;
227
228 UInt32 titleID, messageID;
229 UString messageParam;
230 if (indices.Size() == 1)
231 {
232 int index = indices[0];
233 messageParam = GetItemRelPath2(index);
234 if (IsItem_Folder(index))
235 {
236 titleID = IDS_CONFIRM_FOLDER_DELETE;
237 messageID = IDS_WANT_TO_DELETE_FOLDER;
238 }
239 else
240 {
241 titleID = IDS_CONFIRM_FILE_DELETE;
242 messageID = IDS_WANT_TO_DELETE_FILE;
243 }
244 }
245 else
246 {
247 titleID = IDS_CONFIRM_ITEMS_DELETE;
248 messageID = IDS_WANT_TO_DELETE_ITEMS;
249 messageParam = NumberToString(indices.Size());
250 }
251 if (::MessageBoxW(GetParent(), MyFormatNew(messageID, messageParam), LangString(titleID), MB_OKCANCEL | MB_ICONQUESTION) != IDOK)
252 return;
253
254 CDisableNotify disableNotify(*this);
255 {
256 CThreadFolderOperations op(FOLDER_TYPE_DELETE);
257 op.FolderOperations = _folderOperations;
258 op.Indices = indices;
259 op.DoOperation(*this,
260 LangString(IDS_DELETING),
261 LangString(IDS_ERROR_DELETING));
262 }
263 RefreshTitleAlways();
264 RefreshListCtrl(state);
265 }
266
OnBeginLabelEdit(LV_DISPINFOW * lpnmh)267 BOOL CPanel::OnBeginLabelEdit(LV_DISPINFOW * lpnmh)
268 {
269 int realIndex = GetRealIndex(lpnmh->item);
270 if (realIndex == kParentIndex)
271 return TRUE;
272 if (IsThereReadOnlyFolder())
273 return TRUE;
274 return FALSE;
275 }
276
IsCorrectFsName(const UString & name)277 bool IsCorrectFsName(const UString &name)
278 {
279 const UString lastPart = name.Ptr(name.ReverseFind_PathSepar() + 1);
280 return
281 lastPart != L"." &&
282 lastPart != L"..";
283 }
284
285 bool CorrectFsPath(const UString &relBase, const UString &path, UString &result);
286
CorrectFsPath(const UString & path2,UString & result)287 bool CPanel::CorrectFsPath(const UString &path2, UString &result)
288 {
289 return ::CorrectFsPath(GetFsPath(), path2, result);
290 }
291
OnEndLabelEdit(LV_DISPINFOW * lpnmh)292 BOOL CPanel::OnEndLabelEdit(LV_DISPINFOW * lpnmh)
293 {
294 if (lpnmh->item.pszText == NULL)
295 return FALSE;
296 CDisableTimerProcessing disableTimerProcessing2(*this);
297
298 if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
299 return FALSE;
300
301 UString newName = lpnmh->item.pszText;
302 if (!IsCorrectFsName(newName))
303 {
304 MessageBox_Error_HRESULT(E_INVALIDARG);
305 return FALSE;
306 }
307
308 if (IsFSFolder())
309 {
310 UString correctName;
311 if (!CorrectFsPath(newName, correctName))
312 {
313 MessageBox_Error_HRESULT(E_INVALIDARG);
314 return FALSE;
315 }
316 newName = correctName;
317 }
318
319 SaveSelectedState(_selectedState);
320
321 int realIndex = GetRealIndex(lpnmh->item);
322 if (realIndex == kParentIndex)
323 return FALSE;
324 const UString prefix = GetItemPrefix(realIndex);
325
326
327 CDisableNotify disableNotify(*this);
328 {
329 CThreadFolderOperations op(FOLDER_TYPE_RENAME);
330 op.FolderOperations = _folderOperations;
331 op.Index = realIndex;
332 op.Name = newName;
333 /* HRESULTres = */ op.DoOperation(*this,
334 LangString(IDS_RENAMING),
335 LangString(IDS_ERROR_RENAMING));
336 // fixed in 9.26: we refresh list even after errors
337 // (it's more safe, since error can be at different stages, so list can be incorrect).
338 /*
339 if (res != S_OK)
340 return FALSE;
341 */
342 }
343
344 // Can't use RefreshListCtrl here.
345 // RefreshListCtrlSaveFocused();
346 _selectedState.FocusedName = prefix + newName;
347 _selectedState.FocusedName_Defined = true;
348 _selectedState.SelectFocused = true;
349
350 // We need clear all items to disable GetText before Reload:
351 // number of items can change.
352 // DeleteListItems();
353 // But seems it can still call GetText (maybe for current item)
354 // so we can't delete items.
355
356 _dontShowMode = true;
357
358 PostMsg(kReLoadMessage);
359 return TRUE;
360 }
361
362 bool Dlg_CreateFolder(HWND wnd, UString &destName);
363
CreateFolder()364 void CPanel::CreateFolder()
365 {
366 if (!CheckBeforeUpdate(IDS_CREATE_FOLDER_ERROR))
367 return;
368
369 CDisableTimerProcessing disableTimerProcessing2(*this);
370 CSelectedState state;
371 SaveSelectedState(state);
372
373 UString newName;
374 if (!Dlg_CreateFolder(GetParent(), newName))
375 return;
376
377 if (!IsCorrectFsName(newName))
378 {
379 MessageBox_Error_HRESULT(E_INVALIDARG);
380 return;
381 }
382
383 if (IsFSFolder())
384 {
385 UString correctName;
386 if (!CorrectFsPath(newName, correctName))
387 {
388 MessageBox_Error_HRESULT(E_INVALIDARG);
389 return;
390 }
391 newName = correctName;
392 }
393
394 HRESULT res;
395 CDisableNotify disableNotify(*this);
396 {
397 CThreadFolderOperations op(FOLDER_TYPE_CREATE_FOLDER);
398 op.FolderOperations = _folderOperations;
399 op.Name = newName;
400 res = op.DoOperation(*this,
401 LangString(IDS_CREATE_FOLDER),
402 LangString(IDS_CREATE_FOLDER_ERROR));
403 /*
404 // fixed for 9.26: we must refresh always
405 if (res != S_OK)
406 return;
407 */
408 }
409 if (res == S_OK)
410 {
411 int pos = newName.Find(WCHAR_PATH_SEPARATOR);
412 if (pos >= 0)
413 newName.DeleteFrom(pos);
414 if (!_mySelectMode)
415 state.SelectedNames.Clear();
416 state.FocusedName = newName;
417 state.FocusedName_Defined = true;
418 state.SelectFocused = true;
419 }
420 RefreshTitleAlways();
421 RefreshListCtrl(state);
422 }
423
CreateFile()424 void CPanel::CreateFile()
425 {
426 if (!CheckBeforeUpdate(IDS_CREATE_FILE_ERROR))
427 return;
428
429 CDisableTimerProcessing disableTimerProcessing2(*this);
430 CSelectedState state;
431 SaveSelectedState(state);
432 CComboDialog dlg;
433 LangString(IDS_CREATE_FILE, dlg.Title);
434 LangString(IDS_CREATE_FILE_NAME, dlg.Static);
435 LangString(IDS_CREATE_FILE_DEFAULT_NAME, dlg.Value);
436
437 if (dlg.Create(GetParent()) != IDOK)
438 return;
439
440 CDisableNotify disableNotify(*this);
441
442 UString newName = dlg.Value;
443
444 if (IsFSFolder())
445 {
446 UString correctName;
447 if (!CorrectFsPath(newName, correctName))
448 {
449 MessageBox_Error_HRESULT(E_INVALIDARG);
450 return;
451 }
452 newName = correctName;
453 }
454
455 HRESULT result = _folderOperations->CreateFile(newName, 0);
456 if (result != S_OK)
457 {
458 MessageBox_Error_HRESULT_Caption(result, LangString(IDS_CREATE_FILE_ERROR));
459 // MessageBoxErrorForUpdate(result, IDS_CREATE_FILE_ERROR);
460 return;
461 }
462 int pos = newName.Find(WCHAR_PATH_SEPARATOR);
463 if (pos >= 0)
464 newName.DeleteFrom(pos);
465 if (!_mySelectMode)
466 state.SelectedNames.Clear();
467 state.FocusedName = newName;
468 state.FocusedName_Defined = true;
469 state.SelectFocused = true;
470 RefreshListCtrl(state);
471 }
472
RenameFile()473 void CPanel::RenameFile()
474 {
475 if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
476 return;
477 int index = _listView.GetFocusedItem();
478 if (index >= 0)
479 _listView.EditLabel(index);
480 }
481
ChangeComment()482 void CPanel::ChangeComment()
483 {
484 if (!CheckBeforeUpdate(IDS_COMMENT))
485 return;
486 CDisableTimerProcessing disableTimerProcessing2(*this);
487 int index = _listView.GetFocusedItem();
488 if (index < 0)
489 return;
490 int realIndex = GetRealItemIndex(index);
491 if (realIndex == kParentIndex)
492 return;
493 CSelectedState state;
494 SaveSelectedState(state);
495 UString comment;
496 {
497 NCOM::CPropVariant propVariant;
498 if (_folder->GetProperty(realIndex, kpidComment, &propVariant) != S_OK)
499 return;
500 if (propVariant.vt == VT_BSTR)
501 comment = propVariant.bstrVal;
502 else if (propVariant.vt != VT_EMPTY)
503 return;
504 }
505 UString name = GetItemRelPath2(realIndex);
506 CComboDialog dlg;
507 dlg.Title = name;
508 dlg.Title += " : ";
509 AddLangString(dlg.Title, IDS_COMMENT);
510 dlg.Value = comment;
511 LangString(IDS_COMMENT2, dlg.Static);
512 if (dlg.Create(GetParent()) != IDOK)
513 return;
514 NCOM::CPropVariant propVariant = dlg.Value.Ptr();
515
516 CDisableNotify disableNotify(*this);
517 HRESULT result = _folderOperations->SetProperty(realIndex, kpidComment, &propVariant, NULL);
518 if (result != S_OK)
519 {
520 if (result == E_NOINTERFACE)
521 MessageBox_Error_UnsupportOperation();
522 else
523 MessageBox_Error_HRESULT_Caption(result, L"Set Comment Error");
524 }
525 RefreshListCtrl(state);
526 }
527