// PanelOperations.cpp #include "StdAfx.h" #include "../../../Common/DynamicBuffer.h" #include "../../../Common/StringConvert.h" #include "../../../Common/Wildcard.h" #include "../../../Windows/COM.h" #include "../../../Windows/FileName.h" #include "../../../Windows/PropVariant.h" #include "ComboDialog.h" #include "FSFolder.h" #include "FormatUtils.h" #include "LangUtils.h" #include "Panel.h" #include "UpdateCallback100.h" #include "resource.h" using namespace NWindows; using namespace NFile; using namespace NName; #ifndef _UNICODE extern bool g_IsNT; #endif enum EFolderOpType { FOLDER_TYPE_CREATE_FOLDER = 0, FOLDER_TYPE_DELETE = 1, FOLDER_TYPE_RENAME = 2 }; class CThreadFolderOperations: public CProgressThreadVirt { HRESULT ProcessVirt() Z7_override; public: EFolderOpType OpType; UString Name; UInt32 Index; CRecordVector Indices; CMyComPtr FolderOperations; CMyComPtr UpdateCallback; CUpdateCallback100Imp *UpdateCallbackSpec; CThreadFolderOperations(EFolderOpType opType): OpType(opType) {} HRESULT DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError); }; HRESULT CThreadFolderOperations::ProcessVirt() { NCOM::CComInitializer comInitializer; switch ((int)OpType) { case FOLDER_TYPE_CREATE_FOLDER: return FolderOperations->CreateFolder(Name, UpdateCallback); case FOLDER_TYPE_DELETE: return FolderOperations->Delete(Indices.ConstData(), Indices.Size(), UpdateCallback); case FOLDER_TYPE_RENAME: return FolderOperations->Rename(Index, Name, UpdateCallback); default: return E_FAIL; } } HRESULT CThreadFolderOperations::DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError) { UpdateCallbackSpec = new CUpdateCallback100Imp; UpdateCallback = UpdateCallbackSpec; UpdateCallbackSpec->ProgressDialog = this; WaitMode = true; Sync.FinalMessage.ErrorMessage.Title = titleError; UpdateCallbackSpec->Init(); if (!panel._parentFolders.IsEmpty()) { const CFolderLink &fl = panel._parentFolders.Back(); UpdateCallbackSpec->PasswordIsDefined = fl.UsePassword; UpdateCallbackSpec->Password = fl.Password; } MainWindow = panel._mainWindow; // panel.GetParent() MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); MainAddTitle = progressTitle + L' '; RINOK(Create(progressTitle, MainWindow)) return Result; } #ifndef _UNICODE typedef int (WINAPI * Func_SHFileOperationW)(LPSHFILEOPSTRUCTW lpFileOp); #endif /* void CPanel::MessageBoxErrorForUpdate(HRESULT errorCode, UINT resourceID) { if (errorCode == E_NOINTERFACE) MessageBox_Error_UnsupportOperation(); else MessageBox_Error_HRESULT_Caption(errorCode, LangString(resourceID)); } */ void CPanel::DeleteItems(bool NON_CE_VAR(toRecycleBin)) { CDisableTimerProcessing disableTimerProcessing(*this); CRecordVector indices; Get_ItemIndices_Operated(indices); if (indices.IsEmpty()) return; CSelectedState state; SaveSelectedState(state); #ifndef UNDER_CE // WM6 / SHFileOperationW doesn't ask user! So we use internal delete if (IsFSFolder() && toRecycleBin) { bool useInternalDelete = false; #ifndef _UNICODE if (!g_IsNT) { CDynamicBuffer buffer; FOR_VECTOR (i, indices) { const AString path (GetSystemString(GetItemFullPath(indices[i]))); buffer.AddData(path, path.Len() + 1); } *buffer.GetCurPtrAndGrow(1) = 0; SHFILEOPSTRUCTA fo; fo.hwnd = GetParent(); fo.wFunc = FO_DELETE; fo.pFrom = (const CHAR *)buffer; fo.pTo = NULL; fo.fFlags = 0; if (toRecycleBin) fo.fFlags |= FOF_ALLOWUNDO; // fo.fFlags |= FOF_NOCONFIRMATION; // fo.fFlags |= FOF_NOERRORUI; // fo.fFlags |= FOF_SILENT; // fo.fFlags |= FOF_WANTNUKEWARNING; fo.fAnyOperationsAborted = FALSE; fo.hNameMappings = NULL; fo.lpszProgressTitle = NULL; /* int res = */ ::SHFileOperationA(&fo); } else #endif { CDynamicBuffer buffer; unsigned maxLen = 0; const UString prefix = GetFsPath(); FOR_VECTOR (i, indices) { // L"\\\\?\\") doesn't work here. const UString path = prefix + GetItemRelPath2(indices[i]); if (path.Len() > maxLen) maxLen = path.Len(); buffer.AddData(path, path.Len() + 1); } *buffer.GetCurPtrAndGrow(1) = 0; if (maxLen >= MAX_PATH) { if (toRecycleBin) { MessageBox_Error_LangID(IDS_ERROR_LONG_PATH_TO_RECYCLE); return; } useInternalDelete = true; } else { SHFILEOPSTRUCTW fo; fo.hwnd = GetParent(); fo.wFunc = FO_DELETE; fo.pFrom = (const WCHAR *)buffer; fo.pTo = NULL; fo.fFlags = 0; if (toRecycleBin) fo.fFlags |= FOF_ALLOWUNDO; fo.fAnyOperationsAborted = FALSE; fo.hNameMappings = NULL; fo.lpszProgressTitle = NULL; // int res; #ifdef _UNICODE /* res = */ ::SHFileOperationW(&fo); #else Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION const Func_SHFileOperationW f_SHFileOperationW = Z7_GET_PROC_ADDRESS( Func_SHFileOperationW, ::GetModuleHandleW(L"shell32.dll"), "SHFileOperationW"); if (!f_SHFileOperationW) return; /* res = */ f_SHFileOperationW(&fo); #endif } } /* if (fo.fAnyOperationsAborted) MessageBox_Error_HRESULT_Caption(result, LangString(IDS_ERROR_DELETING)); */ if (!useInternalDelete) { RefreshListCtrl(state); return; } } #endif // DeleteItemsInternal if (!CheckBeforeUpdate(IDS_ERROR_DELETING)) return; UInt32 titleID, messageID; UString messageParam; if (indices.Size() == 1) { const unsigned index = indices[0]; messageParam = GetItemRelPath2(index); if (IsItem_Folder(index)) { titleID = IDS_CONFIRM_FOLDER_DELETE; messageID = IDS_WANT_TO_DELETE_FOLDER; } else { titleID = IDS_CONFIRM_FILE_DELETE; messageID = IDS_WANT_TO_DELETE_FILE; } } else { titleID = IDS_CONFIRM_ITEMS_DELETE; messageID = IDS_WANT_TO_DELETE_ITEMS; messageParam = NumberToString(indices.Size()); } if (::MessageBoxW(GetParent(), MyFormatNew(messageID, messageParam), LangString(titleID), MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES) return; CDisableNotify disableNotify(*this); { CThreadFolderOperations op(FOLDER_TYPE_DELETE); op.FolderOperations = _folderOperations; op.Indices = indices; op.DoOperation(*this, LangString(IDS_DELETING), LangString(IDS_ERROR_DELETING)); } RefreshTitleAlways(); RefreshListCtrl(state); } BOOL CPanel::OnBeginLabelEdit(LV_DISPINFOW * lpnmh) { const unsigned realIndex = GetRealIndex(lpnmh->item); if (realIndex == kParentIndex) return TRUE; if (IsThereReadOnlyFolder()) return TRUE; return FALSE; } static bool IsCorrectFsName(const UString &name) { const UString lastPart = name.Ptr((unsigned)(name.ReverseFind_PathSepar() + 1)); return lastPart != L"." && lastPart != L".."; } bool CorrectFsPath(const UString &relBase, const UString &path, UString &result); bool CPanel::CorrectFsPath(const UString &path2, UString &result) { return ::CorrectFsPath(GetFsPath(), path2, result); } BOOL CPanel::OnEndLabelEdit(LV_DISPINFOW * lpnmh) { if (lpnmh->item.pszText == NULL) return FALSE; CDisableTimerProcessing disableTimerProcessing2(*this); if (!CheckBeforeUpdate(IDS_ERROR_RENAMING)) return FALSE; UString newName = lpnmh->item.pszText; if (!IsCorrectFsName(newName)) { MessageBox_Error_HRESULT(E_INVALIDARG); return FALSE; } if (IsFSFolder()) { UString correctName; if (!CorrectFsPath(newName, correctName)) { MessageBox_Error_HRESULT(E_INVALIDARG); return FALSE; } newName = correctName; } SaveSelectedState(_selectedState); const unsigned realIndex = GetRealIndex(lpnmh->item); if (realIndex == kParentIndex) return FALSE; const UString prefix = GetItemPrefix(realIndex); const UString oldName = GetItemName(realIndex); CDisableNotify disableNotify(*this); { CThreadFolderOperations op(FOLDER_TYPE_RENAME); op.FolderOperations = _folderOperations; op.Index = realIndex; op.Name = newName; const HRESULT res = op.DoOperation(*this, LangString(IDS_RENAMING), LangString(IDS_ERROR_RENAMING)); // fixed in 9.26: we refresh list even after errors // (it's more safe, since error can be at different stages, so list can be incorrect). if (res == S_OK) _selectedState.FocusedName = prefix + newName; else { _selectedState.FocusedName = prefix + oldName; // return FALSE; } } // Can't use RefreshListCtrl here. // RefreshListCtrlSaveFocused(); _selectedState.FocusedName_Defined = true; _selectedState.SelectFocused = true; // We need clear all items to disable GetText before Reload: // number of items can change. // DeleteListItems(); // But seems it can still call GetText (maybe for current item) // so we can't delete items. _dontShowMode = true; PostMsg(kReLoadMessage); return TRUE; } bool Dlg_CreateFolder(HWND wnd, UString &destName); void CPanel::CreateFolder() { if (IsHashFolder()) return; if (!CheckBeforeUpdate(IDS_CREATE_FOLDER_ERROR)) return; CDisableTimerProcessing disableTimerProcessing2(*this); CSelectedState state; SaveSelectedState(state); UString newName; if (!Dlg_CreateFolder(GetParent(), newName)) return; if (!IsCorrectFsName(newName)) { MessageBox_Error_HRESULT(E_INVALIDARG); return; } if (IsFSFolder()) { UString correctName; if (!CorrectFsPath(newName, correctName)) { MessageBox_Error_HRESULT(E_INVALIDARG); return; } newName = correctName; } HRESULT res; CDisableNotify disableNotify(*this); { CThreadFolderOperations op(FOLDER_TYPE_CREATE_FOLDER); op.FolderOperations = _folderOperations; op.Name = newName; res = op.DoOperation(*this, LangString(IDS_CREATE_FOLDER), LangString(IDS_CREATE_FOLDER_ERROR)); /* // fixed for 9.26: we must refresh always if (res != S_OK) return; */ } if (res == S_OK) { int pos = newName.Find(WCHAR_PATH_SEPARATOR); if (pos >= 0) newName.DeleteFrom((unsigned)(pos)); if (!_mySelectMode) state.SelectedNames.Clear(); state.FocusedName = newName; state.FocusedName_Defined = true; state.SelectFocused = true; } RefreshTitleAlways(); RefreshListCtrl(state); } void CPanel::CreateFile() { if (IsHashFolder()) return; if (!CheckBeforeUpdate(IDS_CREATE_FILE_ERROR)) return; CDisableTimerProcessing disableTimerProcessing2(*this); CSelectedState state; SaveSelectedState(state); CComboDialog dlg; LangString(IDS_CREATE_FILE, dlg.Title); LangString(IDS_CREATE_FILE_NAME, dlg.Static); LangString(IDS_CREATE_FILE_DEFAULT_NAME, dlg.Value); if (dlg.Create(GetParent()) != IDOK) return; CDisableNotify disableNotify(*this); UString newName = dlg.Value; if (IsFSFolder()) { UString correctName; if (!CorrectFsPath(newName, correctName)) { MessageBox_Error_HRESULT(E_INVALIDARG); return; } newName = correctName; } const HRESULT result = _folderOperations->CreateFile(newName, NULL); if (result != S_OK) { MessageBox_Error_HRESULT_Caption(result, LangString(IDS_CREATE_FILE_ERROR)); // MessageBoxErrorForUpdate(result, IDS_CREATE_FILE_ERROR); return; } const int pos = newName.Find(WCHAR_PATH_SEPARATOR); if (pos >= 0) newName.DeleteFrom((unsigned)pos); if (!_mySelectMode) state.SelectedNames.Clear(); state.FocusedName = newName; state.FocusedName_Defined = true; state.SelectFocused = true; RefreshListCtrl(state); } void CPanel::RenameFile() { if (!CheckBeforeUpdate(IDS_ERROR_RENAMING)) return; int index = _listView.GetFocusedItem(); if (index >= 0) _listView.EditLabel(index); } void CPanel::ChangeComment() { if (IsHashFolder()) return; if (!CheckBeforeUpdate(IDS_COMMENT)) return; CDisableTimerProcessing disableTimerProcessing2(*this); const int index = _listView.GetFocusedItem(); if (index < 0) return; const unsigned realIndex = GetRealItemIndex(index); if (realIndex == kParentIndex) return; CSelectedState state; SaveSelectedState(state); UString comment; { NCOM::CPropVariant propVariant; if (_folder->GetProperty(realIndex, kpidComment, &propVariant) != S_OK) return; if (propVariant.vt == VT_BSTR) comment = propVariant.bstrVal; else if (propVariant.vt != VT_EMPTY) return; } const UString name = GetItemRelPath2(realIndex); CComboDialog dlg; dlg.Title = name; dlg.Title += " : "; AddLangString(dlg.Title, IDS_COMMENT); dlg.Value = comment; LangString(IDS_COMMENT2, dlg.Static); if (dlg.Create(GetParent()) != IDOK) return; NCOM::CPropVariant propVariant (dlg.Value); CDisableNotify disableNotify(*this); const HRESULT result = _folderOperations->SetProperty(realIndex, kpidComment, &propVariant, NULL); if (result != S_OK) { if (result == E_NOINTERFACE) MessageBox_Error_UnsupportOperation(); else MessageBox_Error_HRESULT_Caption(result, L"Set Comment Error"); } RefreshListCtrl(state); }