xref: /aosp_15_r20/external/lzma/CPP/7zip/UI/FileManager/PanelOperations.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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