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
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 (OpType)
59 {
60 case FOLDER_TYPE_CREATE_FOLDER:
61 return FolderOperations->CreateFolder(Name, UpdateCallback);
62 case FOLDER_TYPE_DELETE:
63 return FolderOperations->Delete(&Indices.Front(), 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.Size() > 0)
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 * SHFileOperationWP)(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 GetOperatedItemIndices(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 = 0;
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 = 0;
151 fo.lpszProgressTitle = 0;
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 = 0;
185 fo.fFlags = 0;
186 if (toRecycleBin)
187 fo.fFlags |= FOF_ALLOWUNDO;
188 fo.fAnyOperationsAborted = FALSE;
189 fo.hNameMappings = 0;
190 fo.lpszProgressTitle = 0;
191 // int res;
192 #ifdef _UNICODE
193 /* res = */ ::SHFileOperationW(&fo);
194 #else
195 SHFileOperationWP shFileOperationW = (SHFileOperationWP)
196 ::GetProcAddress(::GetModuleHandleW(L"shell32.dll"), "SHFileOperationW");
197 if (shFileOperationW == 0)
198 return;
199 /* res = */ shFileOperationW(&fo);
200 #endif
201 }
202 }
203 /*
204 if (fo.fAnyOperationsAborted)
205 MessageBox_Error_HRESULT_Caption(result, LangString(IDS_ERROR_DELETING));
206 */
207 if (!useInternalDelete)
208 {
209 RefreshListCtrl(state);
210 return;
211 }
212 }
213 #endif
214
215 // DeleteItemsInternal
216
217 if (!CheckBeforeUpdate(IDS_ERROR_DELETING))
218 return;
219
220 UInt32 titleID, messageID;
221 UString messageParam;
222 if (indices.Size() == 1)
223 {
224 int index = indices[0];
225 messageParam = GetItemRelPath2(index);
226 if (IsItem_Folder(index))
227 {
228 titleID = IDS_CONFIRM_FOLDER_DELETE;
229 messageID = IDS_WANT_TO_DELETE_FOLDER;
230 }
231 else
232 {
233 titleID = IDS_CONFIRM_FILE_DELETE;
234 messageID = IDS_WANT_TO_DELETE_FILE;
235 }
236 }
237 else
238 {
239 titleID = IDS_CONFIRM_ITEMS_DELETE;
240 messageID = IDS_WANT_TO_DELETE_ITEMS;
241 messageParam = NumberToString(indices.Size());
242 }
243 if (::MessageBoxW(GetParent(), MyFormatNew(messageID, messageParam), LangString(titleID), MB_OKCANCEL | MB_ICONQUESTION) != IDOK)
244 return;
245
246 CDisableNotify disableNotify(*this);
247 {
248 CThreadFolderOperations op(FOLDER_TYPE_DELETE);
249 op.FolderOperations = _folderOperations;
250 op.Indices = indices;
251 op.DoOperation(*this,
252 LangString(IDS_DELETING),
253 LangString(IDS_ERROR_DELETING));
254 }
255 RefreshTitleAlways();
256 RefreshListCtrl(state);
257 }
258
OnBeginLabelEdit(LV_DISPINFOW * lpnmh)259 BOOL CPanel::OnBeginLabelEdit(LV_DISPINFOW * lpnmh)
260 {
261 int realIndex = GetRealIndex(lpnmh->item);
262 if (realIndex == kParentIndex)
263 return TRUE;
264 if (IsThereReadOnlyFolder())
265 return TRUE;
266 return FALSE;
267 }
268
IsCorrectFsName(const UString & name)269 static bool IsCorrectFsName(const UString &name)
270 {
271 const UString lastPart = name.Ptr((unsigned)(name.ReverseFind_PathSepar() + 1));
272 return
273 lastPart != L"." &&
274 lastPart != L"..";
275 }
276
277 bool CorrectFsPath(const UString &relBase, const UString &path, UString &result);
278
CorrectFsPath(const UString & path2,UString & result)279 bool CPanel::CorrectFsPath(const UString &path2, UString &result)
280 {
281 return ::CorrectFsPath(GetFsPath(), path2, result);
282 }
283
OnEndLabelEdit(LV_DISPINFOW * lpnmh)284 BOOL CPanel::OnEndLabelEdit(LV_DISPINFOW * lpnmh)
285 {
286 if (lpnmh->item.pszText == NULL)
287 return FALSE;
288 CDisableTimerProcessing disableTimerProcessing2(*this);
289
290 if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
291 return FALSE;
292
293 UString newName = lpnmh->item.pszText;
294 if (!IsCorrectFsName(newName))
295 {
296 MessageBox_Error_HRESULT(E_INVALIDARG);
297 return FALSE;
298 }
299
300 if (IsFSFolder())
301 {
302 UString correctName;
303 if (!CorrectFsPath(newName, correctName))
304 {
305 MessageBox_Error_HRESULT(E_INVALIDARG);
306 return FALSE;
307 }
308 newName = correctName;
309 }
310
311 SaveSelectedState(_selectedState);
312
313 int realIndex = GetRealIndex(lpnmh->item);
314 if (realIndex == kParentIndex)
315 return FALSE;
316 const UString prefix = GetItemPrefix(realIndex);
317
318
319 CDisableNotify disableNotify(*this);
320 {
321 CThreadFolderOperations op(FOLDER_TYPE_RENAME);
322 op.FolderOperations = _folderOperations;
323 op.Index = realIndex;
324 op.Name = newName;
325 /* HRESULTres = */ op.DoOperation(*this,
326 LangString(IDS_RENAMING),
327 LangString(IDS_ERROR_RENAMING));
328 // fixed in 9.26: we refresh list even after errors
329 // (it's more safe, since error can be at different stages, so list can be incorrect).
330 /*
331 if (res != S_OK)
332 return FALSE;
333 */
334 }
335
336 // Can't use RefreshListCtrl here.
337 // RefreshListCtrlSaveFocused();
338 _selectedState.FocusedName = prefix + newName;
339 _selectedState.FocusedName_Defined = true;
340 _selectedState.SelectFocused = true;
341
342 // We need clear all items to disable GetText before Reload:
343 // number of items can change.
344 // DeleteListItems();
345 // But seems it can still call GetText (maybe for current item)
346 // so we can't delete items.
347
348 _dontShowMode = true;
349
350 PostMsg(kReLoadMessage);
351 return TRUE;
352 }
353
354 bool Dlg_CreateFolder(HWND wnd, UString &destName);
355
CreateFolder()356 void CPanel::CreateFolder()
357 {
358 if (IsHashFolder())
359 return;
360
361 if (!CheckBeforeUpdate(IDS_CREATE_FOLDER_ERROR))
362 return;
363
364 CDisableTimerProcessing disableTimerProcessing2(*this);
365 CSelectedState state;
366 SaveSelectedState(state);
367
368 UString newName;
369 if (!Dlg_CreateFolder(GetParent(), newName))
370 return;
371
372 if (!IsCorrectFsName(newName))
373 {
374 MessageBox_Error_HRESULT(E_INVALIDARG);
375 return;
376 }
377
378 if (IsFSFolder())
379 {
380 UString correctName;
381 if (!CorrectFsPath(newName, correctName))
382 {
383 MessageBox_Error_HRESULT(E_INVALIDARG);
384 return;
385 }
386 newName = correctName;
387 }
388
389 HRESULT res;
390 CDisableNotify disableNotify(*this);
391 {
392 CThreadFolderOperations op(FOLDER_TYPE_CREATE_FOLDER);
393 op.FolderOperations = _folderOperations;
394 op.Name = newName;
395 res = op.DoOperation(*this,
396 LangString(IDS_CREATE_FOLDER),
397 LangString(IDS_CREATE_FOLDER_ERROR));
398 /*
399 // fixed for 9.26: we must refresh always
400 if (res != S_OK)
401 return;
402 */
403 }
404 if (res == S_OK)
405 {
406 int pos = newName.Find(WCHAR_PATH_SEPARATOR);
407 if (pos >= 0)
408 newName.DeleteFrom((unsigned)(pos));
409 if (!_mySelectMode)
410 state.SelectedNames.Clear();
411 state.FocusedName = newName;
412 state.FocusedName_Defined = true;
413 state.SelectFocused = true;
414 }
415 RefreshTitleAlways();
416 RefreshListCtrl(state);
417 }
418
CreateFile()419 void CPanel::CreateFile()
420 {
421 if (IsHashFolder())
422 return;
423
424 if (!CheckBeforeUpdate(IDS_CREATE_FILE_ERROR))
425 return;
426
427 CDisableTimerProcessing disableTimerProcessing2(*this);
428 CSelectedState state;
429 SaveSelectedState(state);
430 CComboDialog dlg;
431 LangString(IDS_CREATE_FILE, dlg.Title);
432 LangString(IDS_CREATE_FILE_NAME, dlg.Static);
433 LangString(IDS_CREATE_FILE_DEFAULT_NAME, dlg.Value);
434
435 if (dlg.Create(GetParent()) != IDOK)
436 return;
437
438 CDisableNotify disableNotify(*this);
439
440 UString newName = dlg.Value;
441
442 if (IsFSFolder())
443 {
444 UString correctName;
445 if (!CorrectFsPath(newName, correctName))
446 {
447 MessageBox_Error_HRESULT(E_INVALIDARG);
448 return;
449 }
450 newName = correctName;
451 }
452
453 HRESULT result = _folderOperations->CreateFile(newName, 0);
454 if (result != S_OK)
455 {
456 MessageBox_Error_HRESULT_Caption(result, LangString(IDS_CREATE_FILE_ERROR));
457 // MessageBoxErrorForUpdate(result, IDS_CREATE_FILE_ERROR);
458 return;
459 }
460 int pos = newName.Find(WCHAR_PATH_SEPARATOR);
461 if (pos >= 0)
462 newName.DeleteFrom((unsigned)pos);
463 if (!_mySelectMode)
464 state.SelectedNames.Clear();
465 state.FocusedName = newName;
466 state.FocusedName_Defined = true;
467 state.SelectFocused = true;
468 RefreshListCtrl(state);
469 }
470
RenameFile()471 void CPanel::RenameFile()
472 {
473 if (!CheckBeforeUpdate(IDS_ERROR_RENAMING))
474 return;
475 int index = _listView.GetFocusedItem();
476 if (index >= 0)
477 _listView.EditLabel(index);
478 }
479
ChangeComment()480 void CPanel::ChangeComment()
481 {
482 if (IsHashFolder())
483 return;
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);
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