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