From 6c470aff829bed12853321c25262b84934242098 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 May 2019 03:53:20 -0400 Subject: changed the top conflict treeview to support multiple selection this required making some changes to both trees because they were sharing m_ConflictsContextItem that was set on right clicks; both trees are now independent hide/unhide is always shown on multiple selection to avoid scanning all the selected items --- src/modinfodialog.cpp | 239 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 186 insertions(+), 53 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 0e41b10e..b0035c02 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1233,18 +1233,38 @@ bool ModInfoDialog::unhideFile(const QString &oldName) } -void ModInfoDialog::hideConflictFile() +void ModInfoDialog::hideConflictFiles() { - if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + bool changed = false; + + for (const auto* item : ui->overwriteTree->selectedItems()) { + if (canHide(item)) { + if (hideFile(item->data(0, Qt::UserRole).toString())) { + changed = true; + } + } + } + + if (changed) { emit originModified(m_Origin->getID()); refreshLists(); } } -void ModInfoDialog::unhideConflictFile() +void ModInfoDialog::unhideConflictFiles() { - if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + bool changed = false; + + for (const auto* item : ui->overwriteTree->selectedItems()) { + if (canUnhide(item)) { + if (unhideFile(item->data(0, Qt::UserRole).toString())) { + changed = true; + } + } + } + + if (changed) { emit originModified(m_Origin->getID()); refreshLists(); } @@ -1309,33 +1329,75 @@ int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo & } } -void ModInfoDialog::openDataFile() -{ - if (m_ConflictsContextItem != nullptr) { - QFileInfo targetInfo(m_ConflictsContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - m_OrganizerCore->spawnBinaryDirect( - binaryInfo, arguments, m_OrganizerCore->currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - } break; - case 2: { - ::ShellExecuteW(nullptr, L"open", - ToWString(targetInfo.absoluteFilePath()).c_str(), - nullptr, nullptr, SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } +void ModInfoDialog::previewOverwriteDataFile() +{ + // the menu item is only shown for a single selection, but check just in case + const auto selection = ui->overwriteTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openOverwriteDataFile() +{ + // the menu item is only shown for a single selection, but check just in case + const auto selection = ui->overwriteTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + +void ModInfoDialog::previewOverwrittenDataFile() +{ + // the overwritten tree only supports single selection, but check just in case + const auto selection = ui->overwrittenTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openOverwrittenDataFile() +{ + // the overwritten tree only supports single selection, but check just in case + const auto selection = ui->overwrittenTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + +void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) +{ + if (!item) { + return; + } + + QFileInfo targetInfo(item->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + m_OrganizerCore->spawnBinaryDirect( + binaryInfo, arguments, m_OrganizerCore->currentProfile()->name(), + targetInfo.absolutePath(), "", ""); + } break; + case 2: { + ::ShellExecuteW(nullptr, L"open", + ToWString(targetInfo.absoluteFilePath()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); + } break; + default: { + // nop + } break; } } -void ModInfoDialog::previewDataFile() +void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) { - QString fileName = QDir::fromNativeSeparators(m_ConflictsContextItem->data(0, Qt::UserRole).toString()); + if (!item) { + return; + } + + QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory @@ -1395,54 +1457,125 @@ void ModInfoDialog::previewDataFile() } } +bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const +{ + if (item->data(1, Qt::UserRole + 2).toBool()) { + // can't hide files from archives + return false; + } -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) + if (item->text(0).endsWith(ModInfo::s_HiddenExt)) { + // already hidden + return false; + } + + return true; +} + +bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const { - m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); + if (item->data(1, Qt::UserRole + 2).toBool()) { + // can't unhide files from archives + return false; + } - if (m_ConflictsContextItem != nullptr) { - // offer to hide/unhide file, but not for files from archives - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); - } + if (!item->text(0).endsWith(ModInfo::s_HiddenExt)) { + // already visible + return false; + } - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + return true; +} - QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); - if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); - } +bool ModInfoDialog::canPreview(const QTreeWidgetItem* item) const +{ + const QString fileName = item->data(0, Qt::UserRole).toString(); + if (!m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { + return false; + } + + return true; +} + +void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) +{ + const auto selection = ui->overwriteTree->selectedItems(); + if (selection.empty()) { + return; + } - menu.exec(ui->overwriteTree->mapToGlobal(pos)); + // for a single selection, hide/unhide is not shown for files from + // archives and whether the action is hide or unhide depends on the current + // state + // + // for multiple selection, both actions are shown unconditionally and + // handled in hideConflictFiles() and unhideConflictFiles() + bool enableHide = true; + bool enableUnhide = true; + bool enableOpen = true; + bool enablePreview = true; + + if (selection.size() == 1) { + // this is a single selection + const auto* item = selection[0]; + if (!item) { + return; } + + enableHide = canHide(item); + enableUnhide = canUnhide(item); + enablePreview = canPreview(item); + // open is always enabled + } + else { + // this is a multiple selection, don't show open/preview so users don't open + // a thousand files, but always enable hide/unhide to avoid potentially + // scanning hundreds of selected files to check their state + enableOpen = false; + enablePreview = false; + } + + + QMenu menu; + + if (enableHide) { + menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); + } + + if (enableUnhide) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + } + + if (enableOpen) { + menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); + } + + if (enablePreview) { + menu.addAction(tr("Preview"), this, SLOT(previewOverwriteDataFile())); } + + menu.exec(ui->overwriteTree->viewport()->mapToGlobal(pos)); } void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) { - m_ConflictsContextItem = ui->overwrittenTree->itemAt(pos.x(), pos.y()); + auto* item = ui->overwrittenTree->itemAt(pos.x(), pos.y()); - if (m_ConflictsContextItem != nullptr) { - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { + if (item != nullptr) { + if (!item->data(1, Qt::UserRole + 2).toBool()) { QMenu menu; - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); - if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + if (canPreview(item)) { + menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); } - menu.exec(ui->overwrittenTree->mapToGlobal(pos)); + menu.exec(ui->overwrittenTree->viewport()->mapToGlobal(pos)); } } } - void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); -- cgit v1.3.1 From 0232435e0a2fed46c864cf241fb2c42d9682bfed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 May 2019 06:26:13 -0400 Subject: disabled unhide menu item in the conflict tree because hidden items are never shown --- src/modinfodialog.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b0035c02..9f35fc59 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1542,9 +1542,11 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); } - if (enableUnhide) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); - } + // disabling this for now, because hidden files are never shows in this list + // at all; if this ever changes, this should be added back + //if (enableUnhide) { + // menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + //} if (enableOpen) { menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); -- cgit v1.3.1 From b0c5cf146c2d8eeb1e6dc945b069ed16b05154f8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 07:21:41 -0400 Subject: added new FileRenamer class to handle user confirmations when hiding/unhiding files conflict tree hooked up filetree shows the new dialogs, but doesn't handle buttons correctly yet --- src/modinfodialog.cpp | 318 ++++++++++++++++++++++++++++++++++++++++---------- src/modinfodialog.h | 140 +++++++++++++++++++++- 2 files changed, 396 insertions(+), 62 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9f35fc59..cbfa02fd 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -74,6 +74,193 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS } +FileRenamer::FileRenamer(QWidget* parent, QFlags flags) + : m_parent(parent), m_flags(flags) +{ + // sanity check for flags + if ((m_flags & (HIDE|UNHIDE)) == 0) { + qCritical("renameFile() missing hide flag"); + // doesn't really matter, it's just for text + m_flags = HIDE; + } +} + +FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QString& newName) +{ + qDebug().nospace() << "renaming " << oldName << " to " << newName; + + if (QFileInfo(newName).exists()) { + qDebug().nospace() << newName << " already exists"; + + // target file already exists, confirm replacement + auto answer = confirmReplace(); + + switch (answer) { + case DECISION_SKIP: { + // user wants to skip this file + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + case DECISION_REPLACE: { + qDebug().nospace() << "removing " << newName; + // user wants to replace the file, so remove it + if (!QFile(newName).remove()) { + qWarning().nospace() << "failed to remove " << newName; + // removal failed, warn the user and allow canceling + if (!removeFailed(newName)) { + qDebug().nospace() << "canceling " << oldName; + // user wants to cancel + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + break; + } + + case DECISION_CANCEL: // fall-through + default: { + // user wants to stop + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + } + } + + // target either didn't exist or was removed correctly + + if (!QFile::rename(oldName, newName)) { + qWarning().nospace() << "failed to rename " << oldName << " to " << newName; + + // renaming failed, warn the user and allow canceling + if (!renameFailed(oldName, newName)) { + // user wants to cancel + qDebug().nospace() << "canceling"; + return RESULT_CANCEL; + } + + // ignore this file and continue on + qDebug().nospace() << "skipping " << oldName; + return RESULT_SKIP; + } + + // everything worked + qDebug().nospace() << "successfully renamed " << oldName << " to " << newName; + return RESULT_OK; +} + +FileRenamer::RenameDecision FileRenamer::confirmReplace() +{ + if (m_flags & REPLACE_ALL) { + // user wants to silently replace all + qDebug().nospace() << "user has selected replace all"; + return DECISION_REPLACE; + } + else if (m_flags & REPLACE_NONE) { + // user wants to silently skip all + qDebug().nospace() << "user has selected replace none"; + return DECISION_SKIP; + } + + QString text; + + if (m_flags & HIDE) { + text = QObject::tr("There already is a hidden version of this file. Replace it?"); + } + else if (m_flags & UNHIDE) { + text = QObject::tr("There already is a visible version of this file. Replace it?"); + } + + auto buttons = QMessageBox::Yes | QMessageBox::No; + if (m_flags & MULTIPLE) { + // only show these buttons when there are multiple files to replace + buttons |= QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel; + } + + const auto answer = QMessageBox::question( + m_parent, QObject::tr("Replace file?"), text, buttons); + + switch (answer) { + case QMessageBox::Yes: + qDebug().nospace() << "user wants to replace"; + return DECISION_REPLACE; + + case QMessageBox::No: + qDebug().nospace() << "user wants to skip"; + return DECISION_SKIP; + + case QMessageBox::YesToAll: + qDebug().nospace() << "user wants to replace all"; + // remember the answer + m_flags |= REPLACE_ALL; + return DECISION_REPLACE; + + case QMessageBox::NoToAll: + qDebug().nospace() << "user wants to replace none"; + // remember the answer + m_flags |= REPLACE_NONE; + return DECISION_SKIP; + + case QMessageBox::Cancel: // fall-through + default: + qDebug().nospace() << "user wants to cancel"; + return DECISION_CANCEL; + } +} + +bool FileRenamer::removeFailed(const QString& name) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(name), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} + +bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) +{ + QMessageBox::StandardButtons buttons = QMessageBox::Ok; + if (m_flags & MULTIPLE) { + // only show cancel for multiple files + buttons |= QMessageBox::Cancel; + } + + const auto answer = QMessageBox::critical( + m_parent, QObject::tr("File operation failed"), + QObject::tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName)), + buttons); + + if (answer == QMessageBox::Cancel) { + // user wants to stop + qDebug().nospace() << "user wants to cancel"; + return false; + } + + // skip this one and continue + qDebug().nospace() << "user wants to skip"; + return true; +} + + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), @@ -1026,11 +1213,14 @@ void ModInfoDialog::renameTriggered() void ModInfoDialog::hideTriggered() { + QFlags flags = FileRenamer::HIDE; + FileRenamer renamer(this, flags); + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); ++iter) { QString path = m_FileSystemModel->filePath(*iter); if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(path); + hideFile(renamer, path); } } } @@ -1038,11 +1228,14 @@ void ModInfoDialog::hideTriggered() void ModInfoDialog::unhideTriggered() { + QFlags flags = FileRenamer::UNHIDE; + FileRenamer renamer(this, flags); + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); ++iter) { QString path = m_FileSystemModel->filePath(*iter); if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(path); + unhideFile(renamer, path); } } } @@ -1184,90 +1377,95 @@ void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, in emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); } +FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) +{ + const QString newName = oldName + ModInfo::s_HiddenExt; + return renamer.rename(oldName, newName); +} -bool ModInfoDialog::hideFile(const QString &oldName) +FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const QString &oldName) { - QString newName = oldName + ModInfo::s_HiddenExt; + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + return renamer.rename(oldName, newName); +} - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } +void ModInfoDialog::changeConflictFiles(bool hide) +{ + bool changed = false; + bool stop = false; - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); - return false; + const auto items = ui->overwriteTree->selectedItems(); + + qDebug().nospace() + << (hide ? "hiding" : "unhiding") << " " + << items.size() << " conflict files"; + + QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + if (items.size() > 1) { + flags |= FileRenamer::MULTIPLE; } -} + FileRenamer renamer(this, flags); -bool ModInfoDialog::unhideFile(const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; + for (const auto* item : items) { + if (stop) { + break; + } + + auto result = FileRenamer::RESULT_CANCEL; + + if (hide) { + if (!canHide(item)) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + continue; } + result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); + } else { - return false; + if (!canUnhide(item)) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + continue; + } + result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); } - } - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); - return false; - } -} + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } -void ModInfoDialog::hideConflictFiles() -{ - bool changed = false; + case FileRenamer::RESULT_SKIP: { + // nop + break; + } - for (const auto* item : ui->overwriteTree->selectedItems()) { - if (canHide(item)) { - if (hideFile(item->data(0, Qt::UserRole).toString())) { - changed = true; + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; } } } + qDebug().nospace() << (hide ? "hiding" : "unhiding") << " conflict files done"; + if (changed) { + qDebug().nospace() << "triggering refresh"; emit originModified(m_Origin->getID()); refreshLists(); } } +void ModInfoDialog::hideConflictFiles() +{ + changeConflictFiles(true); +} void ModInfoDialog::unhideConflictFiles() { - bool changed = false; - - for (const auto* item : ui->overwriteTree->selectedItems()) { - if (canUnhide(item)) { - if (unhideFile(item->data(0, Qt::UserRole).toString())) { - changed = true; - } - } - } - - if (changed) { - emit originModified(m_Origin->getID()); - refreshLists(); - } + changeConflictFiles(false); } int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 45745471..6af96c33 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -48,6 +48,141 @@ class QFileSystemModel; class QTreeView; class CategoryFactory; +/** +* Renames individual files and handles dialog boxes to confirm replacements and +* failures with the user +**/ +class FileRenamer +{ +public: + /** + * controls appearance and replacement behaviour; if RENAME_REPLACE_ALL and + * RENAME_REPLACE_NONE are not provided, the user will have the option to + * choose on the first replacement + **/ + enum RenameFlags + { + /** + * this renamer will be used on multiple files, so display additional + * buttons to replace all and for canceling + **/ + MULTIPLE = 0x01, + + /** + * customizes some of the text shown on dialog to mention that files are + * being hidden + **/ + HIDE = 0x02, + + /** + * customizes some of the text shown on dialog to mention that files are + * being unhidden + **/ + UNHIDE = 0x04, + + /** + * silently replaces all existing files + **/ + REPLACE_ALL = 0x08, + + /** + * silently skips all existing files + **/ + REPLACE_NONE = 0x10, + }; + + + /** result of a single rename + * + **/ + enum RenameResults + { + /** + * the user skipped this file + */ + RESULT_SKIP, + + /** + * the file was successfully renamed + */ + RESULT_OK, + + /** + * the user wants to cancel + */ + RESULT_CANCEL + }; + + + /** + * @param parent Parent widget for dialog boxes + **/ + FileRenamer(QWidget* parent, QFlags flags); + + /** + * renames the given file + * @param oldName current filename + * @param newName new filename + * @return whether the file was renamed, skipped or the user wants to cancel + **/ + RenameResults rename(const QString& oldName, const QString& newName); + +private: + /** + *user's decision when replacing + **/ + enum RenameDecision + { + /** + * replace the file + **/ + DECISION_REPLACE, + + /** + * skip the file + **/ + DECISION_SKIP, + + /** + * cancel the whole thing + **/ + DECISION_CANCEL + }; + + /** + * parent widget for dialog boxes + **/ + QWidget* m_parent; + + /** + * flags + **/ + QFlags m_flags; + + /** + * asks the user to replace an existing file, may return early if the user + * has already selected to replace all/none + * @return whether to replace, skip or cancel + **/ + RenameDecision confirmReplace(); + + /** + * removal of a file failed, ask the user to continue or cancel + * @param name The name of the file that failed to be removed + * @return true to continue, false to stop + **/ + bool removeFailed(const QString& name); + + /** + * renaming a file failed, ask the user to continue or cancel + * @param oldName current filename + * @param newName new filename + * @return true to continue, false to stop + **/ + bool renameFailed(const QString& oldName, const QString& newName); +}; + + /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system @@ -147,8 +282,8 @@ private: void openIniFile(const QString &fileName); bool allowNavigateFromTXT(); bool allowNavigateFromINI(); - bool hideFile(const QString &oldName); - bool unhideFile(const QString &oldName); + FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); + FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); void refreshPrimaryCategoriesBox(); @@ -254,6 +389,7 @@ private: void previewDataFile(const QTreeWidgetItem* item); void openDataFile(const QTreeWidgetItem* item); + void changeConflictFiles(bool hide); }; #endif // MODINFODIALOG_H -- cgit v1.3.1 From 2920a707c7018549616e7d0719385c915ed4d89a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:19:57 -0400 Subject: added filename to file replace confirmation dialog text hooked up filetree to FileRenamer fixed filetree context menu positioning being offset vertically --- src/modinfodialog.cpp | 136 ++++++++++++++++++++++++++++++++++++-------------- src/modinfodialog.h | 14 ++++-- 2 files changed, 107 insertions(+), 43 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cbfa02fd..572c018b 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -93,7 +93,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt qDebug().nospace() << newName << " already exists"; // target file already exists, confirm replacement - auto answer = confirmReplace(); + auto answer = confirmReplace(newName); switch (answer) { case DECISION_SKIP: { @@ -153,7 +153,7 @@ FileRenamer::RenameResults FileRenamer::rename(const QString& oldName, const QSt return RESULT_OK; } -FileRenamer::RenameDecision FileRenamer::confirmReplace() +FileRenamer::RenameDecision FileRenamer::confirmReplace(const QString& newName) { if (m_flags & REPLACE_ALL) { // user wants to silently replace all @@ -169,10 +169,10 @@ FileRenamer::RenameDecision FileRenamer::confirmReplace() QString text; if (m_flags & HIDE) { - text = QObject::tr("There already is a hidden version of this file. Replace it?"); + text = QObject::tr("The hidden file \"%1\" already exists. Replace it?").arg(newName); } else if (m_flags & UNHIDE) { - text = QObject::tr("There already is a visible version of this file. Replace it?"); + text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName); } auto buttons = QMessageBox::Yes | QMessageBox::No; @@ -1213,31 +1213,81 @@ void ModInfoDialog::renameTriggered() void ModInfoDialog::hideTriggered() { - QFlags flags = FileRenamer::HIDE; - FileRenamer renamer(this, flags); - - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(renamer, path); - } - } + changeFiletreeVisibility(true); } void ModInfoDialog::unhideTriggered() { - QFlags flags = FileRenamer::UNHIDE; + changeFiletreeVisibility(false); +} + +void ModInfoDialog::changeFiletreeVisibility(bool hide) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (hide ? "hiding" : "unhiding") << " " + << m_FileSelection.size() << " filetree files"; + + QFlags flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE); + if (m_FileSelection.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + FileRenamer renamer(this, flags); - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(renamer, path); + for (const auto& index : m_FileSelection) { + if (stop) { + break; + } + + const QString path = m_FileSystemModel->filePath(index); + auto result = FileRenamer::RESULT_CANCEL; + + if (hide) { + if (!canHideFile(false, path)) { + qDebug().nospace() << "cannot hide " << path << ", skipping"; + continue; + } + result = hideFile(renamer, path); + + } else { + if (!canUnhideFile(false, path)) { + qDebug().nospace() << "cannot unhide " << path << ", skipping"; + continue; + } + result = unhideFile(renamer, path); + } + + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } + + case FileRenamer::RESULT_SKIP: { + // nop + break; + } + + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } } } + + qDebug().nospace() << (hide ? "hiding" : "unhiding") << " filetree files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + emit originModified(m_Origin->getID()); + refreshLists(); + } } @@ -1322,7 +1372,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) m_FileSelection.clear(); m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } - menu.exec(ui->fileTree->mapToGlobal(pos)); + menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } @@ -1389,7 +1439,7 @@ FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const return renamer.rename(oldName, newName); } -void ModInfoDialog::changeConflictFiles(bool hide) +void ModInfoDialog::changeConflictFilesVisibility(bool hide) { bool changed = false; bool stop = false; @@ -1415,14 +1465,14 @@ void ModInfoDialog::changeConflictFiles(bool hide) auto result = FileRenamer::RESULT_CANCEL; if (hide) { - if (!canHide(item)) { + if (!canHideConflictItem(item)) { qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); } else { - if (!canUnhide(item)) { + if (!canUnhideConflictItem(item)) { qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } @@ -1460,12 +1510,12 @@ void ModInfoDialog::changeConflictFiles(bool hide) void ModInfoDialog::hideConflictFiles() { - changeConflictFiles(true); + changeConflictFilesVisibility(true); } void ModInfoDialog::unhideConflictFiles() { - changeConflictFiles(false); + changeConflictFilesVisibility(false); } int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) @@ -1597,7 +1647,7 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - // what we have is an absolute path to the file in its actual location (for the primary origin) + // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory // we need to look in the virtual directory for the file to make sure the info is up to date. @@ -1655,14 +1705,14 @@ void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) } } -bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const +bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const { - if (item->data(1, Qt::UserRole + 2).toBool()) { + if (isArchive) { // can't hide files from archives return false; } - if (item->text(0).endsWith(ModInfo::s_HiddenExt)) { + if (filename.endsWith(ModInfo::s_HiddenExt)) { // already hidden return false; } @@ -1670,14 +1720,14 @@ bool ModInfoDialog::canHide(const QTreeWidgetItem* item) const return true; } -bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const +bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const { - if (item->data(1, Qt::UserRole + 2).toBool()) { + if (isArchive) { // can't unhide files from archives return false; } - if (!item->text(0).endsWith(ModInfo::s_HiddenExt)) { + if (!filename.endsWith(ModInfo::s_HiddenExt)) { // already visible return false; } @@ -1685,7 +1735,17 @@ bool ModInfoDialog::canUnhide(const QTreeWidgetItem* item) const return true; } -bool ModInfoDialog::canPreview(const QTreeWidgetItem* item) const +bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const +{ + return canHideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); +} + +bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const +{ + return canUnhideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); +} + +bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { const QString fileName = item->data(0, Qt::UserRole).toString(); if (!m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { @@ -1720,9 +1780,9 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po return; } - enableHide = canHide(item); - enableUnhide = canUnhide(item); - enablePreview = canPreview(item); + enableHide = canHideConflictItem(item); + enableUnhide = canUnhideConflictItem(item); + enablePreview = canPreviewConflictItem(item); // open is always enabled } else { @@ -1767,7 +1827,7 @@ void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint & menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - if (canPreview(item)) { + if (canPreviewConflictItem(item)) { menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6af96c33..66c50be5 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -164,7 +164,7 @@ private: * has already selected to replace all/none * @return whether to replace, skip or cancel **/ - RenameDecision confirmReplace(); + RenameDecision confirmReplace(const QString& newName); /** * removal of a file failed, ask the user to continue or cancel @@ -383,13 +383,17 @@ private: std::map m_RealTabPos; - bool canHide(const QTreeWidgetItem* item) const; - bool canUnhide(const QTreeWidgetItem* item) const; - bool canPreview(const QTreeWidgetItem* item) const; + bool canHideConflictItem(const QTreeWidgetItem* item) const; + bool canUnhideConflictItem(const QTreeWidgetItem* item) const; + bool canPreviewConflictItem(const QTreeWidgetItem* item) const; void previewDataFile(const QTreeWidgetItem* item); void openDataFile(const QTreeWidgetItem* item); - void changeConflictFiles(bool hide); + void changeConflictFilesVisibility(bool hide); + void changeFiletreeVisibility(bool hide); + + bool canHideFile(bool isArchive, const QString& filename) const; + bool canUnhideFile(bool isArchive, const QString& filename) const; }; #endif // MODINFODIALOG_H -- cgit v1.3.1 From 2c30d2d0e92e7e7ad5a1209a0efd81d39b353a26 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:38:05 -0400 Subject: fixed filetree context menu for multiple selection --- src/modinfodialog.cpp | 68 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 14 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 572c018b..eb91f73d 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1343,35 +1343,75 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); m_FileSelection = selectionModel->selectedRows(0); -// m_FileSelection = ui->fileTree->indexAt(pos); QMenu menu(ui->fileTree); menu.addAction(m_NewFolderAction); - bool hasFiles = false; + if (selectionModel->hasSelection()) { + bool enableOpen = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; + if (m_FileSelection.size() == 1) { + // single selection + + // only enable open action if a file is selected + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (!hasFiles) { + enableOpen = false; + } + + const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + + if (!canHideFile(false, fileName)) { + enableHide = false; + } + + if (!canUnhideFile(false, fileName)) { + enableUnhide = false; + } + } else { + // this is a multiple selection, don't show open action so users don't open + // a thousand files, but always enable hide/unhide to avoid potentially + // scanning hundreds of selected files to check their state + enableOpen = false; + enableRename = false; } - } - if (selectionModel->hasSelection()) { - if (hasFiles) { + if (enableOpen) { menu.addAction(m_OpenAction); } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(m_UnhideAction); - } else { + + if (enableRename) { + menu.addAction(m_RenameAction); + } + + if (enableDelete) { + menu.addAction(m_DeleteAction); + } + + if (enableHide) { menu.addAction(m_HideAction); } + + if (enableUnhide) { + menu.addAction(m_UnhideAction); + } } else { m_FileSelection.clear(); m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } + menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } -- cgit v1.3.1 From a1f1656c362118b54a3ab4d40af42fa30a1d10ca Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 08:53:59 -0400 Subject: re-enabled unhide menu item in the conflict tree since hidden items can actually appear --- src/modinfodialog.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index eb91f73d..f7910b3c 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1840,11 +1840,11 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); } - // disabling this for now, because hidden files are never shows in this list - // at all; if this ever changes, this should be added back - //if (enableUnhide) { - // menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); - //} + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + if (enableUnhide) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFiles())); + } if (enableOpen) { menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); -- cgit v1.3.1 From eb203e4c2a7ef8d7e9efe43b9e5df0c8b2253ea7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 19 May 2019 09:06:09 -0400 Subject: if the number of selected items is low, check them to accurately show the hide/unhide items --- src/modinfodialog.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) (limited to 'src/modinfodialog.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f7910b3c..555b62db 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -73,6 +73,10 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS return LHS.m_SortValue < RHS.m_SortValue; } +// if there are more than 50 selected items in the conflict tree or filetree, +// don't bother checking whether they're visible, just show both menu items +const int max_scan_for_visibility = 50; + FileRenamer::FileRenamer(QWidget* parent, QFlags flags) : m_parent(parent), m_flags(flags) @@ -1382,10 +1386,33 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } } else { // this is a multiple selection, don't show open action so users don't open - // a thousand files, but always enable hide/unhide to avoid potentially - // scanning hundreds of selected files to check their state + // a thousand files enableOpen = false; enableRename = false; + + if (m_FileSelection.size() < max_scan_for_visibility) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto& index : m_FileSelection) { + const QString fileName = m_FileSystemModel->fileName(index); + + if (canHideFile(false, fileName)) { + enableHide = true; + } + + if (canUnhideFile(false, fileName)) { + enableUnhide = true; + } + + if (enableHide && enableUnhide) { + // found both, no need to check more + break; + } + } + } } if (enableOpen) { @@ -1827,10 +1854,31 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po } else { // this is a multiple selection, don't show open/preview so users don't open - // a thousand files, but always enable hide/unhide to avoid potentially - // scanning hundreds of selected files to check their state + // a thousand files enableOpen = false; enablePreview = false; + + if (selection.size() < max_scan_for_visibility) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto* item : selection) { + if (canHideConflictItem(item)) { + enableHide = true; + } + + if (canUnhideConflictItem(item)) { + enableUnhide = true; + } + + if (enableHide && enableUnhide) { + // found both, no need to check more + break; + } + } + } } -- cgit v1.3.1