summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAl <gabriel.cortesi@outlook.com>2019-05-19 16:58:08 +0200
committerGitHub <noreply@github.com>2019-05-19 16:58:08 +0200
commitb22ee0dc56fcf055088f86624c4f83aa194c2037 (patch)
tree85dda74610e06f3a470a00c840f34fcf2c005f2f /src
parentc1db638d924bbec6521147de45f8815c9c9ee5e1 (diff)
parenteb203e4c2a7ef8d7e9efe43b9e5df0c8b2253ea7 (diff)
Merge pull request #717 from isanae/Develop
enable multiple selection in the conflict tree and file tree
Diffstat (limited to 'src')
-rw-r--r--src/modinfodialog.cpp717
-rw-r--r--src/modinfodialog.h162
-rw-r--r--src/modinfodialog.ui3
3 files changed, 756 insertions, 126 deletions
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index 0e41b10e..555b62db 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -73,6 +73,197 @@ 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<RenameFlags> 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(newName);
+
+ 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(const QString& newName)
+{
+ 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("The hidden file \"%1\" already exists. Replace it?").arg(newName);
+ }
+ else if (m_flags & UNHIDE) {
+ text = QObject::tr("The visible file \"%1\" already exists. Replace it?").arg(newName);
+ }
+
+ 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),
@@ -1026,24 +1217,80 @@ void ModInfoDialog::renameTriggered()
void ModInfoDialog::hideTriggered()
{
- 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);
- }
- }
+ changeFiletreeVisibility(true);
}
void ModInfoDialog::unhideTriggered()
{
- 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);
+ changeFiletreeVisibility(false);
+}
+
+void ModInfoDialog::changeFiletreeVisibility(bool hide)
+{
+ bool changed = false;
+ bool stop = false;
+
+ qDebug().nospace()
+ << (hide ? "hiding" : "unhiding") << " "
+ << m_FileSelection.size() << " filetree files";
+
+ QFlags<FileRenamer::RenameFlags> flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE);
+ if (m_FileSelection.size() > 1) {
+ flags |= FileRenamer::MULTIPLE;
+ }
+
+ FileRenamer renamer(this, flags);
+
+ 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();
}
}
@@ -1100,36 +1347,99 @@ 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;
+
+ if (m_FileSelection.size() == 1) {
+ // single selection
- foreach(QModelIndex idx, m_FileSelection) {
- if (m_FileSystemModel->fileInfo(idx).isFile()) {
- hasFiles = true;
- break;
+ // 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
+ 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 (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->mapToGlobal(pos));
+
+ menu.exec(ui->fileTree->viewport()->mapToGlobal(pos));
}
@@ -1184,70 +1494,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;
+void ModInfoDialog::changeConflictFilesVisibility(bool hide)
+{
+ bool changed = false;
+ bool stop = false;
+
+ const auto items = ui->overwriteTree->selectedItems();
+
+ qDebug().nospace()
+ << (hide ? "hiding" : "unhiding") << " "
+ << items.size() << " conflict files";
+
+ QFlags<FileRenamer::RenameFlags> flags = (hide ? FileRenamer::HIDE : FileRenamer::UNHIDE);
+ if (items.size() > 1) {
+ flags |= FileRenamer::MULTIPLE;
+ }
+
+ FileRenamer renamer(this, flags);
+
+ for (const auto* item : items) {
+ if (stop) {
+ break;
+ }
+
+ auto result = FileRenamer::RESULT_CANCEL;
+
+ if (hide) {
+ if (!canHideConflictItem(item)) {
+ qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping";
+ continue;
}
+ result = hideFile(renamer, item->data(0, Qt::UserRole).toString());
+
} else {
- return false;
+ if (!canUnhideConflictItem(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(oldName).arg(QDir::toNativeSeparators(newName)));
- return false;
- }
-}
+ switch (result) {
+ case FileRenamer::RESULT_OK: {
+ // will trigger a refresh at the end
+ changed = true;
+ break;
+ }
+ case FileRenamer::RESULT_SKIP: {
+ // nop
+ break;
+ }
-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;
+ case FileRenamer::RESULT_CANCEL: {
+ // stop right now, but make sure to refresh if needed
+ stop = true;
+ break;
}
- } else {
- return false;
}
}
- 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;
- }
-}
+ qDebug().nospace() << (hide ? "hiding" : "unhiding") << " conflict files done";
-void ModInfoDialog::hideConflictFile()
-{
- if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) {
+ if (changed) {
+ qDebug().nospace() << "triggering refresh";
emit originModified(m_Origin->getID());
refreshLists();
}
}
+void ModInfoDialog::hideConflictFiles()
+{
+ changeConflictFilesVisibility(true);
+}
-void ModInfoDialog::unhideConflictFile()
+void ModInfoDialog::unhideConflictFiles()
{
- if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) {
- emit originModified(m_Origin->getID());
- refreshLists();
- }
+ changeConflictFilesVisibility(false);
}
int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments)
@@ -1309,35 +1644,77 @@ int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &
}
}
-void ModInfoDialog::openDataFile()
+void ModInfoDialog::previewOverwriteDataFile()
{
- 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;
- }
+ // 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 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.
@@ -1395,54 +1772,158 @@ void ModInfoDialog::previewDataFile()
}
}
+bool ModInfoDialog::canHideFile(bool isArchive, const QString& filename) const
+{
+ if (isArchive) {
+ // can't hide files from archives
+ return false;
+ }
+
+ if (filename.endsWith(ModInfo::s_HiddenExt)) {
+ // already hidden
+ return false;
+ }
+
+ return true;
+}
+
+bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const
+{
+ if (isArchive) {
+ // can't unhide files from archives
+ return false;
+ }
+
+ if (!filename.endsWith(ModInfo::s_HiddenExt)) {
+ // already visible
+ return false;
+ }
+
+ return true;
+}
+
+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())) {
+ return false;
+ }
+
+ return true;
+}
void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos)
{
- m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y());
+ const auto selection = ui->overwriteTree->selectedItems();
+ if (selection.empty()) {
+ return;
+ }
- 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()));
- }
+ // 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;
- menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile()));
+ if (selection.size() == 1) {
+ // this is a single selection
+ const auto* item = selection[0];
+ if (!item) {
+ return;
+ }
- QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString();
- if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) {
- menu.addAction(tr("Preview"), this, SLOT(previewDataFile()));
- }
+ enableHide = canHideConflictItem(item);
+ enableUnhide = canUnhideConflictItem(item);
+ enablePreview = canPreviewConflictItem(item);
+ // open is always enabled
+ }
+ else {
+ // this is a multiple selection, don't show open/preview so users don't open
+ // 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;
+ }
- menu.exec(ui->overwriteTree->mapToGlobal(pos));
+ if (enableHide && enableUnhide) {
+ // found both, no need to check more
+ break;
+ }
+ }
}
}
+
+
+ QMenu menu;
+
+ if (enableHide) {
+ menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles()));
+ }
+
+ // 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()));
+ }
+
+ 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 (canPreviewConflictItem(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);
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 4c731c00..66c50be5 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -49,6 +49,141 @@ 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<RenameFlags> 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<RenameFlags> 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(const QString& newName);
+
+ /**
+ * 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();
@@ -156,12 +291,14 @@ private:
private slots:
- void hideConflictFile();
- void unhideConflictFile();
+ void hideConflictFiles();
+ void unhideConflictFiles();
+ void previewOverwriteDataFile();
+ void openOverwriteDataFile();
int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
- void previewDataFile();
- void openDataFile();
+ void previewOverwrittenDataFile();
+ void openOverwrittenDataFile();
void thumbnailClicked(const QString &fileName);
void linkClicked(const QUrl &url);
@@ -241,13 +378,22 @@ private:
QAction *m_HideAction;
QAction *m_UnhideAction;
- QTreeWidgetItem *m_ConflictsContextItem;
-
const MOShared::DirectoryEntry *m_Directory;
MOShared::FilesOrigin *m_Origin;
std::map<int, int> m_RealTabPos;
+ 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 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
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index 0211e704..39173a14 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -430,6 +430,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
<property name="textElideMode">
<enum>Qt::ElideLeft</enum>
</property>