From 67bc0d83277b36397766d97218d5ed9b4e25640a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 14 Jun 2019 20:21:07 -0400 Subject: clean up the list widgets and thumnails as well as recheck the tabs when refreshing after a change --- src/modinfodialog.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 188ea956..44934544 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -579,9 +579,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } initINITweaks(); @@ -1074,6 +1071,21 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( void ModInfoDialog::refreshFiles() { + // clearing + ui->textFileList->clear(); + ui->iniTweaksList->clear(); + ui->iniFileList->clear(); + ui->inactiveESPList->clear(); + ui->activeESPList->clear(); + ui->imageLabel->setPixmap({}); + + while (ui->thumbnailArea->count() > 0) { + auto* item = ui->thumbnailArea->takeAt(0); + delete item->widget(); + delete item; + } + + if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -1124,6 +1136,10 @@ void ModInfoDialog::refreshFiles() } } } + + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); + ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); + ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) @@ -1189,15 +1205,14 @@ void ModInfoDialog::openTab(int tab) void ModInfoDialog::thumbnailClicked(const QString &fileName) { - QLabel *imageLabel = findChild("imageLabel"); - imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); QImage image(fileName); if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(imageLabel->geometry().width()); + image = image.scaledToWidth(ui->imageLabel->geometry().width()); } else { - image = image.scaledToHeight(imageLabel->geometry().height()); + image = image.scaledToHeight(ui->imageLabel->geometry().height()); } - imageLabel->setPixmap(QPixmap::fromImage(image)); + ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } bool ModInfoDialog::allowNavigateFromTXT() -- cgit v1.3.1 From 59e0c4aa34fec9048d064705863c3269aacd86b5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 17:58:23 -0400 Subject: moved filerenamer to its own .cpp/.h pair --- src/CMakeLists.txt | 3 + src/filerenamer.cpp | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/filerenamer.h | 140 +++++++++++++++++++++++++++++++++++++ src/modinfodialog.cpp | 187 ------------------------------------------------- src/modinfodialog.h | 135 +----------------------------------- 5 files changed, 333 insertions(+), 321 deletions(-) create mode 100644 src/filerenamer.cpp create mode 100644 src/filerenamer.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 645a5a73..f654f9b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,6 +118,7 @@ SET(organizer_SRCS filterwidget.cpp statusbar.cpp apiuseraccount.cpp + filerenamer.cpp shared/windows_error.cpp shared/error_report.cpp @@ -217,6 +218,7 @@ SET(organizer_HDRS filterwidget.h statusbar.h apiuseraccount.h + filerenamer.h shared/windows_error.h shared/error_report.h @@ -402,6 +404,7 @@ set(utilities set(widgets descriptionpage genericicondelegate + filerenamer filterwidget icondelegate lcdnumber diff --git a/src/filerenamer.cpp b/src/filerenamer.cpp new file mode 100644 index 00000000..c5c6782b --- /dev/null +++ b/src/filerenamer.cpp @@ -0,0 +1,189 @@ +#include "filerenamer.h" +#include +#include + +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(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; +} diff --git a/src/filerenamer.h b/src/filerenamer.h new file mode 100644 index 00000000..cd57244c --- /dev/null +++ b/src/filerenamer.h @@ -0,0 +1,140 @@ +#ifndef FILERENAMER_H +#define FILERENAMER_H + +#include + +/** +* 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(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); +}; + +#endif // FILERENAMER_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 44934544..f553a7be 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -78,193 +78,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -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(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; -} - - ExpanderWidget::ExpanderWidget() : m_button(nullptr), m_content(nullptr), opened_(false) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index c4f65d8a..b3ce3d07 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -26,6 +26,7 @@ along with Mod Organizer. If not, see . #include "plugincontainer.h" #include "organizercore.h" #include "filterwidget.h" +#include "filerenamer.h" #include #include @@ -49,140 +50,6 @@ 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(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); -}; - /* Takes a QToolButton and a widget and creates an expandable widget. **/ -- cgit v1.3.1 From 6bd5bed29f3ebcc1155e615d7daa1c9cd1724efb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 18:54:19 -0400 Subject: added initial toolbar, splitter moved most of the text editor stuff to a new TextEditor class --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 77 ++++++++++++++++-------------- src/modinfodialog.h | 10 +++- src/modinfodialog.ui | 130 +++++++++++++++++++++++++++++++++----------------- src/texteditor.cpp | 76 +++++++++++++++++++++++++++++ src/texteditor.h | 36 ++++++++++++++ 6 files changed, 249 insertions(+), 83 deletions(-) create mode 100644 src/texteditor.cpp create mode 100644 src/texteditor.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f654f9b4..f75ab1e0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,6 +119,7 @@ SET(organizer_SRCS statusbar.cpp apiuseraccount.cpp filerenamer.cpp + texteditor.cpp shared/windows_error.cpp shared/error_report.cpp @@ -219,6 +220,7 @@ SET(organizer_HDRS statusbar.h apiuseraccount.h filerenamer.h + texteditor.h shared/windows_error.h shared/error_report.h @@ -414,6 +416,7 @@ set(widgets modidlineedit noeditdelegate qtgroupingproxy + texteditor viewmarkingscrollbar ) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f553a7be..db5b5fd0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -36,6 +36,7 @@ along with Mod Organizer. If not, see . #include "pluginlistsortproxy.h" #include "previewgenerator.h" #include "previewdialog.h" +#include "texteditor.h" #include #include @@ -358,6 +359,17 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } + m_textFileEditor.reset(new TextEditor(ui->textFileView)); + + connect( + m_textFileEditor.get(), &TextEditor::changed, + [&](bool b){ onTextFileChanged(b); }); + + ui->tabTextSplitter->setSizes({200, 1}); + ui->tabTextSplitter->setStretchFactor(0, 0); + ui->tabTextSplitter->setStretchFactor(1, 1); + setTextFileWordWrap(true); + // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be // done here because some of the checks below depend on the ui to decide which @@ -1030,9 +1042,12 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) bool ModInfoDialog::allowNavigateFromTXT() { - if (ui->saveTXTButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (m_textFileEditor->dirty()) { + const int res = QMessageBox::question( + this, tr("Save changes?"), + tr("Save changes to \"%1\"?").arg(m_textFileEditor->filename()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { return false; } else if (res == QMessageBox::Yes) { @@ -1042,7 +1057,6 @@ bool ModInfoDialog::allowNavigateFromTXT() return true; } - bool ModInfoDialog::allowNavigateFromINI() { if (ui->saveButton->isEnabled()) { @@ -1057,14 +1071,14 @@ bool ModInfoDialog::allowNavigateFromINI() return true; } - -void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +void ModInfoDialog::on_textFileList_currentItemChanged( + QListWidgetItem *current, QListWidgetItem *previous) { - QString fullPath = m_RootPath + "/" + current->text(); + const QString fullPath = m_RootPath + "/" + current->text(); - QVariant currentFile = ui->textFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation + if (fullPath == m_textFileEditor->filename()) { + // the new file is the same as the currently displayed file. May be the + // result of a cancellation return; } @@ -1075,17 +1089,12 @@ void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, } } - void ModInfoDialog::openTextFile(const QString &fileName) { - QString encoding; - ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); - ui->textFileView->setProperty("currentFile", fileName); - ui->textFileView->setProperty("encoding", encoding); - ui->saveTXTButton->setEnabled(false); + m_textFileEditor->load(fileName); + ui->textFileSave->setEnabled(false); } - void ModInfoDialog::openIniFile(const QString &fileName) { QFile iniFile(fileName); @@ -1155,35 +1164,31 @@ void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current } - void ModInfoDialog::on_saveButton_clicked() { saveCurrentIniFile(); } - -void ModInfoDialog::on_saveTXTButton_clicked() +void ModInfoDialog::on_textFileSave_clicked() { saveCurrentTextFile(); } +void ModInfoDialog::on_textFileWordWrap_clicked() +{ + setTextFileWordWrap(!m_textFileEditor->wordWrap()); +} + +void ModInfoDialog::setTextFileWordWrap(bool b) +{ + m_textFileEditor->wordWrap(b); + ui->textFileWordWrap->setChecked(b); +} void ModInfoDialog::saveCurrentTextFile() { - QVariant fileNameVar = ui->textFileView->property("currentFile"); - QVariant encodingVar = ui->textFileView->property("encoding"); - if (fileNameVar.isValid() && encodingVar.isValid()) { - QString fileName = fileNameVar.toString(); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveTXTButton->setEnabled(false); + m_textFileEditor->save(); + ui->textFileSave->setEnabled(false); } @@ -1214,9 +1219,9 @@ void ModInfoDialog::on_iniFileView_textChanged() } -void ModInfoDialog::on_textFileView_textChanged() +void ModInfoDialog::onTextFileChanged(bool b) { - ui->saveTXTButton->setEnabled(true); + ui->textFileSave->setEnabled(b); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index b3ce3d07..85226f45 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ namespace Ui { class QFileSystemModel; class QTreeView; class CategoryFactory; +class TextEditor; /* Takes a QToolButton and a widget and creates an expandable widget. @@ -218,14 +220,15 @@ private slots: void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); - void on_saveTXTButton_clicked(); + void on_textFileSave_clicked(); + void on_textFileWordWrap_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_iniFileView_textChanged(); - void on_textFileView_textChanged(); + void onTextFileChanged(bool b); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); @@ -304,6 +307,7 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; FilterWidget m_advancedConflictFilter; + std::unique_ptr m_textFileEditor; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); @@ -347,6 +351,8 @@ private: std::vector createGotoActions( const QList& selection); + + void setTextFileWordWrap(bool b); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index eed4e31f..4a29f6e4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -27,49 +27,89 @@ - Textfiles + Text Files - + - - - - 192 - 16777215 - - - - A list of text-files in the mod directory. + + + Qt::Horizontal - - A list of text-files in the mod directory like readmes. + + false + + + A list of text-files in the mod directory. + + + A list of text-files in the mod directory like readmes. + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Save + + + + + + + Word wrap + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - false - - - Save - - - - - @@ -493,12 +533,12 @@ text-align: left; 2 - - 365 - 200 + + 365 + File @@ -569,12 +609,12 @@ text-align: left; 2 - - 365 - 200 + + 365 + File @@ -1011,7 +1051,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -1024,7 +1064,7 @@ p, li { white-space: pre-wrap; } 200 - + about:blank diff --git a/src/texteditor.cpp b/src/texteditor.cpp new file mode 100644 index 00000000..eef74246 --- /dev/null +++ b/src/texteditor.cpp @@ -0,0 +1,76 @@ +#include "texteditor.h" +#include "utility.h" + +TextEditor::TextEditor(QPlainTextEdit* edit) + : m_edit(edit), m_dirty(false) +{ + m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + wordWrap(true); + + QObject::connect( + m_edit->document(), &QTextDocument::modificationChanged, + [&](bool b){ onChanged(b); }); +} + +bool TextEditor::load(const QString& filename) +{ + m_filename = filename; + m_edit->setPlainText(MOBase::readFileText(filename, &m_encoding)); + m_edit->document()->setModified(false); + + return true; +} + +bool TextEditor::save() +{ + if (m_filename.isEmpty() || m_encoding.isEmpty()) { + return false; + } + + QFile file(m_filename); + file.open(QIODevice::WriteOnly); + file.resize(0); + + QTextCodec* codec = QTextCodec::codecForName(m_encoding.toUtf8()); + QString data = m_edit->toPlainText().replace("\n", "\r\n"); + + file.write(codec->fromUnicode(data)); + m_edit->document()->setModified(false); + + return true; +} + +const QString& TextEditor::filename() const +{ + return m_filename; +} + +void TextEditor::wordWrap(bool b) +{ + if (b) { + m_edit->setLineWrapMode(QPlainTextEdit::WidgetWidth); + } else { + m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); + } +} + +bool TextEditor::wordWrap() const +{ + return (m_edit->lineWrapMode() == QPlainTextEdit::WidgetWidth); +} + +void TextEditor::dirty(bool b) +{ + m_dirty = b; +} + +bool TextEditor::dirty() const +{ + return m_dirty; +} + +void TextEditor::onChanged(bool b) +{ + dirty(b); + emit changed(b); +} diff --git a/src/texteditor.h b/src/texteditor.h new file mode 100644 index 00000000..25a15ad7 --- /dev/null +++ b/src/texteditor.h @@ -0,0 +1,36 @@ +#ifndef MO_TEXTEDITOR_H +#define MO_TEXTEDITOR_H + +#include + +class TextEditor : public QObject +{ + Q_OBJECT + +public: + TextEditor(QPlainTextEdit* edit); + + bool load(const QString& filename); + bool save(); + + const QString& filename() const; + + void wordWrap(bool b); + bool wordWrap() const; + + bool dirty() const; + +signals: + void changed(bool b); + +private: + QPlainTextEdit* m_edit; + QString m_filename; + QString m_encoding; + bool m_dirty; + + void onChanged(bool b); + void dirty(bool b); +}; + +#endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 9f509f9aa2a1066a223bfd23fb54862f9c988b78 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 19:36:55 -0400 Subject: TextEditor now has a dynamic toolbar --- src/modinfodialog.cpp | 31 --------------- src/modinfodialog.h | 5 --- src/modinfodialog.ui | 68 ++++----------------------------- src/texteditor.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/texteditor.h | 29 +++++++++++++- 5 files changed, 132 insertions(+), 103 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index db5b5fd0..169623ad 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -361,14 +361,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_textFileEditor.reset(new TextEditor(ui->textFileView)); - connect( - m_textFileEditor.get(), &TextEditor::changed, - [&](bool b){ onTextFileChanged(b); }); - ui->tabTextSplitter->setSizes({200, 1}); ui->tabTextSplitter->setStretchFactor(0, 0); ui->tabTextSplitter->setStretchFactor(1, 1); - setTextFileWordWrap(true); // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be @@ -1092,7 +1087,6 @@ void ModInfoDialog::on_textFileList_currentItemChanged( void ModInfoDialog::openTextFile(const QString &fileName) { m_textFileEditor->load(fileName); - ui->textFileSave->setEnabled(false); } void ModInfoDialog::openIniFile(const QString &fileName) @@ -1169,26 +1163,9 @@ void ModInfoDialog::on_saveButton_clicked() saveCurrentIniFile(); } -void ModInfoDialog::on_textFileSave_clicked() -{ - saveCurrentTextFile(); -} - -void ModInfoDialog::on_textFileWordWrap_clicked() -{ - setTextFileWordWrap(!m_textFileEditor->wordWrap()); -} - -void ModInfoDialog::setTextFileWordWrap(bool b) -{ - m_textFileEditor->wordWrap(b); - ui->textFileWordWrap->setChecked(b); -} - void ModInfoDialog::saveCurrentTextFile() { m_textFileEditor->save(); - ui->textFileSave->setEnabled(false); } @@ -1211,20 +1188,12 @@ void ModInfoDialog::saveCurrentIniFile() ui->saveButton->setEnabled(false); } - void ModInfoDialog::on_iniFileView_textChanged() { QPushButton* saveButton = findChild("saveButton"); saveButton->setEnabled(true); } - -void ModInfoDialog::onTextFileChanged(bool b) -{ - ui->textFileSave->setEnabled(b); -} - - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 85226f45..42ef990d 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -220,15 +220,12 @@ private slots: void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); - void on_textFileSave_clicked(); - void on_textFileWordWrap_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_iniFileView_textChanged(); - void onTextFileChanged(bool b); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); @@ -351,8 +348,6 @@ private: std::vector createGotoActions( const QList& selection); - - void setTextFileWordWrap(bool b); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 4a29f6e4..513506a7 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -46,67 +46,13 @@ A list of text-files in the mod directory like readmes. - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Save - - - - - - - Word wrap - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/src/texteditor.cpp b/src/texteditor.cpp index eef74246..9de2b9c1 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -1,15 +1,18 @@ #include "texteditor.h" #include "utility.h" +#include TextEditor::TextEditor(QPlainTextEdit* edit) - : m_edit(edit), m_dirty(false) + : m_edit(edit), m_toolbar(*this), m_dirty(false) { + setupToolbar(); + m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); QObject::connect( m_edit->document(), &QTextDocument::modificationChanged, - [&](bool b){ onChanged(b); }); + [&](bool b){ onModified(b); }); } bool TextEditor::load(const QString& filename) @@ -69,8 +72,99 @@ bool TextEditor::dirty() const return m_dirty; } -void TextEditor::onChanged(bool b) +void TextEditor::onModified(bool b) { dirty(b); - emit changed(b); + emit modified(b); +} + +void TextEditor::setupToolbar() +{ + auto* widget = wrapEditWidget(); + if (!widget) { + return; + } + + auto* layout = new QVBoxLayout(widget); + + // adding toolbar and edit + layout->addWidget(m_toolbar.widget()); + layout->addWidget(m_edit); + + // make the edit stretch + layout->setStretch(0, 0); + layout->setStretch(1, 1); + + // visuals + layout->setContentsMargins(0, 0, 0, 0); + widget->show(); +} + +QWidget* TextEditor::wrapEditWidget() +{ + auto widget = std::make_unique(); + + // wrapping the QPlainTextEdit into a new widget so the toolbar can be + // displayed above it + + if (auto* parentLayout=m_edit->parentWidget()->layout()) { + // the edit's parent has a regular layout, replace the edit by the new + // widget and delete the QLayoutItem that's returned as it's not needed + delete parentLayout->replaceWidget(m_edit, widget.get()); + } else if (auto* splitter=qobject_cast(m_edit->parentWidget())) { + // the edit's parent is a QSplitter, which doesn't have a layout; replace + // the edit by using its index in the splitter + splitter->replaceWidget(splitter->indexOf(m_edit), widget.get()); + } else { + // unknown parent + qCritical("TextEditor: cannot wrap edit widget to display a toolbar"); + return nullptr; + } + + return widget.release(); +} + + +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : + m_editor(editor), + m_widget(new QWidget), + m_save(new QAction(QObject::tr("&Save"))), + m_wordWrap(new QAction(QObject::tr("&Word wrap"))) +{ + QObject::connect(m_save, &QAction::triggered, [&]{ onSave(); }); + QObject::connect(m_wordWrap, &QAction::triggered, [&]{ onWordWrap(); }); + + auto* layout = new QHBoxLayout(m_widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setAlignment(Qt::AlignLeft); + + auto* b = new QToolButton; + b->setDefaultAction(m_save); + layout->addWidget(b); + + b = new QToolButton; + b->setDefaultAction(m_wordWrap); + layout->addWidget(b); + + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); +} + +QWidget* TextEditorToolbar::widget() +{ + return m_widget; +} + +void TextEditorToolbar::onTextModified(bool b) +{ + m_save->setEnabled(b); +} + +void TextEditorToolbar::onSave() +{ + m_editor.save(); +} + +void TextEditorToolbar::onWordWrap() +{ + m_editor.wordWrap(!m_editor.wordWrap()); } diff --git a/src/texteditor.h b/src/texteditor.h index 25a15ad7..10f6c4de 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -3,6 +3,27 @@ #include +class TextEditor; + +class TextEditorToolbar +{ +public: + TextEditorToolbar(TextEditor& editor); + + QWidget* widget(); + +private: + TextEditor& m_editor; + QWidget* m_widget; + QAction* m_save; + QAction* m_wordWrap; + + void onTextModified(bool b); + void onSave(); + void onWordWrap(); +}; + + class TextEditor : public QObject { Q_OBJECT @@ -21,16 +42,20 @@ public: bool dirty() const; signals: - void changed(bool b); + void modified(bool b); private: QPlainTextEdit* m_edit; + TextEditorToolbar m_toolbar; QString m_filename; QString m_encoding; bool m_dirty; - void onChanged(bool b); + void onModified(bool b); void dirty(bool b); + + void setupToolbar(); + QWidget* wrapEditWidget(); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From fc8d5c1708aa126bb3301079d15787943a0f47c9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 19:43:36 -0400 Subject: checkable wordwrap on toolbar --- src/texteditor.cpp | 25 ++++++++++++++++--------- src/texteditor.h | 5 +++-- 2 files changed, 19 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 9de2b9c1..56ccfd2c 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -10,6 +10,8 @@ TextEditor::TextEditor(QPlainTextEdit* edit) m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); + emit modified(false); + QObject::connect( m_edit->document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); @@ -55,6 +57,13 @@ void TextEditor::wordWrap(bool b) } else { m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); } + + emit wordWrapChanged(b); +} + +void TextEditor::toggleWordWrap() +{ + wordWrap(!wordWrap()); } bool TextEditor::wordWrap() const @@ -131,8 +140,10 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_save(new QAction(QObject::tr("&Save"))), m_wordWrap(new QAction(QObject::tr("&Word wrap"))) { - QObject::connect(m_save, &QAction::triggered, [&]{ onSave(); }); - QObject::connect(m_wordWrap, &QAction::triggered, [&]{ onWordWrap(); }); + QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); + + m_wordWrap->setCheckable(true); + QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); auto* layout = new QHBoxLayout(m_widget); layout->setContentsMargins(0, 0, 0, 0); @@ -147,6 +158,7 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : layout->addWidget(b); QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); + QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); } QWidget* TextEditorToolbar::widget() @@ -159,12 +171,7 @@ void TextEditorToolbar::onTextModified(bool b) m_save->setEnabled(b); } -void TextEditorToolbar::onSave() -{ - m_editor.save(); -} - -void TextEditorToolbar::onWordWrap() +void TextEditorToolbar::onWordWrap(bool b) { - m_editor.wordWrap(!m_editor.wordWrap()); + m_wordWrap->setChecked(b); } diff --git a/src/texteditor.h b/src/texteditor.h index 10f6c4de..c196b7a4 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -19,8 +19,7 @@ private: QAction* m_wordWrap; void onTextModified(bool b); - void onSave(); - void onWordWrap(); + void onWordWrap(bool b); }; @@ -37,12 +36,14 @@ public: const QString& filename() const; void wordWrap(bool b); + void toggleWordWrap(); bool wordWrap() const; bool dirty() const; signals: void modified(bool b); + void wordWrapChanged(bool b); private: QPlainTextEdit* m_edit; -- cgit v1.3.1 From c79cf72d2250631110e8605ced6f34dda0378dc0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 15 Jun 2019 21:00:04 -0400 Subject: line numbers, which required inheriting from QPlainTextEdit instead --- src/modinfodialog.cpp | 12 ++-- src/modinfodialog.h | 1 - src/modinfodialog.ui | 14 ++--- src/texteditor.cpp | 148 +++++++++++++++++++++++++++++++++++++++++++------- src/texteditor.h | 33 +++++++++-- 5 files changed, 170 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 169623ad..743f76b0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -359,7 +359,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - m_textFileEditor.reset(new TextEditor(ui->textFileView)); + ui->textFileView->setupToolbar(); ui->tabTextSplitter->setSizes({200, 1}); ui->tabTextSplitter->setStretchFactor(0, 0); @@ -1037,10 +1037,10 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) bool ModInfoDialog::allowNavigateFromTXT() { - if (m_textFileEditor->dirty()) { + if (ui->textFileView->dirty()) { const int res = QMessageBox::question( this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(m_textFileEditor->filename()), + tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (res == QMessageBox::Cancel) { @@ -1071,7 +1071,7 @@ void ModInfoDialog::on_textFileList_currentItemChanged( { const QString fullPath = m_RootPath + "/" + current->text(); - if (fullPath == m_textFileEditor->filename()) { + if (fullPath == ui->textFileView->filename()) { // the new file is the same as the currently displayed file. May be the // result of a cancellation return; @@ -1086,7 +1086,7 @@ void ModInfoDialog::on_textFileList_currentItemChanged( void ModInfoDialog::openTextFile(const QString &fileName) { - m_textFileEditor->load(fileName); + ui->textFileView->load(fileName); } void ModInfoDialog::openIniFile(const QString &fileName) @@ -1165,7 +1165,7 @@ void ModInfoDialog::on_saveButton_clicked() void ModInfoDialog::saveCurrentTextFile() { - m_textFileEditor->save(); + ui->textFileView->save(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 42ef990d..09924671 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -304,7 +304,6 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; FilterWidget m_advancedConflictFilter; - std::unique_ptr m_textFileEditor; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 513506a7..785fdeb4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -46,14 +46,7 @@ A list of text-files in the mod directory like readmes. - - - - - - - - + @@ -1217,6 +1210,11 @@ p, li { white-space: pre-wrap; } QLineEdit
modidlineedit.h
+ + TextEditor + QPlainTextEdit +
texteditor.h
+
diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 56ccfd2c..ecb46547 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -2,26 +2,27 @@ #include "utility.h" #include -TextEditor::TextEditor(QPlainTextEdit* edit) - : m_edit(edit), m_toolbar(*this), m_dirty(false) +TextEditor::TextEditor(QWidget* parent) : + QPlainTextEdit(parent), + m_toolbar(*this), m_lineNumbers(nullptr), m_dirty(false) { - setupToolbar(); - - m_edit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); + m_lineNumbers = new TextEditorLineNumbers(*this); + emit modified(false); QObject::connect( - m_edit->document(), &QTextDocument::modificationChanged, + document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); } bool TextEditor::load(const QString& filename) { m_filename = filename; - m_edit->setPlainText(MOBase::readFileText(filename, &m_encoding)); - m_edit->document()->setModified(false); + setPlainText(MOBase::readFileText(filename, &m_encoding)); + document()->setModified(false); return true; } @@ -37,10 +38,10 @@ bool TextEditor::save() file.resize(0); QTextCodec* codec = QTextCodec::codecForName(m_encoding.toUtf8()); - QString data = m_edit->toPlainText().replace("\n", "\r\n"); + QString data = toPlainText().replace("\n", "\r\n"); file.write(codec->fromUnicode(data)); - m_edit->document()->setModified(false); + document()->setModified(false); return true; } @@ -53,9 +54,9 @@ const QString& TextEditor::filename() const void TextEditor::wordWrap(bool b) { if (b) { - m_edit->setLineWrapMode(QPlainTextEdit::WidgetWidth); + setLineWrapMode(QPlainTextEdit::WidgetWidth); } else { - m_edit->setLineWrapMode(QPlainTextEdit::NoWrap); + setLineWrapMode(QPlainTextEdit::NoWrap); } emit wordWrapChanged(b); @@ -68,7 +69,7 @@ void TextEditor::toggleWordWrap() bool TextEditor::wordWrap() const { - return (m_edit->lineWrapMode() == QPlainTextEdit::WidgetWidth); + return (lineWrapMode() == QPlainTextEdit::WidgetWidth); } void TextEditor::dirty(bool b) @@ -98,7 +99,7 @@ void TextEditor::setupToolbar() // adding toolbar and edit layout->addWidget(m_toolbar.widget()); - layout->addWidget(m_edit); + layout->addWidget(this); // make the edit stretch layout->setStretch(0, 0); @@ -116,23 +117,132 @@ QWidget* TextEditor::wrapEditWidget() // wrapping the QPlainTextEdit into a new widget so the toolbar can be // displayed above it - if (auto* parentLayout=m_edit->parentWidget()->layout()) { + if (auto* parentLayout=parentWidget()->layout()) { // the edit's parent has a regular layout, replace the edit by the new // widget and delete the QLayoutItem that's returned as it's not needed - delete parentLayout->replaceWidget(m_edit, widget.get()); - } else if (auto* splitter=qobject_cast(m_edit->parentWidget())) { + delete parentLayout->replaceWidget(this, widget.get()); + + } else if (auto* splitter=qobject_cast(parentWidget())) { // the edit's parent is a QSplitter, which doesn't have a layout; replace // the edit by using its index in the splitter - splitter->replaceWidget(splitter->indexOf(m_edit), widget.get()); + auto index = splitter->indexOf(this); + + if (index == -1) { + qCritical( + "TextEditor: cannot wrap edit widget to display a toolbar, " + "parent is a splitter, but widget isn't in it"); + + return nullptr; + } + + splitter->replaceWidget(index, widget.get()); + } else { // unknown parent - qCritical("TextEditor: cannot wrap edit widget to display a toolbar"); + qCritical( + "TextEditor: cannot wrap edit widget to display a toolbar, " + "no parent or parent has no layout"); + return nullptr; } return widget.release(); } +void TextEditor::resizeEvent(QResizeEvent* e) +{ + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + m_lineNumbers->setGeometry(QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); +} + +void TextEditor::paintLineNumbers(QPaintEvent* e) +{ + QPainter painter(m_lineNumbers); + painter.fillRect(e->rect(), Qt::lightGray); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + while (block.isValid() && top <= e->rect().bottom()) { + if (block.isVisible() && bottom >= e->rect().top()) { + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + + painter.drawText( + 0, top, m_lineNumbers->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} + + +TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) + : QWidget(&editor), m_editor(editor) +{ + setFont(editor.font()); + + connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); + connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); + //connect(e, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + + updateAreaWidth(); + //highlightCurrentLine(); +} + +QSize TextEditorLineNumbers::sizeHint() const +{ + return QSize(areaWidth(), 0); +} + +void TextEditorLineNumbers::paintEvent(QPaintEvent* e) +{ + m_editor.paintLineNumbers(e); +} + +int TextEditorLineNumbers::areaWidth() const +{ + int digits = 1; + int max = qMax(1, m_editor.blockCount()); + + while (max >= 10) { + max /= 10; + ++digits; + } + + digits = std::max(3, digits); + + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + + return space; +} + +void TextEditorLineNumbers::updateAreaWidth() +{ + m_editor.setViewportMargins(areaWidth(), 0, 0, 0); +} + +void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) +{ + if (dy) { + scroll(0, dy); + } else { + update(0, rect.y(), width(), rect.height()); + } + + if (rect.contains(m_editor.viewport()->rect())) { + updateAreaWidth(); + } +} + TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index c196b7a4..af542341 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -23,12 +23,33 @@ private: }; -class TextEditor : public QObject +class TextEditorLineNumbers : public QWidget +{ +public: + TextEditorLineNumbers(TextEditor& editor); + QSize sizeHint() const override; + int areaWidth() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + TextEditor& m_editor; + + void updateAreaWidth(); + void updateArea(const QRect &rect, int dy); +}; + + +class TextEditor : public QPlainTextEdit { Q_OBJECT + friend class TextEditorLineNumbers; public: - TextEditor(QPlainTextEdit* edit); + TextEditor(QWidget* parent=nullptr); + + void setupToolbar(); bool load(const QString& filename); bool save(); @@ -45,9 +66,12 @@ signals: void modified(bool b); void wordWrapChanged(bool b); +protected: + void resizeEvent(QResizeEvent* e) override; + private: - QPlainTextEdit* m_edit; TextEditorToolbar m_toolbar; + TextEditorLineNumbers* m_lineNumbers; QString m_filename; QString m_encoding; bool m_dirty; @@ -55,8 +79,9 @@ private: void onModified(bool b); void dirty(bool b); - void setupToolbar(); QWidget* wrapEditWidget(); + + void paintLineNumbers(QPaintEvent* e); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From a2b68c3a2e17198e834684fe1719b19464559657 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 01:11:13 -0400 Subject: text and background color, simple syntax highlighter that doesn't do anything highlight current line --- src/texteditor.cpp | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/texteditor.h | 34 ++++++++++++++++ 2 files changed, 145 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ecb46547..7a83e461 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -4,12 +4,14 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), - m_toolbar(*this), m_lineNumbers(nullptr), m_dirty(false) + m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), + m_dirty(false) { setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); wordWrap(true); m_lineNumbers = new TextEditorLineNumbers(*this); + m_highlighter = new TextEditorHighlighter(document()); emit modified(false); @@ -82,6 +84,48 @@ bool TextEditor::dirty() const return m_dirty; } +QString TextEditor::textColor() const +{ + return m_highlighter->textColor().name(QColor::HexArgb); +} + +void TextEditor::setTextColor(const QString& s) +{ + const QColor c(s); + if (!c.isValid()) { + qWarning() << "TextEditor: invalid text color '" << s << "'"; + return; + } + + m_highlighter->setTextColor(c); +} + +QString TextEditor::backgroundColor() const +{ + return m_highlighter->backgroundColor().name(QColor::HexArgb); +} + +void TextEditor::setBackgroundColor(const QString& s) +{ + const QColor c(s); + if (!c.isValid()) { + qWarning() << "TextEditor: invalid backgorund color '" << s << "'"; + return; + } + + if (m_highlighter->backgroundColor() == c) { + return; + } + + m_highlighter->setBackgroundColor(c); + + setStyleSheet(QString("QPlainTextEdit{ background-color: rgba(%1, %2, %3, %4); }") + .arg(c.redF() * 255) + .arg(c.greenF() * 255) + .arg(c.blueF() * 255) + .arg(c.alphaF())); +} + void TextEditor::onModified(bool b) { dirty(b); @@ -185,6 +229,50 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) } +TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : + QSyntaxHighlighter(doc), + m_background(QColor("transparent")), + m_text(QColor("black")) +{ +} + +QColor TextEditorHighlighter::backgroundColor() +{ + return m_background; +} + +void TextEditorHighlighter::setBackgroundColor(const QColor& c) +{ + m_background = c; + changed(); +} + +QColor TextEditorHighlighter::textColor() +{ + return m_text; +} + +void TextEditorHighlighter::setTextColor(const QColor& c) +{ + m_text = c; + changed(); +} + +void TextEditorHighlighter::highlightBlock(const QString& s) +{ + QTextCharFormat f; + f.setBackground(m_background); + f.setForeground(m_text); + + setFormat(0, s.size(), f); +} + +void TextEditorHighlighter::changed() +{ + rehighlight(); +} + + TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) : QWidget(&editor), m_editor(editor) { @@ -192,10 +280,10 @@ TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); - //connect(e, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + connect(&m_editor, &QPlainTextEdit::cursorPositionChanged, [&]{ highlightCurrentLine(); }); updateAreaWidth(); - //highlightCurrentLine(); + highlightCurrentLine(); } QSize TextEditorLineNumbers::sizeHint() const @@ -211,7 +299,7 @@ void TextEditorLineNumbers::paintEvent(QPaintEvent* e) int TextEditorLineNumbers::areaWidth() const { int digits = 1; - int max = qMax(1, m_editor.blockCount()); + int max = std::max(1, m_editor.blockCount()); while (max >= 10) { max /= 10; @@ -243,6 +331,25 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } } +void TextEditorLineNumbers::highlightCurrentLine() +{ + QList extraSelections; + + if (!m_editor.isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::yellow).lighter(160); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = m_editor.textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + m_editor.setExtraSelections(extraSelections); +} + TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index af542341..0ae39c60 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -23,6 +23,8 @@ private: }; +// mostly from https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html +// class TextEditorLineNumbers : public QWidget { public: @@ -38,12 +40,37 @@ private: void updateAreaWidth(); void updateArea(const QRect &rect, int dy); + void highlightCurrentLine(); +}; + + +class TextEditorHighlighter : public QSyntaxHighlighter +{ +public: + TextEditorHighlighter(QTextDocument* doc); + + QColor backgroundColor(); + void setBackgroundColor(const QColor& c); + + QColor textColor(); + void setTextColor(const QColor& c); + +protected: + void highlightBlock(const QString& text) override; + +private: + QColor m_background, m_text; + + void changed(); }; class TextEditor : public QPlainTextEdit { Q_OBJECT + Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) + Q_PROPERTY(QString backgroundColor READ backgroundColor WRITE setBackgroundColor) + friend class TextEditorLineNumbers; public: @@ -62,6 +89,12 @@ public: bool dirty() const; + QString textColor() const; + void setTextColor(const QString& s); + + QString backgroundColor() const; + void setBackgroundColor(const QString& s); + signals: void modified(bool b); void wordWrapChanged(bool b); @@ -72,6 +105,7 @@ protected: private: TextEditorToolbar m_toolbar; TextEditorLineNumbers* m_lineNumbers; + TextEditorHighlighter* m_highlighter; QString m_filename; QString m_encoding; bool m_dirty; -- cgit v1.3.1 From 0084ca6c4c1fad76dafe700aad3db4fdb5ef2750 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 02:53:52 -0400 Subject: text editor default style changed colour properties to use an actual QColor stylable highlight background color padding for line numbers --- src/texteditor.cpp | 187 +++++++++++++++++++++++++++++++++++------------------ src/texteditor.h | 48 ++++++++++---- 2 files changed, 159 insertions(+), 76 deletions(-) (limited to 'src') diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 7a83e461..ccafb37a 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -7,17 +7,53 @@ TextEditor::TextEditor(QWidget* parent) : m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), m_dirty(false) { - setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - wordWrap(true); - m_lineNumbers = new TextEditorLineNumbers(*this); m_highlighter = new TextEditorHighlighter(document()); + setDefaultStyle(); + wordWrap(true); + emit modified(false); - QObject::connect( + connect( document(), &QTextDocument::modificationChanged, [&](bool b){ onModified(b); }); + + connect( + this, &QPlainTextEdit::cursorPositionChanged, + [&]{ highlightCurrentLine(); }); +} + +void TextEditor::setDefaultStyle() +{ + const auto font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + setFont(font); + m_lineNumbers->setFont(font); + + QColor textColor(Qt::black); + QColor altTextColor(Qt::darkGray); + QColor backgroundColor(Qt::white); + + { + auto w = std::make_unique(); + + if (auto* s=style()) { + s->polish(w.get()); + } + + textColor = w->palette().color(QPalette::WindowText); + altTextColor = w->palette().color(QPalette::Disabled, QPalette::WindowText); + backgroundColor = w->palette().color(QPalette::Window); + } + + setTextColor(textColor); + m_lineNumbers->setTextColor(altTextColor); + + setBackgroundColor(backgroundColor); + m_lineNumbers->setBackgroundColor(backgroundColor); + + setHighlightBackgroundColor(backgroundColor); } bool TextEditor::load(const QString& filename) @@ -84,35 +120,13 @@ bool TextEditor::dirty() const return m_dirty; } -QString TextEditor::textColor() const -{ - return m_highlighter->textColor().name(QColor::HexArgb); -} - -void TextEditor::setTextColor(const QString& s) +QColor TextEditor::backgroundColor() const { - const QColor c(s); - if (!c.isValid()) { - qWarning() << "TextEditor: invalid text color '" << s << "'"; - return; - } - - m_highlighter->setTextColor(c); + return m_highlighter->backgroundColor(); } -QString TextEditor::backgroundColor() const +void TextEditor::setBackgroundColor(const QColor& c) { - return m_highlighter->backgroundColor().name(QColor::HexArgb); -} - -void TextEditor::setBackgroundColor(const QString& s) -{ - const QColor c(s); - if (!c.isValid()) { - qWarning() << "TextEditor: invalid backgorund color '" << s << "'"; - return; - } - if (m_highlighter->backgroundColor() == c) { return; } @@ -126,6 +140,27 @@ void TextEditor::setBackgroundColor(const QString& s) .arg(c.alphaF())); } +QColor TextEditor::textColor() const +{ + return m_highlighter->textColor(); +} + +void TextEditor::setTextColor(const QColor& c) +{ + m_highlighter->setTextColor(c); +} + +QColor TextEditor::highlightBackgroundColor() const +{ + return m_highlightBackground; +} + +void TextEditor::setHighlightBackgroundColor(const QColor& c) +{ + m_highlightBackground = c; + update(); +} + void TextEditor::onModified(bool b) { dirty(b); @@ -201,10 +236,12 @@ void TextEditor::resizeEvent(QResizeEvent* e) m_lineNumbers->setGeometry(QRect(cr.left(), cr.top(), m_lineNumbers->areaWidth(), cr.height())); } -void TextEditor::paintLineNumbers(QPaintEvent* e) +void TextEditor::paintLineNumbers(QPaintEvent* e, const QColor& textColor) { + QStyleOption opt; + opt.init(m_lineNumbers); + QPainter painter(m_lineNumbers); - painter.fillRect(e->rect(), Qt::lightGray); QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); @@ -214,10 +251,10 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) while (block.isValid() && top <= e->rect().bottom()) { if (block.isVisible() && bottom >= e->rect().top()) { QString number = QString::number(blockNumber + 1); - painter.setPen(Qt::black); + painter.setPen(textColor); painter.drawText( - 0, top, m_lineNumbers->width(), fontMetrics().height(), + 0, top, m_lineNumbers->width() - 3, fontMetrics().height(), Qt::AlignRight, number); } @@ -228,6 +265,25 @@ void TextEditor::paintLineNumbers(QPaintEvent* e) } } +void TextEditor::highlightCurrentLine() +{ + QList extraSelections; + + if (!isReadOnly()) { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::yellow).lighter(160); + + selection.format.setBackground(m_highlightBackground); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + setExtraSelections(extraSelections); +} + TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : QSyntaxHighlighter(doc), @@ -236,7 +292,7 @@ TextEditorHighlighter::TextEditorHighlighter(QTextDocument* doc) : { } -QColor TextEditorHighlighter::backgroundColor() +QColor TextEditorHighlighter::backgroundColor() const { return m_background; } @@ -247,7 +303,7 @@ void TextEditorHighlighter::setBackgroundColor(const QColor& c) changed(); } -QColor TextEditorHighlighter::textColor() +QColor TextEditorHighlighter::textColor() const { return m_text; } @@ -274,16 +330,14 @@ void TextEditorHighlighter::changed() TextEditorLineNumbers::TextEditorLineNumbers(TextEditor& editor) - : QWidget(&editor), m_editor(editor) + : QFrame(&editor), m_editor(editor) { setFont(editor.font()); connect(&m_editor, &QPlainTextEdit::blockCountChanged, [&]{ updateAreaWidth(); }); connect(&m_editor, &QPlainTextEdit::updateRequest, [&](auto&& rect, int dy){ updateArea(rect, dy); }); - connect(&m_editor, &QPlainTextEdit::cursorPositionChanged, [&]{ highlightCurrentLine(); }); updateAreaWidth(); - highlightCurrentLine(); } QSize TextEditorLineNumbers::sizeHint() const @@ -291,11 +345,6 @@ QSize TextEditorLineNumbers::sizeHint() const return QSize(areaWidth(), 0); } -void TextEditorLineNumbers::paintEvent(QPaintEvent* e) -{ - m_editor.paintLineNumbers(e); -} - int TextEditorLineNumbers::areaWidth() const { int digits = 1; @@ -308,11 +357,42 @@ int TextEditorLineNumbers::areaWidth() const digits = std::max(3, digits); - int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits + 3; return space; } +QColor TextEditorLineNumbers::textColor() const +{ + return m_text; +} + +void TextEditorLineNumbers::setTextColor(const QColor& c) +{ + m_text = c; + m_editor.update(); +} + +QColor TextEditorLineNumbers::backgroundColor() const +{ + return m_background; +} + +void TextEditorLineNumbers::setBackgroundColor(const QColor& c) +{ + m_background = c; + m_editor.update(); +} + +void TextEditorLineNumbers::paintEvent(QPaintEvent* e) +{ + QPainter painter(this); + painter.fillRect(e->rect(), m_background); + + QFrame::paintEvent(e); + m_editor.paintLineNumbers(e, m_text); +} + void TextEditorLineNumbers::updateAreaWidth() { m_editor.setViewportMargins(areaWidth(), 0, 0, 0); @@ -331,25 +411,6 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } } -void TextEditorLineNumbers::highlightCurrentLine() -{ - QList extraSelections; - - if (!m_editor.isReadOnly()) { - QTextEdit::ExtraSelection selection; - - QColor lineColor = QColor(Qt::yellow).lighter(160); - - selection.format.setBackground(lineColor); - selection.format.setProperty(QTextFormat::FullWidthSelection, true); - selection.cursor = m_editor.textCursor(); - selection.cursor.clearSelection(); - extraSelections.append(selection); - } - - m_editor.setExtraSelections(extraSelections); -} - TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), diff --git a/src/texteditor.h b/src/texteditor.h index 0ae39c60..0f7447ff 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -5,8 +5,10 @@ class TextEditor; -class TextEditorToolbar +class TextEditorToolbar : public QObject { + Q_OBJECT; + public: TextEditorToolbar(TextEditor& editor); @@ -25,34 +27,47 @@ private: // mostly from https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html // -class TextEditorLineNumbers : public QWidget +class TextEditorLineNumbers : public QFrame { + Q_OBJECT; + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor); + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + public: TextEditorLineNumbers(TextEditor& editor); + QSize sizeHint() const override; int areaWidth() const; + QColor textColor() const; + void setTextColor(const QColor& c); + + QColor backgroundColor() const; + void setBackgroundColor(const QColor& c); + protected: void paintEvent(QPaintEvent *event) override; private: TextEditor& m_editor; + QColor m_background, m_text; void updateAreaWidth(); void updateArea(const QRect &rect, int dy); - void highlightCurrentLine(); }; class TextEditorHighlighter : public QSyntaxHighlighter { + Q_OBJECT; + public: TextEditorHighlighter(QTextDocument* doc); - QColor backgroundColor(); + QColor backgroundColor() const; void setBackgroundColor(const QColor& c); - QColor textColor(); + QColor textColor() const; void setTextColor(const QColor& c); protected: @@ -67,9 +82,10 @@ private: class TextEditor : public QPlainTextEdit { - Q_OBJECT - Q_PROPERTY(QString textColor READ textColor WRITE setTextColor) - Q_PROPERTY(QString backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_OBJECT; + Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor); + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + Q_PROPERTY(QColor highlightBackgroundColor READ highlightBackgroundColor WRITE setHighlightBackgroundColor); friend class TextEditorLineNumbers; @@ -89,11 +105,14 @@ public: bool dirty() const; - QString textColor() const; - void setTextColor(const QString& s); + QColor backgroundColor() const; + void setBackgroundColor(const QColor& c); - QString backgroundColor() const; - void setBackgroundColor(const QString& s); + QColor textColor() const; + void setTextColor(const QColor& c); + + QColor highlightBackgroundColor() const; + void setHighlightBackgroundColor(const QColor& c); signals: void modified(bool b); @@ -106,16 +125,19 @@ private: TextEditorToolbar m_toolbar; TextEditorLineNumbers* m_lineNumbers; TextEditorHighlighter* m_highlighter; + QColor m_highlightBackground; QString m_filename; QString m_encoding; bool m_dirty; + void setDefaultStyle(); void onModified(bool b); void dirty(bool b); QWidget* wrapEditWidget(); - void paintLineNumbers(QPaintEvent* e); + void highlightCurrentLine(); + void paintLineNumbers(QPaintEvent* e, const QColor& textColor); }; #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 491b96c0528159f0830e439f5c5a55e3ff09ad1a Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 03:06:31 -0400 Subject: made TextEditorToolbar a widget added save and word wrap icons --- src/resources/save.svg | 1 + src/resources/word-wrap.svg | 1 + src/texteditor.cpp | 17 ++++++----------- src/texteditor.h | 7 ++----- 4 files changed, 10 insertions(+), 16 deletions(-) create mode 100644 src/resources/save.svg create mode 100644 src/resources/word-wrap.svg (limited to 'src') diff --git a/src/resources/save.svg b/src/resources/save.svg new file mode 100644 index 00000000..3fb2a8df --- /dev/null +++ b/src/resources/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/resources/word-wrap.svg b/src/resources/word-wrap.svg new file mode 100644 index 00000000..63b1f1f0 --- /dev/null +++ b/src/resources/word-wrap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ccafb37a..78ce1610 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -4,9 +4,10 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), - m_toolbar(*this), m_lineNumbers(nullptr), m_highlighter(nullptr), + m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), m_dirty(false) { + m_toolbar = new TextEditorToolbar(*this); m_lineNumbers = new TextEditorLineNumbers(*this); m_highlighter = new TextEditorHighlighter(document()); @@ -177,7 +178,7 @@ void TextEditor::setupToolbar() auto* layout = new QVBoxLayout(widget); // adding toolbar and edit - layout->addWidget(m_toolbar.widget()); + layout->addWidget(m_toolbar); layout->addWidget(this); // make the edit stretch @@ -414,16 +415,15 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : m_editor(editor), - m_widget(new QWidget), - m_save(new QAction(QObject::tr("&Save"))), - m_wordWrap(new QAction(QObject::tr("&Word wrap"))) + m_save(new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"))), + m_wordWrap(new QAction(QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"))) { QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); m_wordWrap->setCheckable(true); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); - auto* layout = new QHBoxLayout(m_widget); + auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setAlignment(Qt::AlignLeft); @@ -439,11 +439,6 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); } -QWidget* TextEditorToolbar::widget() -{ - return m_widget; -} - void TextEditorToolbar::onTextModified(bool b) { m_save->setEnabled(b); diff --git a/src/texteditor.h b/src/texteditor.h index 0f7447ff..eef5ca52 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -5,18 +5,15 @@ class TextEditor; -class TextEditorToolbar : public QObject +class TextEditorToolbar : public QFrame { Q_OBJECT; public: TextEditorToolbar(TextEditor& editor); - QWidget* widget(); - private: TextEditor& m_editor; - QWidget* m_widget; QAction* m_save; QAction* m_wordWrap; @@ -122,7 +119,7 @@ protected: void resizeEvent(QResizeEvent* e) override; private: - TextEditorToolbar m_toolbar; + TextEditorToolbar* m_toolbar; TextEditorLineNumbers* m_lineNumbers; TextEditorHighlighter* m_highlighter; QColor m_highlightBackground; -- cgit v1.3.1 From ab7f42dd97e5e6f6076877469a774213bfc3bf76 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 03:12:49 -0400 Subject: split ExpanderWidget into its own set of files --- src/CMakeLists.txt | 3 +++ src/expanderwidget.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/expanderwidget.h | 46 ++++++++++++++++++++++++++++++++++++++++++ src/modinfodialog.cpp | 54 -------------------------------------------------- src/modinfodialog.h | 42 +-------------------------------------- 5 files changed, 104 insertions(+), 95 deletions(-) create mode 100644 src/expanderwidget.cpp create mode 100644 src/expanderwidget.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f75ab1e0..d5aa8247 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -120,6 +120,7 @@ SET(organizer_SRCS apiuseraccount.cpp filerenamer.cpp texteditor.cpp + expanderwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -221,6 +222,7 @@ SET(organizer_HDRS apiuseraccount.h filerenamer.h texteditor.h + expanderwidget.h shared/windows_error.h shared/error_report.h @@ -405,6 +407,7 @@ set(utilities set(widgets descriptionpage + expanderwidget genericicondelegate filerenamer filterwidget diff --git a/src/expanderwidget.cpp b/src/expanderwidget.cpp new file mode 100644 index 00000000..2f47da5b --- /dev/null +++ b/src/expanderwidget.cpp @@ -0,0 +1,54 @@ +#include "expanderwidget.h" + +ExpanderWidget::ExpanderWidget() + : m_button(nullptr), m_content(nullptr), opened_(false) +{ +} + +ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) + : ExpanderWidget() +{ + set(button, content); +} + +void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) +{ + m_button = button; + m_content = content; + + m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); + + toggle(o); +} + +void ExpanderWidget::toggle() +{ + if (opened()) { + toggle(false); + } + else { + toggle(true); + } +} + +void ExpanderWidget::toggle(bool b) +{ + if (b) { + m_button->setArrowType(Qt::DownArrow); + m_content->show(); + } else { + m_button->setArrowType(Qt::RightArrow); + m_content->hide(); + } + + // the state has to be remembered instead of using m_content's visibility + // because saving the state in saveConflictExpandersState() happens after the + // dialog is closed, which marks all the widgets hidden + opened_ = b; +} + +bool ExpanderWidget::opened() const +{ + return opened_; +} diff --git a/src/expanderwidget.h b/src/expanderwidget.h new file mode 100644 index 00000000..da3eb9d6 --- /dev/null +++ b/src/expanderwidget.h @@ -0,0 +1,46 @@ +#ifndef EXPANDERWIDGET_H +#define EXPANDERWIDGET_H + +#include + +/* Takes a QToolButton and a widget and creates an expandable widget. +**/ +class ExpanderWidget +{ +public: + /** empty expander, use set() + **/ + ExpanderWidget(); + + /** see set() + **/ + ExpanderWidget(QToolButton* button, QWidget* content); + + /** @brief sets the button and content widgets to use + * the button will be given an arrow icon, clicking it will toggle the + * visibility of the given widget + * @param button the button that toggles the content + * @param content the widget that will be shown or hidden + * @param opened initial state, defaults to closed + **/ + void set(QToolButton* button, QWidget* content, bool opened=false); + + /** either opens or closes the expander depending on the current state + **/ + void toggle(); + + /** sets the current state of the expander + **/ + void toggle(bool b); + + /** returns whether the expander is currently opened + **/ + bool opened() const; + +private: + QToolButton* m_button; + QWidget* m_content; + bool opened_; +}; + +#endif // EXPANDERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 743f76b0..065058fc 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -79,60 +79,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -ExpanderWidget::ExpanderWidget() - : m_button(nullptr), m_content(nullptr), opened_(false) -{ -} - -ExpanderWidget::ExpanderWidget(QToolButton* button, QWidget* content) - : ExpanderWidget() -{ - set(button, content); -} - -void ExpanderWidget::set(QToolButton* button, QWidget* content, bool o) -{ - m_button = button; - m_content = content; - - m_button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); - - toggle(o); -} - -void ExpanderWidget::toggle() -{ - if (opened()) { - toggle(false); - } - else { - toggle(true); - } -} - -void ExpanderWidget::toggle(bool b) -{ - if (b) { - m_button->setArrowType(Qt::DownArrow); - m_content->show(); - } else { - m_button->setArrowType(Qt::RightArrow); - m_content->hide(); - } - - // the state has to be remembered instead of using m_content's visibility - // because saving the state in saveConflictExpandersState() happens after the - // dialog is closed, which marks all the widgets hidden - opened_ = b; -} - -bool ExpanderWidget::opened() const -{ - return opened_; -} - - class ElideLeftDelegate : public QStyledItemDelegate { public: diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 09924671..df450ac0 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see . #include "organizercore.h" #include "filterwidget.h" #include "filerenamer.h" +#include "expanderwidget.h" #include #include @@ -53,47 +54,6 @@ class CategoryFactory; class TextEditor; -/* Takes a QToolButton and a widget and creates an expandable widget. - **/ -class ExpanderWidget -{ -public: - /** empty expander, use set() - **/ - ExpanderWidget(); - - /** see set() - **/ - ExpanderWidget(QToolButton* button, QWidget* content); - - /** @brief sets the button and content widgets to use - * the button will be given an arrow icon, clicking it will toggle the - * visibility of the given widget - * @param button the button that toggles the content - * @param content the widget that will be shown or hidden - * @param opened initial state, defaults to closed - **/ - void set(QToolButton* button, QWidget* content, bool opened=false); - - /** either opens or closes the expander depending on the current state - **/ - void toggle(); - - /** sets the current state of the expander - **/ - void toggle(bool b); - - /** returns whether the expander is currently opened - **/ - bool opened() const; - -private: - QToolButton* m_button; - QWidget* m_content; - bool opened_; -}; - - /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system -- cgit v1.3.1 From 6e2ae2444ca0bdea964cf0551edd338e6dafa2bc Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:00:00 -0400 Subject: split text file tab into its own class --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 100 +++++++++++++++++------------------------ src/modinfodialog.h | 29 +++++++++--- src/modinfodialogtextfiles.cpp | 91 +++++++++++++++++++++++++++++++++++++ src/modinfodialogtextfiles.h | 21 +++++++++ 5 files changed, 180 insertions(+), 64 deletions(-) create mode 100644 src/modinfodialogtextfiles.cpp create mode 100644 src/modinfodialogtextfiles.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5aa8247..7e62f3e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp modinfoforeign.cpp @@ -154,6 +155,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogtextfiles.h modinfo.h modinfobackup.h modinfoforeign.h @@ -352,6 +354,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogtextfiles modinfoforeign modinfooverwrite modinforegular diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 065058fc..7e72653f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -38,6 +38,8 @@ along with Mod Organizer. If not, see . #include "previewdialog.h" #include "texteditor.h" +#include "modinfodialogtextfiles.h" + #include #include #include @@ -59,6 +61,18 @@ using namespace MOBase; using namespace MOShared; +std::vector> ModInfoDialogTab::createTabs( + Ui::ModInfoDialog* ui) +{ + std::vector> v; + + v.push_back(std::make_unique(ui)); + + return v; +} + + + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); public: @@ -254,6 +268,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); + + m_tabs = ModInfoDialogTab::createTabs(ui); + this->setWindowTitle(modInfo->name()); this->setWindowModality(Qt::WindowModal); @@ -305,12 +322,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - ui->textFileView->setupToolbar(); - - ui->tabTextSplitter->setSizes({200, 1}); - ui->tabTextSplitter->setStretchFactor(0, 0); - ui->tabTextSplitter->setStretchFactor(1, 1); - // refresh everything but the conflict lists, which are done in exec() because // they depend on restoring the state to some widgets; this refresh has to be // done here because some of the checks below depend on the ui to decide which @@ -333,15 +344,17 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } else if (unmanaged) { + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); + initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); @@ -838,7 +851,10 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( void ModInfoDialog::refreshFiles() { // clearing - ui->textFileList->clear(); + for (auto& tab : m_tabs) { + tab->clear(); + } + ui->iniTweaksList->clear(); ui->iniFileList->clear(); ui->inactiveESPList->clear(); @@ -857,9 +873,13 @@ void ModInfoDialog::refreshFiles() while (dirIterator.hasNext()) { QString fileName = dirIterator.next(); - if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); - } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && + for (auto& tab : m_tabs) { + if (tab->feedFile(m_RootPath, fileName)) { + break; + } + } + + if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && !fileName.endsWith("meta.ini")) { QString namePart = fileName.mid(m_RootPath.length() + 1); if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { @@ -903,7 +923,6 @@ void ModInfoDialog::refreshFiles() } } - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } @@ -943,9 +962,17 @@ void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) void ModInfoDialog::on_closeButton_clicked() { - if (allowNavigateFromTXT() && allowNavigateFromINI()) { - this->close(); + for (auto& tab : m_tabs) { + if (!tab->canClose()) { + return; + } + } + + if (!allowNavigateFromINI()) { + return; } + + close(); } @@ -981,23 +1008,6 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } -bool ModInfoDialog::allowNavigateFromTXT() -{ - if (ui->textFileView->dirty()) { - const int res = QMessageBox::question( - this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentTextFile(); - } - } - return true; -} - bool ModInfoDialog::allowNavigateFromINI() { if (ui->saveButton->isEnabled()) { @@ -1012,29 +1022,6 @@ bool ModInfoDialog::allowNavigateFromINI() return true; } -void ModInfoDialog::on_textFileList_currentItemChanged( - QListWidgetItem *current, QListWidgetItem *previous) -{ - const QString fullPath = m_RootPath + "/" + current->text(); - - if (fullPath == ui->textFileView->filename()) { - // the new file is the same as the currently displayed file. May be the - // result of a cancellation - return; - } - - if (allowNavigateFromTXT()) { - openTextFile(fullPath); - } else { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - -void ModInfoDialog::openTextFile(const QString &fileName) -{ - ui->textFileView->load(fileName); -} - void ModInfoDialog::openIniFile(const QString &fileName) { QFile iniFile(fileName); @@ -1109,11 +1096,6 @@ void ModInfoDialog::on_saveButton_clicked() saveCurrentIniFile(); } -void ModInfoDialog::saveCurrentTextFile() -{ - ui->textFileView->save(); -} - void ModInfoDialog::saveCurrentIniFile() { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index df450ac0..97ab9a38 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -53,9 +53,30 @@ class QTreeView; class CategoryFactory; class TextEditor; +class TextFilesTab; + + +class ModInfoDialogTab +{ +public: + static std::vector> createTabs( + Ui::ModInfoDialog* ui); + + ModInfoDialogTab() = default; + ModInfoDialogTab(const ModInfoDialogTab&) = delete; + ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; + ModInfoDialogTab(ModInfoDialogTab&&) = default; + ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; + virtual ~ModInfoDialogTab() = default; + + virtual void clear() = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool canClose() = 0; +}; + /** - * this is a larger dialog used to visualise information abount the mod. + * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system **/ class ModInfoDialog : public MOBase::TutorableDialog @@ -147,11 +168,8 @@ private: void deleteFile(const QModelIndex &index); void saveIniTweaks(); void saveCategories(QTreeWidgetItem *currentNode); - void saveCurrentTextFile(); void saveCurrentIniFile(); - void openTextFile(const QString &fileName); void openIniFile(const QString &fileName); - bool allowNavigateFromTXT(); bool allowNavigateFromINI(); FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); @@ -189,7 +207,6 @@ private slots: void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); @@ -234,6 +251,8 @@ private: ModInfo::Ptr m_ModInfo; + std::vector> m_tabs; + QSignalMapper m_ThumbnailMapper; QString m_RootPath; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp new file mode 100644 index 00000000..98da8c4a --- /dev/null +++ b/src/modinfodialogtextfiles.cpp @@ -0,0 +1,91 @@ +#include "modinfodialogtextfiles.h" +#include "ui_modinfodialog.h" +#include + +class TextFileItem : public QListWidgetItem +{ +public: + TextFileItem(const QString& rootPath, QString fullPath) + : m_fullPath(std::move(fullPath)) + { + setText(m_fullPath.mid(rootPath.length() + 1)); + } + + const QString& fullPath() const + { + return m_fullPath; + } + +private: + QString m_fullPath; +}; + + +TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ + ui->textFileView->setupToolbar(); + + ui->tabTextSplitter->setSizes({200, 1}); + ui->tabTextSplitter->setStretchFactor(0, 0); + ui->tabTextSplitter->setStretchFactor(1, 1); + + QObject::connect( + ui->textFileList, &QListWidget::currentItemChanged, + [&](auto* current, auto* previous){ onSelection(current, previous); }); +} + +void TextFilesTab::clear() +{ + ui->textFileList->clear(); +} + +bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + if (fullPath.endsWith(".txt", Qt::CaseInsensitive)) { + ui->textFileList->addItem(new TextFileItem(rootPath, fullPath)); + return true; + } + + return false; +} + +bool TextFilesTab::canClose() +{ + if (!ui->textFileView->dirty()) { + return true; + } + + const int res = QMessageBox::question( + ui->tabText, + QObject::tr("Save changes?"), + QObject::tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + + if (res == QMessageBox::Cancel) { + return false; + } + + if (res == QMessageBox::Yes) { + ui->textFileView->save(); + } + + return true; +} + +void TextFilesTab::onSelection( + QListWidgetItem* current, QListWidgetItem* previous) +{ + auto* item = dynamic_cast(current); + if (!item) { + qCritical("TextFilesTab: item is not a TextFileItem"); + return; + } + + if (!canClose()) { + ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + return; + } + + ui->textFileView->load(item->fullPath()); +} diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h new file mode 100644 index 00000000..ab9e972c --- /dev/null +++ b/src/modinfodialogtextfiles.h @@ -0,0 +1,21 @@ +#ifndef MODINFODIALOGTEXTFILES_H +#define MODINFODIALOGTEXTFILES_H + +#include "modinfodialog.h" + +class TextFilesTab : public ModInfoDialogTab +{ +public: + TextFilesTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + bool canClose() override; + +private: + Ui::ModInfoDialog* ui; + + void onSelection(QListWidgetItem* current, QListWidgetItem* previous); +}; + +#endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From 10f9fd041cefb14659f07c2163ef27796fae7e3e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:04:03 -0400 Subject: disable editor without selection --- src/modinfodialogtextfiles.cpp | 13 ++++++++++++- src/modinfodialogtextfiles.h | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 98da8c4a..50174ec5 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -38,6 +38,7 @@ TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) void TextFilesTab::clear() { ui->textFileList->clear(); + select(nullptr); } bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -87,5 +88,15 @@ void TextFilesTab::onSelection( return; } - ui->textFileView->load(item->fullPath()); + select(item); +} + +void TextFilesTab::select(TextFileItem* item) +{ + if (item) { + ui->textFileView->setEnabled(true); + ui->textFileView->load(item->fullPath()); + } else { + ui->textFileView->setEnabled(false); + } } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index ab9e972c..80eb9dc0 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -3,6 +3,8 @@ #include "modinfodialog.h" +class TextFileItem; + class TextFilesTab : public ModInfoDialogTab { public: @@ -16,6 +18,7 @@ private: Ui::ModInfoDialog* ui; void onSelection(QListWidgetItem* current, QListWidgetItem* previous); + void select(TextFileItem* item); }; #endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From b96f154625a0a139e722731b055f022bc90c2058 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:29:15 -0400 Subject: removed unused ini tweaks widgets splitter in ini tab --- src/modinfodialog.cpp | 90 +------------------ src/modinfodialog.h | 6 -- src/modinfodialog.ui | 233 ++++++++++++++++++++++++++------------------------ 3 files changed, 122 insertions(+), 207 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 7e72653f..23b8f4d8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -359,7 +359,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); } - initINITweaks(); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); @@ -421,7 +420,6 @@ ModInfoDialog::~ModInfoDialog() else m_ModInfo->setNotes(ui->notesEdit->toHtml()); saveCategories(ui->categoriesTree->invisibleRootItem()); - saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo delete ui->descriptionView->page(); delete ui->descriptionView; delete ui; @@ -436,19 +434,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -void ModInfoDialog::initINITweaks() -{ - int numTweaks = m_Settings->beginReadArray("INI Tweaks"); - for (int i = 0; i < numTweaks; ++i) { - m_Settings->setArrayIndex(i); - QList items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); - if (items.size() != 0) { - items.at(0)->setCheckState(Qt::Checked); - } - } - m_Settings->endArray(); -} - void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) { ui->fileTree = findChild("fileTree"); @@ -855,7 +840,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->iniTweaksList->clear(); ui->iniFileList->clear(); ui->inactiveESPList->clear(); ui->activeESPList->clear(); @@ -882,15 +866,7 @@ void ModInfoDialog::refreshFiles() if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && !fileName.endsWith("meta.ini")) { QString namePart = fileName.mid(m_RootPath.length() + 1); - if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { - QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); - newItem->setData(Qt::UserRole, namePart); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newItem); - } else { - ui->iniFileList->addItem(namePart); - } + ui->iniFileList->addItem(namePart); } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || fileName.endsWith(".esm", Qt::CaseInsensitive) || fileName.endsWith(".esl", Qt::CaseInsensitive)) { @@ -1038,23 +1014,6 @@ void ModInfoDialog::openIniFile(const QString &fileName) ui->saveButton->setEnabled(false); } - -void ModInfoDialog::saveIniTweaks() -{ - m_Settings->remove("INI Tweaks"); - m_Settings->beginWriteArray("INI Tweaks"); - - int countEnabled = 0; - for (int i = 0; i < ui->iniTweaksList->count(); ++i) { - if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { - m_Settings->setArrayIndex(countEnabled++); - m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); - } - } - m_Settings->endArray(); -} - - void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) { QString fullPath = m_RootPath + "/" + current->text(); @@ -1072,25 +1031,6 @@ void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, } } - -void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } - -} - void ModInfoDialog::on_saveButton_clicked() { saveCurrentIniFile(); @@ -2129,31 +2069,3 @@ void ModInfoDialog::on_prevButton_clicked() emit modOpenPrev(tab); this->accept(); } - - -void ModInfoDialog::createTweak() -{ - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); - if (name.isNull()) { - return; - } else if (!fixDirectoryName(name)) { - QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); - return; - } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { - QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); - return; - } - - QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); - newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); - newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); - newTweak->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newTweak); -} - -void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); - menu.exec(ui->iniTweaksList->mapToGlobal(pos)); -} diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 97ab9a38..0d54177a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -153,7 +153,6 @@ public slots: private: void initFiletree(ModInfo::Ptr modInfo); - void initINITweaks(); void refreshLists(); @@ -166,7 +165,6 @@ private: QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); - void saveIniTweaks(); void saveCategories(QTreeWidgetItem *currentNode); void saveCurrentIniFile(); void openIniFile(const QString &fileName); @@ -208,7 +206,6 @@ private slots: void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); @@ -225,9 +222,6 @@ private slots: void on_prevButton_clicked(); - void on_iniTweaksList_customContextMenuRequested(const QPoint &pos); - - void createTweak(); private: using FileEntry = MOShared::FileEntry; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 785fdeb4..10ebbb33 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -38,15 +38,58 @@ false - - - A list of text-files in the mod directory. - - - A list of text-files in the mod directory like readmes. - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Text Files + + + + + + + A list of text-files in the mod directory. + + + A list of text-files in the mod directory like readmes. + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + - @@ -55,113 +98,79 @@ INI-Files - + - - - 6 - - - QLayout::SetMinimumSize + + + Qt::Horizontal - - - - Ini Files - - - - - - - - 228 - 16777215 - - - - This is a list of .ini files in the mod. - - - This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. - - - - - - - false - - - Ini Tweaks *This feature is non-functional* - - - - - - - false - - - - 228 - 16777215 - - - - Qt::CustomContextMenu - - - This is a list of ini tweaks (ini modifications that can be toggled). - - - This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod whether the tweaks are really optional. - - - - - - - - - - - - - - - - - true - - - false - - - false - - - false - - - - - - - false - - - Save changes to the file. - - - Save changes to the file. This overwrites the original. There is no automatic backup! - - - Save + + + + 6 - - - + + + + Ini Files + + + + + + + This is a list of .ini files in the mod. + + + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. + + + + + + + + + + + + + + + + + true + + + false + + + false + + + false + + + + + + + false + + + Save changes to the file. + + + Save changes to the file. This overwrites the original. There is no automatic backup! + + + Save + + + + + + -- cgit v1.3.1 From 81815d202b4364847062ba248321474ef5a2d686 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 04:54:41 -0400 Subject: split ini tab stuff to IniFilesTab refactored TextFilesTab into GenericFiles so it can be used by IniFilesTab --- src/modinfodialog.cpp | 90 +------------------------------ src/modinfodialog.h | 8 --- src/modinfodialog.ui | 38 ++----------- src/modinfodialogtextfiles.cpp | 117 +++++++++++++++++++++++++++++------------ src/modinfodialogtextfiles.h | 41 ++++++++++++--- 5 files changed, 124 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 23b8f4d8..8f989d02 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -67,6 +67,7 @@ std::vector> ModInfoDialogTab::createTabs( std::vector> v; v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -840,7 +841,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->iniFileList->clear(); ui->inactiveESPList->clear(); ui->activeESPList->clear(); ui->imageLabel->setPixmap({}); @@ -863,11 +863,7 @@ void ModInfoDialog::refreshFiles() } } - if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && - !fileName.endsWith("meta.ini")) { - QString namePart = fileName.mid(m_RootPath.length() + 1); - ui->iniFileList->addItem(namePart); - } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || + if (fileName.endsWith(".esp", Qt::CaseInsensitive) || fileName.endsWith(".esm", Qt::CaseInsensitive) || fileName.endsWith(".esl", Qt::CaseInsensitive)) { QString relativePath = fileName.mid(m_RootPath.length() + 1); @@ -944,10 +940,6 @@ void ModInfoDialog::on_closeButton_clicked() } } - if (!allowNavigateFromINI()) { - return; - } - close(); } @@ -984,84 +976,6 @@ void ModInfoDialog::thumbnailClicked(const QString &fileName) ui->imageLabel->setPixmap(QPixmap::fromImage(image)); } -bool ModInfoDialog::allowNavigateFromINI() -{ - if (ui->saveButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentIniFile(); - } - } - return true; -} - -void ModInfoDialog::openIniFile(const QString &fileName) -{ - QFile iniFile(fileName); - iniFile.open(QIODevice::ReadOnly); - QByteArray buffer = iniFile.readAll(); - - QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); - QTextEdit *iniFileView = findChild("iniFileView"); - iniFileView->setText(codec->toUnicode(buffer)); - iniFileView->setProperty("currentFile", fileName); - iniFileView->setProperty("encoding", codec->name()); - iniFile.close(); - - ui->saveButton->setEnabled(false); -} - -void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->text(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - -void ModInfoDialog::on_saveButton_clicked() -{ - saveCurrentIniFile(); -} - - -void ModInfoDialog::saveCurrentIniFile() -{ - QVariant fileNameVar = ui->iniFileView->property("currentFile"); - QVariant encodingVar = ui->iniFileView->property("encoding"); - if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { - QString fileName = fileNameVar.toString(); - QDir().mkpath(QFileInfo(fileName).absolutePath()); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveButton->setEnabled(false); -} - -void ModInfoDialog::on_iniFileView_textChanged() -{ - QPushButton* saveButton = findChild("saveButton"); - saveButton->setEnabled(true); -} - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 0d54177a..70fd157c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -53,8 +53,6 @@ class QTreeView; class CategoryFactory; class TextEditor; -class TextFilesTab; - class ModInfoDialogTab { @@ -166,9 +164,6 @@ private: bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); void saveCategories(QTreeWidgetItem *currentNode); - void saveCurrentIniFile(); - void openIniFile(const QString &fileName); - bool allowNavigateFromINI(); FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); @@ -193,7 +188,6 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_saveButton_clicked(); void on_activateESP_clicked(); void on_deactivateESP_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); @@ -201,11 +195,9 @@ private slots: void on_sourceGameEdit_currentIndexChanged(int); void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); - void on_iniFileView_textChanged(); void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 10ebbb33..c6ab111f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -86,7 +86,7 @@ 0 - + @@ -96,11 +96,11 @@ - INI-Files + INI Files - + Qt::Horizontal @@ -129,43 +129,15 @@ - + - + - - true - - - false - - - false - - - false - - - - - - - false - - - Save changes to the file. - - - Save changes to the file. This overwrites the original. There is no automatic backup! - - - Save - diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 50174ec5..cf800aa6 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -2,10 +2,10 @@ #include "ui_modinfodialog.h" #include -class TextFileItem : public QListWidgetItem +class FileListItem : public QListWidgetItem { public: - TextFileItem(const QString& rootPath, QString fullPath) + FileListItem(const QString& rootPath, QString fullPath) : m_fullPath(std::move(fullPath)) { setText(m_fullPath.mid(rootPath.length() + 1)); @@ -21,46 +21,36 @@ private: }; -TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) - : ui(ui) +GenericFilesTab::GenericFilesTab(QListWidget* list, QSplitter* sp, TextEditor* e) + : m_list(list), m_editor(e) { - ui->textFileView->setupToolbar(); + m_editor->setupToolbar(); - ui->tabTextSplitter->setSizes({200, 1}); - ui->tabTextSplitter->setStretchFactor(0, 0); - ui->tabTextSplitter->setStretchFactor(1, 1); + sp->setSizes({200, 1}); + sp->setStretchFactor(0, 0); + sp->setStretchFactor(1, 1); QObject::connect( - ui->textFileList, &QListWidget::currentItemChanged, + m_list, &QListWidget::currentItemChanged, [&](auto* current, auto* previous){ onSelection(current, previous); }); } -void TextFilesTab::clear() +void GenericFilesTab::clear() { - ui->textFileList->clear(); + m_list->clear(); select(nullptr); } -bool TextFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +bool GenericFilesTab::canClose() { - if (fullPath.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(new TextFileItem(rootPath, fullPath)); - return true; - } - - return false; -} - -bool TextFilesTab::canClose() -{ - if (!ui->textFileView->dirty()) { + if (!m_editor->dirty()) { return true; } const int res = QMessageBox::question( - ui->tabText, + m_list, QObject::tr("Save changes?"), - QObject::tr("Save changes to \"%1\"?").arg(ui->textFileView->filename()), + QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (res == QMessageBox::Cancel) { @@ -68,35 +58,94 @@ bool TextFilesTab::canClose() } if (res == QMessageBox::Yes) { - ui->textFileView->save(); + m_editor->save(); } return true; } -void TextFilesTab::onSelection( +bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".txt" + }; + + for (const auto* e : extensions) { + if (wantsFile(rootPath, fullPath)) { + m_list->addItem(new FileListItem(rootPath, fullPath)); + return true; + } + } + + return false; +} + +void GenericFilesTab::onSelection( QListWidgetItem* current, QListWidgetItem* previous) { - auto* item = dynamic_cast(current); + auto* item = dynamic_cast(current); if (!item) { - qCritical("TextFilesTab: item is not a TextFileItem"); + qCritical("TextFilesTab: item is not a FileListItem"); return; } if (!canClose()) { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + m_list->setCurrentItem(previous, QItemSelectionModel::Current); return; } select(item); } -void TextFilesTab::select(TextFileItem* item) +void GenericFilesTab::select(FileListItem* item) { if (item) { - ui->textFileView->setEnabled(true); - ui->textFileView->load(item->fullPath()); + m_editor->setEnabled(true); + m_editor->load(item->fullPath()); } else { - ui->textFileView->setEnabled(false); + m_editor->setEnabled(false); + } +} + + +TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) + : GenericFilesTab(ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +{ +} + +bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const +{ + static constexpr const char* extensions[] = { + ".txt" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + return true; + } } + + return false; +} + +IniFilesTab::IniFilesTab(Ui::ModInfoDialog* ui) + : GenericFilesTab(ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +{ +} + +bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const +{ + static constexpr const char* extensions[] = { + ".ini", ".cfg" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + if (!fullPath.endsWith("meta.ini")) { + return true; + } + } + } + + return false; } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 80eb9dc0..8725ea09 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -2,23 +2,50 @@ #define MODINFODIALOGTEXTFILES_H #include "modinfodialog.h" +#include +#include -class TextFileItem; +class FileListItem; +class TextEditor; -class TextFilesTab : public ModInfoDialogTab +class GenericFilesTab : public ModInfoDialogTab { public: - TextFilesTab(Ui::ModInfoDialog* ui); + GenericFilesTab(QListWidget* list, QSplitter* splitter, TextEditor* editor); void clear() override; - bool feedFile(const QString& rootPath, const QString& fullPath) override; bool canClose() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; -private: - Ui::ModInfoDialog* ui; +protected: + QListWidget* m_list; + TextEditor* m_editor; + + virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; +private: void onSelection(QListWidgetItem* current, QListWidgetItem* previous); - void select(TextFileItem* item); + void select(FileListItem* item); +}; + + +class TextFilesTab : public GenericFilesTab +{ +public: + TextFilesTab(Ui::ModInfoDialog* ui); + +protected: + bool wantsFile(const QString& rootPath, const QString& fullPath) const override; +}; + + +class IniFilesTab : public GenericFilesTab +{ +public: + IniFilesTab(Ui::ModInfoDialog* ui); + +protected: + bool wantsFile(const QString& rootPath, const QString& fullPath) const override; }; #endif // MODINFODIALOGTEXTFILES_H -- cgit v1.3.1 From 9f1520c69e88113d9a1a06f91bbabe9cf5ddef9f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 05:50:45 -0400 Subject: split images tab stuff in ImagesTab text editor: fixed modified flag not being set to false when loading an empty file --- src/CMakeLists.txt | 3 ++ src/modinfodialog.cpp | 51 ++++++------------------- src/modinfodialog.h | 5 +-- src/modinfodialog.ui | 8 ++-- src/modinfodialogimages.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++ src/modinfodialogimages.h | 37 ++++++++++++++++++ src/texteditor.cpp | 12 +++++- 7 files changed, 158 insertions(+), 49 deletions(-) create mode 100644 src/modinfodialogimages.cpp create mode 100644 src/modinfodialogimages.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e62f3e9..c5018baf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogimages.cpp modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp @@ -155,6 +156,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogimages.h modinfodialogtextfiles.h modinfo.h modinfobackup.h @@ -354,6 +356,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogimages modinfodialogtextfiles modinfoforeign modinfooverwrite diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 8f989d02..cc3140b6 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -39,6 +39,7 @@ along with Mod Organizer. If not, see . #include "texteditor.h" #include "modinfodialogtextfiles.h" +#include "modinfodialogimages.h" #include #include @@ -68,10 +69,16 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } +bool ModInfoDialogTab::canClose() +{ + return true; +} + class ModFileListWidget : public QListWidgetItem { @@ -262,7 +269,7 @@ public: 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), + m_RequestStarted(false), m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), @@ -307,8 +314,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->descriptionView->setPage(new DescriptionPage()); - connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); - connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); //TODO: No easy way to delegate links @@ -347,14 +352,16 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); + ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); + ui->tabWidget->setTabEnabled(TAB_INIFILES, true); + ui->tabWidget->setTabEnabled(TAB_IMAGES, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); @@ -843,13 +850,6 @@ void ModInfoDialog::refreshFiles() ui->inactiveESPList->clear(); ui->activeESPList->clear(); - ui->imageLabel->setPixmap({}); - - while (ui->thumbnailArea->count() > 0) { - auto* item = ui->thumbnailArea->takeAt(0); - delete item->widget(); - delete item; - } if (m_RootPath.length() > 0) { @@ -875,27 +875,10 @@ void ModInfoDialog::refreshFiles() } else { ui->activeESPList->addItem(relativePath); } - } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || - (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { - QImage image = QImage(fileName); - if (!image.isNull()) { - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(128); - } else { - image = image.scaledToHeight(96); - } - - QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); - thumbnailButton->setIconSize(QSize(image.width(), image.height())); - connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); - m_ThumbnailMapper.setMapping(thumbnailButton, fileName); - ui->thumbnailArea->addWidget(thumbnailButton); - } } } } - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } @@ -964,18 +947,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::thumbnailClicked(const QString &fileName) -{ - ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - QImage image(fileName); - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(ui->imageLabel->geometry().width()); - } else { - image = image.scaledToHeight(ui->imageLabel->geometry().height()); - } - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); -} - void ModInfoDialog::on_activateESP_clicked() { QListWidget *activeESPList = findChild("activeESPList"); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 70fd157c..f3ef6e76 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -69,7 +69,7 @@ public: virtual void clear() = 0; virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; - virtual bool canClose() = 0; + virtual bool canClose(); }; @@ -135,7 +135,6 @@ public: signals: - void thumbnailClickedSignal(const QString &filename); void linkActivated(const QString &link); void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); @@ -172,7 +171,6 @@ private: int tabIndex(const QString &tabId); private slots: - void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -239,7 +237,6 @@ private: std::vector> m_tabs; - QSignalMapper m_ThumbnailMapper; QString m_RootPath; OrganizerCore *m_OrganizerCore; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c6ab111f..ba670ef3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -154,7 +154,7 @@ - + 0 0 @@ -183,7 +183,7 @@ - + 16777215 @@ -193,7 +193,7 @@ true - + 0 @@ -225,7 +225,7 @@ 0 - + 0 diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp new file mode 100644 index 00000000..4f2ee78c --- /dev/null +++ b/src/modinfodialogimages.cpp @@ -0,0 +1,91 @@ +#include "modinfodialogimages.h" +#include "ui_modinfodialog.h" + +ThumbnailButton::ThumbnailButton(const QString& fullPath, QImage original) + : m_original(std::move(original)) +{ + const auto ratio = static_cast(m_original.width()) / m_original.height(); + + QImage thumbnail; + if (ratio > 1.34) { + thumbnail = m_original.scaledToWidth(128); + } else { + thumbnail = m_original.scaledToHeight(96); + } + + setIcon(QPixmap::fromImage(thumbnail)); + setIconSize(QSize(thumbnail.width(), thumbnail.height())); + + connect(this, &QPushButton::clicked, [&]{ emit open(m_original); }); +} + +const QImage& ThumbnailButton::image() const +{ + return m_original; +} + + +ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ +} + +void ImagesTab::clear() +{ + ui->imageLabel->setPixmap({}); + + while (ui->imageThumbnails->count() > 0) { + auto* item = ui->imageThumbnails->takeAt(0); + delete item->widget(); + delete item; + } +} + +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".png", ".jpg" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + add(fullPath); + return true; + } + } + + return false; +} + +void ImagesTab::add(const QString& fullPath) +{ + QImage image = QImage(fullPath); + + if (image.isNull()) { + qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; + return; + } + + auto* button = new ThumbnailButton(fullPath, std::move(image)); + + QObject::connect( + button, &ThumbnailButton::open, + [&](const QImage& image){ onOpen(image); }); + + ui->imageThumbnails->addWidget(button); +} + +void ImagesTab::onOpen(const QImage& original) +{ + QImage image; + + const auto ratio = static_cast(original.width()) / original.height(); + + if (ratio > 1.34) { + image = original.scaledToWidth(ui->imageLabel->geometry().width()); + } else { + image = original.scaledToHeight(ui->imageLabel->geometry().height()); + } + + ui->imageLabel->setPixmap(QPixmap::fromImage(image)); +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h new file mode 100644 index 00000000..3c492e77 --- /dev/null +++ b/src/modinfodialogimages.h @@ -0,0 +1,37 @@ +#ifndef MODINFODIALOGIMAGES_H +#define MODINFODIALOGIMAGES_H + +#include "modinfodialog.h" + +class ThumbnailButton : public QPushButton +{ + Q_OBJECT; + +public: + ThumbnailButton(const QString& fullPath, QImage image); + const QImage& image() const; + +signals: + void open(const QImage& image); + +private: + const QImage m_original; +}; + + +class ImagesTab : public ModInfoDialogTab +{ +public: + ImagesTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + +private: + Ui::ModInfoDialog* ui; + + void add(const QString& fullPath); + void onOpen(const QImage& image); +}; + +#endif // MODINFODIALOGIMAGES_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 78ce1610..99490b22 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -60,9 +60,19 @@ void TextEditor::setDefaultStyle() bool TextEditor::load(const QString& filename) { m_filename = filename; - setPlainText(MOBase::readFileText(filename, &m_encoding)); + clear(); + + const QString s = MOBase::readFileText(filename, &m_encoding); + + setPlainText(s); document()->setModified(false); + if (s.isEmpty()) { + // the modificationChanged even is not fired by the setModified() call + // above when the text being set is empty + onModified(false); + } + return true; } -- cgit v1.3.1 From a13067602590f16484561b9ca23f15aa45089db8 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 17 Jun 2019 08:56:43 -0400 Subject: images list now vertical on the left reworked scaling to respect aspect ratio and not exceed original size --- src/modinfodialog.ui | 100 +++++++++++++----------------------- src/modinfodialogimages.cpp | 122 ++++++++++++++++++++++++++++++++------------ src/modinfodialogimages.h | 21 ++++++-- 3 files changed, 141 insertions(+), 102 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index ba670ef3..c1dba1c7 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -150,68 +150,14 @@ Images - + - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Qt::LeftToRight - - - - - - Qt::AlignCenter - - - - - - - - 16777215 - 128 - - - - true + + + Qt::Horizontal - - - - 0 - 0 - 746 - 126 - - - - Images located in the mod. - - - This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - - - - 0 - + + 0 @@ -225,14 +171,40 @@ 0 - - - 0 + + + true - + + + + 0 + 0 + 604 + 436 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4f2ee78c..fb2ad1fe 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,44 +1,109 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" -ThumbnailButton::ThumbnailButton(const QString& fullPath, QImage original) - : m_original(std::move(original)) +ScalableImage::ScalableImage(QImage original) + : m_original(std::move(original)), m_border(1) { - const auto ratio = static_cast(m_original.width()) / m_original.height(); + auto sp = sizePolicy(); + sp.setHeightForWidth(true); + setSizePolicy(sp); +} + +void ScalableImage::setImage(QImage image) +{ + m_original = std::move(image); + m_scaled = {}; + + update(); +} + +const QImage& ScalableImage::image() const +{ + return m_original; +} + +bool ScalableImage::hasHeightForWidth() const +{ + return true; +} + +int ScalableImage::heightForWidth(int w) const +{ + return w; +} + +void ScalableImage::paintEvent(QPaintEvent* e) +{ + if (m_original.isNull()) { + return; + } + + const QRect widgetRect = rect(); + const QRect imageRect = widgetRect.adjusted( + m_border, m_border, -m_border, -m_border); + + const auto ratio = std::min({ + 1.0, + static_cast(imageRect.width()) / m_original.width(), + static_cast(imageRect.height()) / m_original.height()}); + + const QSize scaledSize( + static_cast(std::round(m_original.width() * ratio)), + static_cast(std::round(m_original.height() * ratio))); - QImage thumbnail; - if (ratio > 1.34) { - thumbnail = m_original.scaledToWidth(128); - } else { - thumbnail = m_original.scaledToHeight(96); + if (m_scaled.isNull() || m_scaled.size() != scaledSize) { + qDebug() << "scaled to " << scaledSize; + + m_scaled = m_original.scaled( + scaledSize.width(), scaledSize.height(), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - setIcon(QPixmap::fromImage(thumbnail)); - setIconSize(QSize(thumbnail.width(), thumbnail.height())); + const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); + + const QRect drawImageRect( + (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2, + (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2, + m_scaled.width(), m_scaled.height()); + + + QPainter painter(this); - connect(this, &QPushButton::clicked, [&]{ emit open(m_original); }); + painter.setPen(QColor(Qt::black)); + painter.drawRect(drawBorderRect); + painter.drawImage(drawImageRect, m_scaled); } -const QImage& ThumbnailButton::image() const +void ScalableImage::mousePressEvent(QMouseEvent* e) { - return m_original; + if (e->button() == Qt::LeftButton) { + emit clicked(m_original); + } } ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) - : ui(ui) + : ui(ui), m_image(new ScalableImage) { + ui->imagesImage->layout()->addWidget(m_image); + ui->imagesThumbnails->setLayout(new QVBoxLayout); + + ui->tabImagesSplitter->setSizes({128, 1}); + ui->tabImagesSplitter->setStretchFactor(0, 0); + ui->tabImagesSplitter->setStretchFactor(1, 1); } void ImagesTab::clear() { - ui->imageLabel->setPixmap({}); + m_image->setImage({}); - while (ui->imageThumbnails->count() > 0) { - auto* item = ui->imageThumbnails->takeAt(0); + while (ui->imagesThumbnails->layout()->count() > 0) { + auto* item = ui->imagesThumbnails->layout()->takeAt(0); delete item->widget(); delete item; } + + static_cast(ui->imagesThumbnails->layout())->addStretch(1); } bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -66,26 +131,17 @@ void ImagesTab::add(const QString& fullPath) return; } - auto* button = new ThumbnailButton(fullPath, std::move(image)); + auto* thumbnail = new ScalableImage(std::move(image)); QObject::connect( - button, &ThumbnailButton::open, - [&](const QImage& image){ onOpen(image); }); + thumbnail, &ScalableImage::clicked, + [&](const QImage& image){ onClicked(image); }); - ui->imageThumbnails->addWidget(button); + static_cast(ui->imagesThumbnails->layout())->insertWidget( + ui->imagesThumbnails->layout()->count() - 1, thumbnail); } -void ImagesTab::onOpen(const QImage& original) +void ImagesTab::onClicked(const QImage& original) { - QImage image; - - const auto ratio = static_cast(original.width()) / original.height(); - - if (ratio > 1.34) { - image = original.scaledToWidth(ui->imageLabel->geometry().width()); - } else { - image = original.scaledToHeight(ui->imageLabel->geometry().height()); - } - - ui->imageLabel->setPixmap(QPixmap::fromImage(image)); + m_image->setImage(original); } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 3c492e77..73fb5936 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -3,19 +3,29 @@ #include "modinfodialog.h" -class ThumbnailButton : public QPushButton +class ScalableImage : public QWidget { Q_OBJECT; public: - ThumbnailButton(const QString& fullPath, QImage image); + ScalableImage(QImage image={}); + + void setImage(QImage image); const QImage& image() const; + bool hasHeightForWidth() const; + int heightForWidth(int w) const; + signals: - void open(const QImage& image); + void clicked(const QImage& image); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; private: - const QImage m_original; + QImage m_original, m_scaled; + int m_border; }; @@ -29,9 +39,10 @@ public: private: Ui::ModInfoDialog* ui; + ScalableImage* m_image; void add(const QString& fullPath); - void onOpen(const QImage& image); + void onClicked(const QImage& image); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From f8a037a409d1b6bbb2f34b237b8b66d454179f56 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 18 Jun 2019 09:29:06 -0400 Subject: changed layout of esps tab, moved to modinfodialogesps.cpp/h slightly offset next/previous images so they look better when shown next to each other vertically --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 98 +------------ src/modinfodialog.h | 2 - src/modinfodialog.ui | 268 ++++++++++++++++++++++-------------- src/modinfodialogesps.cpp | 287 +++++++++++++++++++++++++++++++++++++++ src/modinfodialogesps.h | 26 ++++ src/modinfodialogimages.cpp | 2 - src/resources/go-next_16.png | Bin 676 -> 719 bytes src/resources/go-previous_16.png | Bin 655 -> 718 bytes 9 files changed, 483 insertions(+), 203 deletions(-) create mode 100644 src/modinfodialogesps.cpp create mode 100644 src/modinfodialogesps.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c5018baf..aba922f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogesps.cpp modinfodialogimages.cpp modinfodialogtextfiles.cpp modinfo.cpp @@ -156,6 +157,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogesps.h modinfodialogimages.h modinfodialogtextfiles.h modinfo.h @@ -356,6 +358,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogesps modinfodialogimages modinfodialogtextfiles modinfoforeign diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index cc3140b6..a52f2031 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -40,6 +40,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" +#include "modinfodialogesps.h" #include #include @@ -70,6 +71,7 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -353,15 +355,16 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); + ui->tabWidget->setTabEnabled(TAB_ESPS, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); ui->tabWidget->setTabEnabled(TAB_INIFILES, true); ui->tabWidget->setTabEnabled(TAB_IMAGES, true); + ui->tabWidget->setTabEnabled(TAB_ESPS, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); @@ -848,10 +851,6 @@ void ModInfoDialog::refreshFiles() tab->clear(); } - ui->inactiveESPList->clear(); - ui->activeESPList->clear(); - - if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -862,24 +861,8 @@ void ModInfoDialog::refreshFiles() break; } } - - if (fileName.endsWith(".esp", Qt::CaseInsensitive) || - fileName.endsWith(".esm", Qt::CaseInsensitive) || - fileName.endsWith(".esl", Qt::CaseInsensitive)) { - QString relativePath = fileName.mid(m_RootPath.length() + 1); - if (relativePath.contains('/')) { - QFileInfo fileInfo(fileName); - QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName()); - newItem->setData(Qt::UserRole, relativePath); - ui->inactiveESPList->addItem(newItem); - } else { - ui->activeESPList->addItem(relativePath); - } - } } } - - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) @@ -947,79 +930,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::on_activateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = inactiveESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QListWidgetItem *selectedItem = inactiveESPList->takeItem(selectedRow); - - QDir root(m_RootPath); - bool renamed = false; - - while (root.exists(selectedItem->text())) { - bool okClicked = false; - QString newName = QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, selectedItem->text(), &okClicked); - if (!okClicked) { - inactiveESPList->insertItem(selectedRow, selectedItem); - return; - } else if (newName.size() > 0) { - selectedItem->setText(newName); - renamed = true; - } - } - - if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { - activeESPList->addItem(selectedItem); - if (renamed) { - selectedItem->setData(Qt::UserRole, QVariant()); - } - } else { - inactiveESPList->insertItem(selectedRow, selectedItem); - reportError(tr("failed to move file")); - } -} - - -void ModInfoDialog::on_deactivateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = activeESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QDir root(m_RootPath); - - QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); - - // if we moved the file from optional to active in this session, we move the file back to - // where it came from. Otherwise, it is moved to the new folder "optional" - if (selectedItem->data(Qt::UserRole).isNull()) { - selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); - if (!root.exists("optional")) { - if (!root.mkdir("optional")) { - reportError(tr("failed to create directory \"optional\"")); - activeESPList->insertItem(selectedRow, selectedItem); - return; - } - } - } - - if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { - inactiveESPList->addItem(selectedItem); - } else { - activeESPList->insertItem(selectedRow, selectedItem); - } -} - void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) { emit linkActivated(link); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index f3ef6e76..6ecf16bf 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -186,8 +186,6 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_activateESP_clicked(); - void on_deactivateESP_clicked(); void on_visitNexusLabel_linkActivated(const QString &link); void on_modIDEdit_editingFinished(); void on_sourceGameEdit_currentIndexChanged(int); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c1dba1c7..f1212634 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -104,7 +104,7 @@ Qt::Horizontal - + 6 @@ -128,7 +128,7 @@ - + @@ -180,7 +180,7 @@ 0 0 - 604 + 741 436 @@ -213,117 +213,175 @@ Optional ESPs - - - - - List of esps, esms, and esls that can not be loaded by the game. - - - List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. -They usually contain optional functionality, see the readme. - -Most mods do not have optional esps, so chances are good you are looking at an empty list. - - - - - - - Optional ESPs + + + + + Qt::Horizontal - - - - - - - - - 96 - 0 - - - - Make the selected mod in the lower list unavailable. - - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - - - - - - - :/MO/gui/resources/go-up.png:/MO/gui/resources/go-up.png + + + + 0 - - - 22 - 22 - + + 0 - - - - - - - 96 - 0 - + + 0 - - Move a file to the data directory. + + 0 - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + + + Optional ESPs + + + + + + + List of esps, esms, and esls that can not be loaded by the game. + + + List of esps, esms, and esls contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. +They usually contain optional functionality, see the readme. + +Most mods do not have optional esps, so chances are good you are looking at an empty list. + + + + + + + + + 0 - - + + 0 - - - :/MO/gui/resources/go-down.png:/MO/gui/resources/go-down.png + + 0 - - - 22 - 22 - + + 0 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - ESPs in the data directory and thus visible to the game. - - - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. - - - - - - - Available ESPs - + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move a file to the data directory. + + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + + + + + + :/MO/gui/next:/MO/gui/next + + + + 22 + 22 + + + + + + + + Make the selected mod in the lower list unavailable. + + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + + + + + + :/MO/gui/previous:/MO/gui/previous + + + + 22 + 22 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available ESPs + + + + + + + ESPs in the data directory and thus visible to the game. + + + These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + + + + + + + + diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp new file mode 100644 index 00000000..32dcdb35 --- /dev/null +++ b/src/modinfodialogesps.cpp @@ -0,0 +1,287 @@ +#include "modinfodialogesps.h" +#include "ui_modinfodialog.h" +#include + +using MOBase::reportError; + + +class ESP +{ +public: + ESP(QString rootPath, QString relativePath) + : m_rootPath(std::move(rootPath)), m_active(false) + { + if (relativePath.contains('/')) { + m_inactivePath = relativePath; + } else { + m_activePath = relativePath; + m_active = true; + } + } + + const QString& rootPath() const + { + return m_rootPath; + } + + const QString& relativePath() const + { + if (m_active) { + return m_activePath; + } else { + return m_inactivePath; + } + } + + const QString& activePath() const + { + return m_activePath; + } + + const QString& inactivePath() const + { + return m_inactivePath; + } + + QFileInfo fileInfo() const + { + return m_rootPath + QDir::separator() + relativePath(); + } + + bool isActive() const + { + return m_active; + } + + bool activate(const QString& newName) + { + QDir root(m_rootPath); + + if (root.rename(m_inactivePath, newName)) { + m_active = true; + m_activePath = newName; + + if (QFileInfo(m_inactivePath).fileName() != newName) { + // file was renamed + m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; + } + + return true; + } + + return false; + } + + bool deactivate(const QString& newName) + { + QDir root(m_rootPath); + + if (root.rename(m_activePath, newName)) { + m_active = false; + m_inactivePath = newName; + return true; + } + + return false; + } + +private: + QString m_rootPath; + QString m_activePath; + QString m_inactivePath; + bool m_active; +}; + + +class ESPItem : public QListWidgetItem +{ +public: + ESPItem(ESP esp) + : m_esp(std::move(esp)) + { + updateText(); + } + + const ESP& esp() const + { + return m_esp; + } + + void setESP(ESP esp) + { + m_esp = esp; + updateText(); + } + +private: + ESP m_esp; + + void updateText() + { + setText(m_esp.fileInfo().fileName()); + } +}; + + +ESPsTab::ESPsTab(Ui::ModInfoDialog* ui) + : ui(ui) +{ + QObject::connect( + ui->activateESP1, &QToolButton::clicked, [&]{ onActivate(); }); + + QObject::connect( + ui->deactivateESP1, &QToolButton::clicked, [&]{ onDeactivate(); }); +} + +void ESPsTab::clear() +{ + ui->inactiveESPList1->clear(); + ui->activeESPList1->clear(); +} + +bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + static constexpr const char* extensions[] = { + ".esp", ".esm", ".esl" + }; + + for (const auto* e : extensions) { + if (fullPath.endsWith(e, Qt::CaseInsensitive)) { + QString relativePath = fullPath.mid(rootPath.length() + 1); + + auto* item = new ESPItem({rootPath, relativePath}); + + if (item->esp().isActive()) { + ui->activeESPList1->addItem(item); + } else { + ui->inactiveESPList1->addItem(item); + } + + return true; + } + } + + return false; +} + +void ESPsTab::onActivate() +{ + auto* item = selectedInactive(); + if (!item) { + qWarning("ESPsTab::onActivate(): no selection"); + return; + } + + ESP esp = item->esp(); + + if (esp.isActive()) { + qWarning("ESPsTab::onActive(): item is already active"); + return; + } + + QDir root(esp.rootPath()); + const QFileInfo file(esp.fileInfo()); + + QString newName = file.fileName(); + + while (root.exists(newName)) { + bool okClicked = false; + + newName = QInputDialog::getText( + ui->tabESPs, + QObject::tr("File Exists"), + QObject::tr("A file with that name exists, please enter a new one"), + QLineEdit::Normal, file.fileName(), &okClicked); + + if (!okClicked) { + return; + } + + if (newName.isEmpty()) { + newName = file.fileName(); + } + } + + if (esp.activate(newName)) { + ui->inactiveESPList1->takeItem(ui->inactiveESPList1->row(item)); + ui->activeESPList1->addItem(item); + item->setESP(esp); + } else { + reportError(QObject::tr("Failed to move file")); + } +} + +void ESPsTab::onDeactivate() +{ + auto* item = selectedActive(); + if (!item) { + qWarning("ESPsTab::onDeactivate(): no selection"); + return; + } + + ESP esp = item->esp(); + + if (!esp.isActive()) { + qWarning("ESPsTab::onDeactivate(): item is already inactive"); + return; + } + + QDir root(esp.rootPath()); + + // if we moved the file from optional to active in this session, we move the + // file back to where it came from. Otherwise, it is moved to the new folder + // "optional" + + QString newName = esp.inactivePath(); + + if (newName.isEmpty()) { + if (!root.exists("optional")) { + if (!root.mkdir("optional")) { + reportError(QObject::tr("Failed to create directory \"optional\"")); + return; + } + } + + newName = QString("optional") + QDir::separator() + esp.fileInfo().fileName(); + } + + if (esp.deactivate(newName)) { + ui->activeESPList1->takeItem(ui->activeESPList1->row(item)); + ui->inactiveESPList1->addItem(item); + item->setESP(esp); + } else { + reportError(QObject::tr("Failed to move file")); + } +} + +ESPItem* ESPsTab::selectedInactive() +{ + auto* item = ui->inactiveESPList1->currentItem(); + if (!item) { + return nullptr; + } + + auto* esp = dynamic_cast(item); + if (!esp) { + qCritical("ESPsTab: inactive item is not an ESPItem"); + return nullptr; + } + + return esp; +} + +ESPItem* ESPsTab::selectedActive() +{ + auto* item = ui->activeESPList1->currentItem(); + if (!item) { + return nullptr; + } + + auto* esp = dynamic_cast(item); + if (!esp) { + qCritical("ESPsTab: active item is not an ESPItem"); + return nullptr; + } + + return esp; +} diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h new file mode 100644 index 00000000..a46677cb --- /dev/null +++ b/src/modinfodialogesps.h @@ -0,0 +1,26 @@ +#ifndef MODINFODIALOGESPS_H +#define MODINFODIALOGESPS_H + +#include "modinfodialog.h" + +class ESPItem; + +class ESPsTab : public ModInfoDialogTab +{ +public: + ESPsTab(Ui::ModInfoDialog* ui); + + void clear() override; + bool feedFile(const QString& rootPath, const QString& fullPath) override; + +private: + Ui::ModInfoDialog* ui; + + void onActivate(); + void onDeactivate(); + + ESPItem* selectedInactive(); + ESPItem* selectedActive(); +}; + +#endif // MODINFODIALOGESPS_H diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index fb2ad1fe..1659bed3 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -52,8 +52,6 @@ void ScalableImage::paintEvent(QPaintEvent* e) static_cast(std::round(m_original.height() * ratio))); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { - qDebug() << "scaled to " << scaledSize; - m_scaled = m_original.scaled( scaledSize.width(), scaledSize.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); diff --git a/src/resources/go-next_16.png b/src/resources/go-next_16.png index 6ef8de76..58742d39 100644 Binary files a/src/resources/go-next_16.png and b/src/resources/go-next_16.png differ diff --git a/src/resources/go-previous_16.png b/src/resources/go-previous_16.png index 659cd90d..b4b22d04 100644 Binary files a/src/resources/go-previous_16.png and b/src/resources/go-previous_16.png differ -- cgit v1.3.1 From c98115e2f6f59b807382c41256d7f1a86009aa40 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:24:23 -0400 Subject: in the process of moving stuff to modinfodialogconflicts.h/cpp --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 480 ++----------------------------------- src/modinfodialog.h | 45 ++-- src/modinfodialog.ui | 58 ++--- src/modinfodialogconflicts.cpp | 530 +++++++++++++++++++++++++++++++++++++++++ src/modinfodialogconflicts.h | 86 +++++++ 6 files changed, 689 insertions(+), 513 deletions(-) create mode 100644 src/modinfodialogconflicts.cpp create mode 100644 src/modinfodialogconflicts.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aba922f9..eead6541 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp modinfodialogtextfiles.cpp @@ -157,6 +158,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h modinfodialogtextfiles.h @@ -358,6 +360,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogconflicts modinfodialogesps modinfodialogimages modinfodialogtextfiles diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a52f2031..c24176b3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -41,6 +41,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" +#include "modinfodialogconflicts.h" #include #include @@ -72,6 +73,7 @@ std::vector> ModInfoDialogTab::createTabs( v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); v.push_back(std::make_unique(ui)); + v.push_back(std::make_unique(ui)); return v; } @@ -81,6 +83,15 @@ bool ModInfoDialogTab::canClose() return true; } +void ModInfoDialogTab::saveState(Settings&) +{ + // no-op +} + +void ModInfoDialogTab::restoreState(const Settings& s) +{ + // no-op +} class ModFileListWidget : public QListWidgetItem { @@ -103,20 +114,6 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS const int max_scan_for_context_menu = 50; -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const - { - QStyledItemDelegate::initStyleOption(option, index); - option->textElideMode = Qt::ElideLeft; - } -}; - - bool canPreviewFile( PluginContainer* pluginContainer, bool isArchive, const QString& filename) { @@ -165,110 +162,6 @@ bool canUnhideFile(bool isArchive, const QString& filename) } -int naturalCompare(const QString& a, const QString& b) -{ - static QCollator c = []{ - QCollator c; - c.setNumericMode(true); - c.setCaseSensitivity(Qt::CaseInsensitive); - return c; - }(); - - return c.compare(a, b); -} - - -class ConflictItem : public QTreeWidgetItem -{ -public: - static const int FILENAME_USERROLE = Qt::UserRole + 1; - static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; - static const int ARCHIVE_USERROLE = Qt::UserRole + 3; - static const int INDEX_USERROLE = Qt::UserRole + 4; - static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; - - ConflictItem( - QStringList columns, FileEntry::Index index, const QString& fileName, - bool hasAltOrigins, const QString& altOrigin, bool archive) - : QTreeWidgetItem(columns) - { - setData(0, FILENAME_USERROLE, fileName); - setData(0, ALT_ORIGIN_USERROLE, altOrigin); - setData(0, ARCHIVE_USERROLE, archive); - setData(0, INDEX_USERROLE, index); - setData(0, HAS_ALTS_USERROLE, hasAltOrigins); - - if (archive) { - QFont f = font(0); - f.setItalic(true); - - for (int i=0; i); - return data(0, INDEX_USERROLE).toUInt(); - } - - bool canHide() const - { - return canHideFile(isArchive(), fileName()); - } - - bool canUnhide() const - { - return canUnhideFile(isArchive(), fileName()); - } - - bool canOpen() const - { - return canOpenFile(isArchive(), fileName()); - } - - bool canPreview(PluginContainer* pluginContainer) const - { - return canPreviewFile(pluginContainer, isArchive(), fileName()); - } - - bool operator<(const QTreeWidgetItem& other) const - { - const int column = treeWidget()->sortColumn(); - - if (column >= columnCount() || column >= other.columnCount()) { - // shouldn't happen - qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; - return false; - } - - return (naturalCompare(text(column), other.text(column)) < 0); - } -}; - - 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_RequestStarted(false), @@ -356,6 +249,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_INIFILES, false); ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); @@ -365,13 +259,13 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_INIFILES, true); ui->tabWidget->setTabEnabled(TAB_IMAGES, true); ui->tabWidget->setTabEnabled(TAB_ESPS, true); + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); initFiletree(modInfo); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); } - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); @@ -389,36 +283,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } - - m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true); - m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); - m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); - - - m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); - m_advancedConflictFilter.changed = [&]{ refreshConflictLists(false, true); }; - - // left-elide the overwrites column so that the nearest are visible - ui->conflictsAdvancedList->setItemDelegateForColumn( - 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); - - // left-elide the file column to see filenames - ui->conflictsAdvancedList->setItemDelegateForColumn( - 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); - - // don't elide the overwritten by column so that the nearest are visible - - connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { - refreshConflictLists(false, true); - }); - - connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] { - refreshConflictLists(false, true); - }); - - connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] { - refreshConflictLists(false, true); - }); } @@ -488,41 +352,19 @@ int ModInfoDialog::tabIndex(const QString &tabId) void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); - s.directInterface().setValue("mod_info_conflicts", saveConflictsState()); - s.directInterface().setValue( - "mod_info_conflicts_overwrite", - ui->overwriteTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_noconflict", - ui->noConflictTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_conflicts_overwritten", - ui->overwrittenTree->header()->saveState()); - - s.directInterface().setValue( - "mod_info_advanced_conflicts", - ui->conflictsAdvancedList->header()->saveState()); + for (const auto& tab : m_tabs) { + tab->saveState(s); + } } void ModInfoDialog::restoreState(const Settings& s) { restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray()); - - ui->overwriteTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_overwrite").toByteArray()); - - ui->noConflictTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_noconflict").toByteArray()); - ui->overwrittenTree->header()->restoreState( - s.directInterface().value("mod_info_conflicts_overwritten").toByteArray()); - - ui->conflictsAdvancedList->header()->restoreState( - s.directInterface().value("mod_info_advanced_conflicts").toByteArray()); + for (const auto& tab : m_tabs) { + tab->restoreState(s); + } } void ModInfoDialog::restoreTabState(const QByteArray &state) @@ -556,37 +398,6 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) ui->tabWidget->blockSignals(false); } -void ModInfoDialog::restoreConflictsState(const QByteArray &state) -{ - QDataStream stream(state); - - bool overwriteExpanded = false; - bool overwrittenExpanded = false; - bool noConflictExpanded = false; - - stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; - - if (stream.status() == QDataStream::Ok) { - m_overwriteExpander.toggle(overwriteExpanded); - m_overwrittenExpander.toggle(overwrittenExpanded); - m_nonconflictExpander.toggle(noConflictExpanded); - } - - int index = 0; - bool noConflictChecked = false; - bool showAllChecked = false; - bool showNearestChecked = false; - - stream >> index >> noConflictChecked >> showAllChecked >> showNearestChecked; - - if (stream.status() == QDataStream::Ok) { - ui->tabConflictsTabs->setCurrentIndex(index); - ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); - ui->conflictsAdvancedShowAll->setChecked(showAllChecked); - ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); - } -} - QByteArray ModInfoDialog::saveTabState() const { QByteArray result; @@ -599,251 +410,12 @@ QByteArray ModInfoDialog::saveTabState() const return result; } -QByteArray ModInfoDialog::saveConflictsState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - - stream - << m_overwriteExpander.opened() - << m_overwrittenExpander.opened() - << m_nonconflictExpander.opened() - << ui->tabConflictsTabs->currentIndex() - << ui->conflictsAdvancedShowNoConflict->isChecked() - << ui->conflictsAdvancedShowAll->isChecked() - << ui->conflictsAdvancedShowNearest->isChecked(); - - return result; -} - void ModInfoDialog::refreshLists() { refreshConflictLists(true, true); refreshFiles(); } -void ModInfoDialog::refreshConflictLists( - bool refreshGeneral, bool refreshAdvanced) -{ - int numNonConflicting = 0; - int numOverwrite = 0; - int numOverwritten = 0; - - if (refreshGeneral) { - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); - } - - if (refreshAdvanced) { - ui->conflictsAdvancedList->clear(); - } - - if (m_Origin != nullptr) { - std::vector files = m_Origin->getFiles(); - - for (const auto& file : m_Origin->getFiles()) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(m_RootPath); - - bool archive = false; - const int fileOrigin = file->getOrigin(archive); - const auto& alternatives = file->getAlternatives(); - - if (refreshGeneral) { - if (fileOrigin == m_Origin->getID()) { - if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); - - ++numOverwrite; - } else { - // otherwise, put the file in the noconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( - file->getIndex(), archive, fileName, relativeName)); - - ++numNonConflicting; - } - } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, fileName, relativeName)); - - ++numOverwritten; - } - } - - if (refreshAdvanced) { - auto* advancedItem = createAdvancedConflictItem( - file->getIndex(), fileOrigin, archive, - fileName, relativeName, alternatives); - - if (advancedItem) { - ui->conflictsAdvancedList->addTopLevelItem(advancedItem); - } - } - } - } - - if (refreshGeneral) { - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); - } -} - -QTreeWidgetItem* ModInfoDialog::createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, - const FileEntry::AlternativesVector& alternatives) -{ - QString altString; - - for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; - } - - altString += ToQString(m_Directory->getOriginByID(alt.first).getName()); - } - - QStringList fields(relativeName); - fields.append(altString); - - const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); - - return new ConflictItem(fields, index, fileName, true, origin, archive); -} - -QTreeWidgetItem* ModInfoDialog::createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName) -{ - return new ConflictItem({relativeName}, index, fileName, false, "", archive); -} - -QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName) -{ - const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); - - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - - return new ConflictItem( - fields, index, fileName, true, ToQString(realOrigin.getName()), archive); -} - -QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( - FileEntry::Index index,int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives) -{ - QString before, after; - - if (!alternatives.empty()) { - int beforePrio = 0; - int afterPrio = std::numeric_limits::max(); - - for (const auto& alt : alternatives) - { - const auto altOrigin = m_Directory->getOriginByID(alt.first); - - if (ui->conflictsAdvancedShowAll->isChecked()) { - // fills 'before' and 'after' with all the alternatives that come - // before and after this mod in terms of priority - - if (altOrigin.getPriority() < m_Origin->getPriority()) { - // add all the mods having a lower priority than this one - if (!before.isEmpty()) { - before += ", "; - } - - before += ToQString(altOrigin.getName()); - } else if (altOrigin.getPriority() > m_Origin->getPriority()) { - // add all the mods having a higher priority than this one - if (!after.isEmpty()) { - after += ", "; - } - - after += ToQString(altOrigin.getName()); - } - } else { - // keep track of the nearest mods that come before and after this one - // in terms of priority - - if (altOrigin.getPriority() < m_Origin->getPriority()) { - // the alternative has a lower priority than this mod - - if (altOrigin.getPriority() > beforePrio) { - // the alternative has a higher priority and therefore is closer - // to this mod, use it - before = ToQString(altOrigin.getName()); - beforePrio = altOrigin.getPriority(); - } - } - - if (altOrigin.getPriority() > m_Origin->getPriority()) { - // the alternative has a higher priority than this mod - - if (altOrigin.getPriority() < afterPrio) { - // the alternative has a lower priority and there is closer - // to this mod, use it - after = ToQString(altOrigin.getName()); - afterPrio = altOrigin.getPriority(); - } - } - } - } - - // the primary origin is never in the list of alternatives, so it has to - // be handled separately - // - // if 'after' is not empty, it means at least one alternative with a higher - // priority than this mod was found; if the user only wants to see the - // nearest mods, it's not worth checking for the primary origin because it - // will always have a higher priority than the alternatives (or it wouldn't - // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { - FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); - - // if no mods overwrite this file, the primary origin is the same as this - // mod, so ignore that - if (realOrigin.getID() != m_Origin->getID()) { - if (!after.isEmpty()) { - after += ", "; - } - - after += ToQString(realOrigin.getName()); - } - } - } - - bool hasAlts = !before.isEmpty() || !after.isEmpty(); - - if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { - // if both before and after are empty, it means this file has no conflicts - // at all, only display it if the user wants it - if (!hasAlts) { - return nullptr; - } - } - - bool matched = m_advancedConflictFilter.matches([&](auto&& what) { - return - before.contains(what, Qt::CaseInsensitive) || - relativeName.contains(what, Qt::CaseInsensitive) || - after.contains(what, Qt::CaseInsensitive); - }); - - if (!matched) { - return nullptr; - } - - return new ConflictItem( - {before, relativeName, after}, index, fileName, hasAlts, "", archive); -} - void ModInfoDialog::refreshFiles() { // clearing @@ -1475,7 +1047,6 @@ void ModInfoDialog::refreshPrimaryCategoriesBox() } } - void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) { if (index != -1) { @@ -1483,19 +1054,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) } } - -void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - if (auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); - } - } -} - FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) { const QString newName = oldName + ModInfo::s_HiddenExt; diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6ecf16bf..53b5d94a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -70,9 +70,31 @@ public: virtual void clear() = 0; virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; virtual bool canClose(); + virtual void saveState(Settings& s); + virtual void restoreState(const Settings& s); }; +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + + +bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename); +bool canOpenFile(bool isArchive, const QString& filename); +bool canHideFile(bool isArchive, const QString& filename); +bool canUnhideFile(bool isArchive, const QString& filename); + + /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system @@ -262,36 +284,13 @@ private: std::map m_RealTabPos; - ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; - FilterWidget m_advancedConflictFilter; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); - QTreeWidgetItem* createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives); - - QTreeWidgetItem* createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName); - - QTreeWidgetItem* createOverwrittenItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName); - - QTreeWidgetItem* createAdvancedConflictItem( - FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, - const MOShared::FileEntry::AlternativesVector& alternatives); - void restoreTabState(const QByteArray &state); - void restoreConflictsState(const QByteArray &state); - QByteArray saveTabState() const; - QByteArray saveConflictsState() const; void changeFiletreeVisibility(bool visible); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index f1212634..a26d15ab 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -241,7 +241,7 @@ - + List of esps, esms, and esls that can not be loaded by the game. @@ -285,7 +285,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Move a file to the data directory. @@ -308,7 +308,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Make the selected mod in the lower list unavailable. @@ -368,7 +368,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + ESPs in the data directory and thus visible to the game. @@ -386,23 +386,23 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Conflicts - + - 0 + 1 - + General - + 0 @@ -423,11 +423,11 @@ Most mods do not have optional esps, so chances are good you are looking at an e 0 - + - + - + 0 @@ -444,7 +444,7 @@ text-align: left; - + QFrame::Sunken @@ -461,7 +461,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -502,11 +502,11 @@ text-align: left; - + - + - + 0 @@ -523,7 +523,7 @@ text-align: left; - + QFrame::Sunken @@ -537,7 +537,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -578,11 +578,11 @@ text-align: left; - + - + - + 0 @@ -599,7 +599,7 @@ text-align: left; - + QFrame::Sunken @@ -613,7 +613,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -696,7 +696,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -751,7 +751,7 @@ text-align: left; 0 - + Whether files that have no conflicts should be visible in the list @@ -764,7 +764,7 @@ text-align: left; - + Shows all mods overwriting or being overwritten by this mod @@ -777,7 +777,7 @@ text-align: left; - + Shows only the nearest conflicting mods, in order of priority @@ -802,7 +802,7 @@ text-align: left; 0 - + Filter diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp new file mode 100644 index 00000000..17313436 --- /dev/null +++ b/src/modinfodialogconflicts.cpp @@ -0,0 +1,530 @@ +#include "modinfodialogconflicts.h" +#include "ui_modinfodialog.h" +#include "utility.h" + +using namespace MOShared; +using namespace MOBase; + +int naturalCompare(const QString& a, const QString& b) +{ + static QCollator c = []{ + QCollator c; + c.setNumericMode(true); + c.setCaseSensitivity(Qt::CaseInsensitive); + return c; + }(); + + return c.compare(a, b); +} + + +class ConflictItem : public QTreeWidgetItem +{ +public: + static const int FILENAME_USERROLE = Qt::UserRole + 1; + static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; + static const int ARCHIVE_USERROLE = Qt::UserRole + 3; + static const int INDEX_USERROLE = Qt::UserRole + 4; + static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; + + ConflictItem( + QStringList columns, FileEntry::Index index, const QString& fileName, + bool hasAltOrigins, const QString& altOrigin, bool archive) + : QTreeWidgetItem(columns) + { + setData(0, FILENAME_USERROLE, fileName); + setData(0, ALT_ORIGIN_USERROLE, altOrigin); + setData(0, ARCHIVE_USERROLE, archive); + setData(0, INDEX_USERROLE, index); + setData(0, HAS_ALTS_USERROLE, hasAltOrigins); + + if (archive) { + QFont f = font(0); + f.setItalic(true); + + for (int i=0; i); + return data(0, INDEX_USERROLE).toUInt(); + } + + bool canHide() const + { + return canHideFile(isArchive(), fileName()); + } + + bool canUnhide() const + { + return canUnhideFile(isArchive(), fileName()); + } + + bool canOpen() const + { + return canOpenFile(isArchive(), fileName()); + } + + bool canPreview(PluginContainer* pluginContainer) const + { + return canPreviewFile(pluginContainer, isArchive(), fileName()); + } + + bool operator<(const QTreeWidgetItem& other) const + { + const int column = treeWidget()->sortColumn(); + + if (column >= columnCount() || column >= other.columnCount()) { + // shouldn't happen + qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; + return false; + } + + return (naturalCompare(text(column), other.text(column)) < 0); + } +}; + + +ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_general(ui, oc), m_advanced(ui, oc) +{ +} + +void ConflictsTab::saveState(Settings& s) +{ + s.directInterface().setValue( + "mod_info_conflicts_tab", ui->tabConflictsTabs1->currentIndex()); + + m_general.saveState(s); + m_advanced.saveState(s); +} + +void ConflictsTab::restoreState(const Settings& s) +{ + ui->tabConflictsTabs1->setCurrentIndex( + s.directInterface().value("mod_info_conflicts_tab", 0).toInt()); + + m_general.restoreState(s); + m_advanced.restoreState(s); +} + + +GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_core(oc), m_origin(nullptr) +{ + m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); + m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); + m_expanders.nonconflict.set(ui->noConflictExpander1, ui->noConflictTree1); + + QObject::connect( + ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, + [&](auto* item, int col){ onOverwriteActivated(item, col); }); +} + +void GeneralConflictsTab::saveState(Settings& s) +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream + << m_expanders.overwrite.opened() + << m_expanders.overwritten.opened() + << m_expanders.nonconflict.opened(); + + s.directInterface().setValue( + "mod_info_conflicts_general_expanders", result); + + s.directInterface().setValue( + "mod_info_conflicts_general_overwrite", + ui->overwriteTree1->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_general_noconflict", + ui->noConflictTree1->header()->saveState()); + + s.directInterface().setValue( + "mod_info_conflicts_general_overwritten", + ui->overwrittenTree1->header()->saveState()); +} + +void GeneralConflictsTab::restoreState(const Settings& s) +{ + QDataStream stream(s.directInterface() + .value("mod_info_conflicts_general_expanders").toByteArray()); + + bool overwriteExpanded = false; + bool overwrittenExpanded = false; + bool noConflictExpanded = false; + + stream >> overwriteExpanded >> overwrittenExpanded >> noConflictExpanded; + + if (stream.status() == QDataStream::Ok) { + m_expanders.overwrite.toggle(overwriteExpanded); + m_expanders.overwritten.toggle(overwrittenExpanded); + m_expanders.nonconflict.toggle(noConflictExpanded); + } + + ui->overwriteTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_overwrite").toByteArray()); + + ui->noConflictTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_noconflict").toByteArray()); + + ui->overwrittenTree1->header()->restoreState(s.directInterface() + .value("mod_info_conflicts_general_overwritten").toByteArray()); +} + +void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + update(); +} + +void GeneralConflictsTab::update() +{ + int numNonConflicting = 0; + int numOverwrite = 0; + int numOverwritten = 0; + + ui->overwriteTree1->clear(); + ui->overwrittenTree1->clear(); + ui->noConflictTree1->clear(); + + if (m_origin != nullptr) { + const auto rootPath = m_mod->absolutePath(); + + for (const auto& file : m_origin->getFiles()) { + const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + const QString fileName = relativeName.mid(0).prepend(rootPath); + + bool archive = false; + const int fileOrigin = file->getOrigin(archive); + const auto& alternatives = file->getAlternatives(); + + if (fileOrigin == m_origin->getID()) { + if (!alternatives.empty()) { + ui->overwriteTree1->addTopLevelItem(createOverwriteItem( + file->getIndex(), archive, fileName, relativeName, alternatives)); + + ++numOverwrite; + } else { + // otherwise, put the file in the noconflict tree + ui->noConflictTree1->addTopLevelItem(createNoConflictItem( + file->getIndex(), archive, fileName, relativeName)); + + ++numNonConflicting; + } + } else { + ui->overwrittenTree1->addTopLevelItem(createOverwrittenItem( + file->getIndex(), fileOrigin, archive, fileName, relativeName)); + + ++numOverwritten; + } + } + } + + ui->overwriteCount1->display(numOverwrite); + ui->overwrittenCount1->display(numOverwritten); + ui->noConflictCount1->display(numNonConflicting); +} + +QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, + const FileEntry::AlternativesVector& alternatives) +{ + const auto& ds = *m_core.directoryStructure(); + + QString altString; + + for (const auto& alt : alternatives) { + if (!altString.isEmpty()) { + altString += ", "; + } + + altString += ToQString(ds.getOriginByID(alt.first).getName()); + } + + QStringList fields(relativeName); + fields.append(altString); + + const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); + + return new ConflictItem(fields, index, fileName, true, origin, archive); +} + +QTreeWidgetItem* GeneralConflictsTab::createNoConflictItem( + FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName) +{ + return new ConflictItem({relativeName}, index, fileName, false, "", archive); +} + +QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( + FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName) +{ + const auto& ds = *m_core.directoryStructure(); + + const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + + QStringList fields(relativeName); + fields.append(ToQString(realOrigin.getName())); + + return new ConflictItem( + fields, index, fileName, true, ToQString(realOrigin.getName()), archive); +} + +void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) +{ + if (auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } + } +} + + +AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) + : ui(ui), m_core(oc), m_origin(nullptr) +{ + // left-elide the overwrites column so that the nearest are visible + ui->conflictsAdvancedList1->setItemDelegateForColumn( + 0, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + + // left-elide the file column to see filenames + ui->conflictsAdvancedList1->setItemDelegateForColumn( + 1, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + + // don't elide the overwritten by column so that the nearest are visible + + QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] { + update(); + }); + + QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] { + update(); + }); + + QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] { + update(); + }); + + m_filter.set(ui->conflictsAdvancedFilter1); + m_filter.changed = [&]{ update(); }; +} + +void AdvancedConflictsTab::saveState(Settings& s) +{ + s.directInterface().setValue( + "mod_info_conflicts_advanced_list", + ui->conflictsAdvancedList1->header()->saveState()); + + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + + stream + << ui->conflictsAdvancedShowNoConflict1->isChecked() + << ui->conflictsAdvancedShowAll1->isChecked() + << ui->conflictsAdvancedShowNearest1->isChecked(); + + s.directInterface().setValue( + "mod_info_conflicts_advanced_options", + ui->conflictsAdvancedList1->header()->saveState()); +} + +void AdvancedConflictsTab::restoreState(const Settings& s) +{ + ui->conflictsAdvancedList1->header()->restoreState( + s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray()); + + QDataStream stream(s.directInterface() + .value("mod_info_conflicts_advanced_options").toByteArray()); + + bool noConflictChecked = false; + bool showAllChecked = false; + bool showNearestChecked = false; + + stream >> noConflictChecked >> showAllChecked >> showNearestChecked; + + if (stream.status() == QDataStream::Ok) { + ui->conflictsAdvancedShowNoConflict1->setChecked(noConflictChecked); + ui->conflictsAdvancedShowAll1->setChecked(showAllChecked); + ui->conflictsAdvancedShowNearest1->setChecked(showNearestChecked); + } +} + +void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + update(); +} + +void AdvancedConflictsTab::update() +{ + ui->conflictsAdvancedList1->clear(); + + if (m_origin != nullptr) { + const auto rootPath = m_mod->absolutePath(); + + for (const auto& file : m_origin->getFiles()) { + const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + const QString fileName = relativeName.mid(0).prepend(rootPath); + + bool archive = false; + const int fileOrigin = file->getOrigin(archive); + const auto& alternatives = file->getAlternatives(); + + auto* advancedItem = createItem( + file->getIndex(), fileOrigin, archive, + fileName, relativeName, alternatives); + + if (advancedItem) { + ui->conflictsAdvancedList1->addTopLevelItem(advancedItem); + } + } + } +} + +QTreeWidgetItem* AdvancedConflictsTab::createItem( + FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives) +{ + const auto& ds = *m_core.directoryStructure(); + + QString before, after; + + if (!alternatives.empty()) { + int beforePrio = 0; + int afterPrio = std::numeric_limits::max(); + + for (const auto& alt : alternatives) + { + const auto altOrigin = ds.getOriginByID(alt.first); + + if (ui->conflictsAdvancedShowAll1->isChecked()) { + // fills 'before' and 'after' with all the alternatives that come + // before and after this mod in terms of priority + + if (altOrigin.getPriority() < m_origin->getPriority()) { + // add all the mods having a lower priority than this one + if (!before.isEmpty()) { + before += ", "; + } + + before += ToQString(altOrigin.getName()); + } else if (altOrigin.getPriority() > m_origin->getPriority()) { + // add all the mods having a higher priority than this one + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(altOrigin.getName()); + } + } else { + // keep track of the nearest mods that come before and after this one + // in terms of priority + + if (altOrigin.getPriority() < m_origin->getPriority()) { + // the alternative has a lower priority than this mod + + if (altOrigin.getPriority() > beforePrio) { + // the alternative has a higher priority and therefore is closer + // to this mod, use it + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } + } + + if (altOrigin.getPriority() > m_origin->getPriority()) { + // the alternative has a higher priority than this mod + + if (altOrigin.getPriority() < afterPrio) { + // the alternative has a lower priority and there is closer + // to this mod, use it + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } + } + } + } + + // the primary origin is never in the list of alternatives, so it has to + // be handled separately + // + // if 'after' is not empty, it means at least one alternative with a higher + // priority than this mod was found; if the user only wants to see the + // nearest mods, it's not worth checking for the primary origin because it + // will always have a higher priority than the alternatives (or it wouldn't + // be the primary) + if (after.isEmpty() || ui->conflictsAdvancedShowAll1->isChecked()) { + FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + + // if no mods overwrite this file, the primary origin is the same as this + // mod, so ignore that + if (realOrigin.getID() != m_origin->getID()) { + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(realOrigin.getName()); + } + } + } + + bool hasAlts = !before.isEmpty() || !after.isEmpty(); + + if (!ui->conflictsAdvancedShowNoConflict1->isChecked()) { + // if both before and after are empty, it means this file has no conflicts + // at all, only display it if the user wants it + if (!hasAlts) { + return nullptr; + } + } + + bool matched = m_filter.matches([&](auto&& what) { + return + before.contains(what, Qt::CaseInsensitive) || + relativeName.contains(what, Qt::CaseInsensitive) || + after.contains(what, Qt::CaseInsensitive); + }); + + if (!matched) { + return nullptr; + } + + return new ConflictItem( + {before, relativeName, after}, index, fileName, hasAlts, "", archive); +} diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h new file mode 100644 index 00000000..894f9dd4 --- /dev/null +++ b/src/modinfodialogconflicts.h @@ -0,0 +1,86 @@ +#ifndef MODINFODIALOGCONFLICTS_H +#define MODINFODIALOGCONFLICTS_H + +#include "modinfodialog.h" +#include "expanderwidget.h" + +class GeneralConflictsTab +{ +public: + GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s); + void restoreState(const Settings& s); + + void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void update(); + +private: + struct Expanders + { + ExpanderWidget overwrite, overwritten, nonconflict; + }; + + Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; + Expanders m_expanders; + + QTreeWidgetItem* createOverwriteItem( + MOShared::FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); + + QTreeWidgetItem* createNoConflictItem( + MOShared::FileEntry::Index index, bool archive, + const QString& fileName, const QString& relativeName); + + QTreeWidgetItem* createOverwrittenItem( + MOShared::FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName); + + void onOverwriteActivated(QTreeWidgetItem* item, int); +}; + + +class AdvancedConflictsTab +{ +public: + AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s); + void restoreState(const Settings& s); + + void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void update(); + +private: + Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; + FilterWidget m_filter; + + QTreeWidgetItem* createItem( + MOShared::FileEntry::Index index, int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); +}; + + +class ConflictsTab : public ModInfoDialogTab +{ +public: + ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; + +private: + Ui::ModInfoDialog* ui; + GeneralConflictsTab m_general; + AdvancedConflictsTab m_advanced; +}; + +#endif // MODINFODIALOGCONFLICTS_H -- cgit v1.3.1 From b5c47eb161f6e20c9275909d32202e52bf3ba4a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:24:44 -0400 Subject: pass parent widget to tabs for dialogs finished moving over conflict tab to its own set of files --- src/modinfodialog.cpp | 444 +++++++---------------------------------- src/modinfodialog.h | 62 ++---- src/modinfodialogconflicts.cpp | 441 +++++++++++++++++++++++++++++++++++++--- src/modinfodialogconflicts.h | 80 +++++++- src/modinfodialogesps.cpp | 30 +-- src/modinfodialogesps.h | 5 +- src/modinfodialogimages.cpp | 2 +- src/modinfodialogimages.h | 4 +- src/modinfodialogtextfiles.cpp | 17 +- src/modinfodialogtextfiles.h | 13 +- 10 files changed, 616 insertions(+), 482 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c24176b3..d52103b9 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -63,20 +63,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const int max_scan_for_context_menu = 50; -std::vector> ModInfoDialogTab::createTabs( - Ui::ModInfoDialog* ui) -{ - std::vector> v; - - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - - return v; -} bool ModInfoDialogTab::canClose() { @@ -93,6 +81,26 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } +void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +{ + // no-op +} + +void ModInfoDialogTab::update() +{ + // no-op +} + +void ModInfoDialogTab::emitOriginModified(int originID) +{ + emit originModified(originID); +} + +void ModInfoDialogTab::emitModOpen(QString name) +{ + emit modOpen(name); +} + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -109,20 +117,16 @@ 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 menu items apply to them, just show all of them -const int max_scan_for_context_menu = 50; - bool canPreviewFile( - PluginContainer* pluginContainer, bool isArchive, const QString& filename) + PluginContainer& pluginContainer, bool isArchive, const QString& filename) { if (isArchive) { return false; } const auto ext = QFileInfo(filename).suffix(); - return pluginContainer->previewGenerator().previewSupported(ext); + return pluginContainer.previewGenerator().previewSupported(ext); } bool canOpenFile(bool isArchive, const QString&) @@ -161,6 +165,18 @@ bool canUnhideFile(bool isArchive, const QString& filename) return true; } +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName) +{ + const QString newName = oldName + ModInfo::s_HiddenExt; + return renamer.rename(oldName, newName); +} + +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName) +{ + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + return renamer.rename(oldName, newName); +} + 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), @@ -172,7 +188,20 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo { ui->setupUi(this); - m_tabs = ModInfoDialogTab::createTabs(ui); + m_tabs = createTabs(); + + for (std::size_t i=0; i(i)); + }); + } this->setWindowTitle(modInfo->name()); this->setWindowModality(Qt::WindowModal); @@ -223,12 +252,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - // refresh everything but the conflict lists, which are done in exec() because - // they depend on restoring the state to some widgets; this refresh has to be - // done here because some of the checks below depend on the ui to decide which - // tabs to enable - refreshFiles(); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -283,8 +306,11 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } -} + for (auto& tab : m_tabs) { + tab->setMod(m_ModInfo, m_Origin); + } +} ModInfoDialog::~ModInfoDialog() { @@ -301,11 +327,23 @@ ModInfoDialog::~ModInfoDialog() delete m_Settings; } +std::vector> ModInfoDialog::createTabs() +{ + std::vector> v; + + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique( + this, ui, *m_OrganizerCore, *m_PluginContainer)); + + return v; +} int ModInfoDialog::exec() { - // no need to refresh the other stuff, that was done in the constructor - refreshConflictLists(true, true); + refreshLists(); return TutorableDialog::exec(); } @@ -412,17 +450,15 @@ QByteArray ModInfoDialog::saveTabState() const void ModInfoDialog::refreshLists() { - refreshConflictLists(true, true); + for (auto& tab : m_tabs) { + tab->update(); + } + refreshFiles(); } void ModInfoDialog::refreshFiles() { - // clearing - for (auto& tab : m_tabs) { - tab->clear(); - } - if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -936,7 +972,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (!canPreviewFile(m_PluginContainer, false, fileName)) { + if (!canPreviewFile(*m_PluginContainer, false, fileName)) { enablePreview = false; } @@ -1054,342 +1090,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) } } -FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) -{ - const QString newName = oldName + ModInfo::s_HiddenExt; - return renamer.rename(oldName, newName); -} - -FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - return renamer.rename(oldName, newName); -} - -void ModInfoDialog::changeConflictItemsVisibility( - const QList& items, bool visible) -{ - bool changed = false; - bool stop = false; - - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << items.size() << " conflict files"; - - QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - - if (items.size() > 1) { - flags |= FileRenamer::MULTIPLE; - } - - FileRenamer renamer(this, flags); - - for (const auto* item : items) { - if (stop) { - break; - } - - const auto* ci = dynamic_cast(item); - if (!ci) { - continue; - } - - auto result = FileRenamer::RESULT_CANCEL; - - if (visible) { - if (!ci->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; - continue; - } - result = unhideFile(renamer, ci->fileName()); - - } else { - if (!ci->canHide()) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; - continue; - } - result = hideFile(renamer, ci->fileName()); - } - - 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() << (visible ? "unhiding" : "hiding") << " conflict files done"; - - if (changed) { - qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); - } - refreshLists(); - } -} - -void ModInfoDialog::openConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->executeFileVirtualized(this, ci->fileName()); - } - } -} - -void ModInfoDialog::previewConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->previewFileWithAlternatives(this, ci->fileName()); - } - } -} - -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwriteTree); -} - -void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwrittenTree); -} - -void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->noConflictTree); -} - -void ModInfoDialog::on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->conflictsAdvancedList); -} - -void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) -{ - auto actions = createConflictMenuActions(tree->selectedItems()); - - QMenu menu; - - // open - if (actions.open) { - connect(actions.open, &QAction::triggered, [&]{ - openConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.open); - } - - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.preview); - } - - // hide - if (actions.hide) { - connect(actions.hide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), false); - }); - - menu.addAction(actions.hide); - } - - // unhide - if (actions.unhide) { - connect(actions.unhide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), true); - }); - - menu.addAction(actions.unhide); - } - - // goto - if (actions.gotoMenu) { - menu.addMenu(actions.gotoMenu); - - for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ - close(); - emit modOpen(name, TAB_CONFLICTS); - }); - - actions.gotoMenu->addAction(a); - } - } - - if (!menu.isEmpty()) { - menu.exec(tree->viewport()->mapToGlobal(pos)); - } -} - -ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( - const QList& selection) -{ - if (selection.empty()) { - return {}; - } - - bool enableHide = true; - bool enableUnhide = true; - bool enableOpen = true; - bool enablePreview = true; - bool enableGoto = true; - - if (selection.size() == 1) { - // this is a single selection - const auto* ci = dynamic_cast(selection[0]); - if (!ci) { - return {}; - } - - enableHide = ci->canHide(); - enableUnhide = ci->canUnhide(); - enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(m_PluginContainer); - enableGoto = ci->hasAlts(); - } - else { - // this is a multiple selection, don't show open/preview so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - - // don't bother with this on multiple selection, at least for now - enableGoto = false; - - if (selection.size() < max_scan_for_context_menu) { - // 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 (const auto* ci=dynamic_cast(item)) { - if (ci->canHide()) { - enableHide = true; - } - - if (ci->canUnhide()) { - enableUnhide = true; - } - - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; - } - } - } - } - } - - ConflictActions actions; - - actions.hide = new QAction(tr("Hide"), this); - actions.hide->setEnabled(enableHide); - - // note that it is possible for hidden files to appear if they override other - // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), this); - actions.unhide->setEnabled(enableUnhide); - - actions.open = new QAction(tr("Open/Execute"), this); - actions.open->setEnabled(enableOpen); - - actions.preview = new QAction(tr("Preview"), this); - actions.preview->setEnabled(enablePreview); - - actions.gotoMenu = new QMenu(tr("Go to..."), this); - actions.gotoMenu->setEnabled(enableGoto); - - if (enableGoto) { - actions.gotoActions = createGotoActions(selection); - } - - return actions; -} - -std::vector ModInfoDialog::createGotoActions(const QList& selection) -{ - if (!m_Origin || selection.size() != 1) { - return {}; - } - - const auto* item = dynamic_cast(selection[0]); - if (!item) { - return {}; - } - - auto file = m_Origin->findFile(item->fileIndex()); - if (!file) { - return {}; - } - - - std::vector mods; - - // add all alternatives - for (const auto& alt : file->getAlternatives()) { - const auto& o = m_Directory->getOriginByID(alt.first); - if (o.getID() != m_Origin->getID()) { - mods.push_back(ToQString(o.getName())); - } - } - - // add the real origin if different from this mod - const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin()); - if (realOrigin.getID() != m_Origin->getID()) { - mods.push_back(ToQString(realOrigin.getName())); - } - - std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { - return (QString::localeAwareCompare(a, b) < 0); - }); - - std::vector actions; - - for (const auto& name : mods) { - actions.push_back(new QAction(name, this)); - } - - return actions; -} - -void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - if (const auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); - } - } -} - void ModInfoDialog::on_refreshButton_clicked() { if (m_ModInfo->getNexusID() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 53b5d94a..2a3ee2d8 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -54,12 +54,11 @@ class CategoryFactory; class TextEditor; -class ModInfoDialogTab +class ModInfoDialogTab : public QObject { -public: - static std::vector> createTabs( - Ui::ModInfoDialog* ui); + Q_OBJECT; +public: ModInfoDialogTab() = default; ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; @@ -72,6 +71,17 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void update(); + +signals: + void originModified(int originID); + void modOpen(QString name); + +protected: + void emitOriginModified(int originID); + void emitModOpen(QString name); }; @@ -89,11 +99,14 @@ protected: }; -bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename); +bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); bool canUnhideFile(bool isArchive, const QString& filename); +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); + /** * this is a larger dialog used to visualise information about the mod. @@ -185,8 +198,6 @@ private: bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); void saveCategories(QTreeWidgetItem *currentNode); - FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); - FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); void refreshPrimaryCategoriesBox(); @@ -216,12 +227,6 @@ private slots: void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwriteTree_customContextMenuRequested(const QPoint &pos); - void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); - void on_noConflictTree_customContextMenuRequested(const QPoint &pos); - void on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); @@ -235,22 +240,6 @@ private slots: private: using FileEntry = MOShared::FileEntry; - struct ConflictActions - { - QAction* hide; - QAction* unhide; - QAction* open; - QAction* preview; - QMenu* gotoMenu; - std::vector gotoActions; - - ConflictActions() : - hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), - gotoMenu(nullptr) - { - } - }; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; @@ -285,27 +274,14 @@ private: std::map m_RealTabPos; + std::vector> createTabs(); - void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void changeFiletreeVisibility(bool visible); - - void openConflictItems(const QList& items); - void previewConflictItems(const QList& items); - void changeConflictItemsVisibility( - const QList& items, bool visible); - - void showConflictMenu(const QPoint &pos, QTreeWidget* tree); - - ConflictActions createConflictMenuActions( - const QList& selection); - - std::vector createGotoActions( - const QList& selection); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 17313436..22306d0c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -5,6 +5,11 @@ using namespace MOShared; using namespace MOBase; +// if there are more than 50 selected items in the conflict tree or filetree, +// don't bother checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; + + int naturalCompare(const QString& a, const QString& b) { static QCollator c = []{ @@ -89,7 +94,7 @@ public: return canOpenFile(isArchive(), fileName()); } - bool canPreview(PluginContainer* pluginContainer) const + bool canPreview(PluginContainer& pluginContainer) const { return canPreviewFile(pluginContainer, isArchive(), fileName()); } @@ -109,9 +114,45 @@ public: }; -ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_general(ui, oc), m_advanced(ui, oc) +ConflictsTab::ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin) : + m_parent(parent), ui(ui), m_core(oc), m_plugin(plugin), + m_origin(nullptr), m_general(this, ui, oc), m_advanced(this, ui, oc) +{ + connect( + &m_general, &GeneralConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); + + connect( + &m_advanced, &AdvancedConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); +} + +void ConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + m_general.setMod(mod, origin); + m_advanced.setMod(mod, origin); +} + +void ConflictsTab::update() { + m_general.update(); + m_advanced.update(); +} + +void ConflictsTab::clear() +{ + m_general.clear(); + m_advanced.clear(); +} + +bool ConflictsTab::feedFile(const QString&, const QString&) +{ + return false; } void ConflictsTab::saveState(Settings& s) @@ -132,9 +173,307 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +void ConflictsTab::changeItemsVisibility( + const QList& items, bool visible) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (visible ? "unhiding" : "hiding") << " " + << items.size() << " conflict files"; + + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + + if (items.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + + FileRenamer renamer(m_parent, flags); + + for (const auto* item : items) { + if (stop) { + break; + } + + const auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } + + auto result = FileRenamer::RESULT_CANCEL; + + if (visible) { + if (!ci->canUnhide()) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + continue; + } + + result = unhideFile(renamer, ci->fileName()); + + } else { + if (!ci->canHide()) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + continue; + } + + result = hideFile(renamer, ci->fileName()); + } + + 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() << (visible ? "unhiding" : "hiding") << " conflict files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + + if (m_origin) { + emit originModified(m_origin->getID()); + } + + update(); + } +} + +void ConflictsTab::openItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.executeFileVirtualized(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::previewItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.previewFileWithAlternatives(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) +{ + auto actions = createMenuActions(tree->selectedItems()); + + QMenu menu; + + // open + if (actions.open) { + connect(actions.open, &QAction::triggered, [&]{ + openItems(tree->selectedItems()); + }); + + menu.addAction(actions.open); + } + + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree->selectedItems()); + }); + + menu.addAction(actions.preview); + } + + // hide + if (actions.hide) { + connect(actions.hide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), false); + }); + + menu.addAction(actions.hide); + } + + // unhide + if (actions.unhide) { + connect(actions.unhide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), true); + }); + + menu.addAction(actions.unhide); + } + + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + emitModOpen(name); + }); + + actions.gotoMenu->addAction(a); + } + } + + if (!menu.isEmpty()) { + menu.exec(tree->viewport()->mapToGlobal(pos)); + } +} + +ConflictsTab::Actions ConflictsTab::createMenuActions( + const QList& selection) +{ + if (selection.empty()) { + return {}; + } + + bool enableHide = true; + bool enableUnhide = true; + bool enableOpen = true; + bool enablePreview = true; + bool enableGoto = true; + + if (selection.size() == 1) { + // this is a single selection + const auto* ci = dynamic_cast(selection[0]); + if (!ci) { + return {}; + } + + enableHide = ci->canHide(); + enableUnhide = ci->canUnhide(); + enableOpen = ci->canOpen(); + enablePreview = ci->canPreview(m_plugin); + enableGoto = ci->hasAlts(); + } + else { + // this is a multiple selection, don't show open/preview so users don't open + // a thousand files + enableOpen = false; + enablePreview = false; + + // don't bother with this on multiple selection, at least for now + enableGoto = false; + + if (selection.size() < max_scan_for_context_menu) { + // 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 (const auto* ci=dynamic_cast(item)) { + if (ci->canHide()) { + enableHide = true; + } + + if (ci->canUnhide()) { + enableUnhide = true; + } + + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + break; + } + } + } + } + } + + Actions actions; + + actions.hide = new QAction(tr("Hide"), m_parent); + actions.hide->setEnabled(enableHide); + + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + actions.unhide = new QAction(tr("Unhide"), m_parent); + actions.unhide->setEnabled(enableUnhide); + + actions.open = new QAction(tr("Open/Execute"), m_parent); + actions.open->setEnabled(enableOpen); + + actions.preview = new QAction(tr("Preview"), m_parent); + actions.preview->setEnabled(enablePreview); + + actions.gotoMenu = new QMenu(tr("Go to..."), m_parent); + actions.gotoMenu->setEnabled(enableGoto); + + if (enableGoto) { + actions.gotoActions = createGotoActions(selection); + } + + return actions; +} + +std::vector ConflictsTab::createGotoActions( + const QList& selection) +{ + if (!m_origin || selection.size() != 1) { + return {}; + } + + const auto* item = dynamic_cast(selection[0]); + if (!item) { + return {}; + } + + auto file = m_origin->findFile(item->fileIndex()); + if (!file) { + return {}; + } + + + std::vector mods; + const auto& ds = *m_core.directoryStructure(); + + // add all alternatives + for (const auto& alt : file->getAlternatives()) { + const auto& o = ds.getOriginByID(alt.first); + if (o.getID() != m_origin->getID()) { + mods.push_back(ToQString(o.getName())); + } + } + + // add the real origin if different from this mod + const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin()); + if (realOrigin.getID() != m_origin->getID()) { + mods.push_back(ToQString(realOrigin.getName())); + } + + std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { + return (QString::localeAwareCompare(a, b) < 0); + }); + + std::vector actions; + + for (const auto& name : mods) { + actions.push_back(new QAction(name, m_parent)); + } + + return actions; +} -GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +GeneralConflictsTab::GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); @@ -143,6 +482,33 @@ GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& o QObject::connect( ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwriteActivated(item, col); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::itemDoubleClicked, + [&](auto* item, int col){ onOverwrittenActivated(item, col); }); + + QObject::connect( + ui->overwriteTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree1); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree1); }); + + QObject::connect( + ui->noConflictTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree1); }); +} + +void GeneralConflictsTab::clear() +{ + ui->overwriteTree1->clear(); + ui->overwrittenTree1->clear(); + ui->noConflictTree1->clear(); + + ui->overwriteCount1->display(0); + ui->overwrittenCount1->display(0); + ui->noConflictCount1->display(0); } void GeneralConflictsTab::saveState(Settings& s) @@ -198,24 +564,20 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin) +void GeneralConflictsTab::setMod(ModInfo::Ptr mod, FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void GeneralConflictsTab::update() { + clear(); + int numNonConflicting = 0; int numOverwrite = 0; int numOverwritten = 0; - ui->overwriteTree1->clear(); - ui->overwrittenTree1->clear(); - ui->noConflictTree1->clear(); - if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); @@ -307,15 +669,26 @@ void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) const auto origin = ci->altOrigin(); if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); + emit modOpen(origin); } } } +void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) +{ + if (const auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); + + if (!origin.isEmpty()) { + emit modOpen(origin); + } + } +} -AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +AdvancedConflictsTab::AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList1->setItemDelegateForColumn( @@ -327,22 +700,31 @@ AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& // don't elide the overwritten by column so that the nearest are visible - QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, + [&]{ update(); }); + + QObject::connect( + ui->conflictsAdvancedList1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList1); }); m_filter.set(ui->conflictsAdvancedFilter1); m_filter.changed = [&]{ update(); }; } +void AdvancedConflictsTab::clear() +{ + ui->conflictsAdvancedList1->clear(); +} + void AdvancedConflictsTab::saveState(Settings& s) { s.directInterface().setValue( @@ -358,8 +740,7 @@ void AdvancedConflictsTab::saveState(Settings& s) << ui->conflictsAdvancedShowNearest1->isChecked(); s.directInterface().setValue( - "mod_info_conflicts_advanced_options", - ui->conflictsAdvancedList1->header()->saveState()); + "mod_info_conflicts_advanced_options", result); } void AdvancedConflictsTab::restoreState(const Settings& s) @@ -383,17 +764,15 @@ void AdvancedConflictsTab::restoreState(const Settings& s) } } -void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void AdvancedConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void AdvancedConflictsTab::update() { - ui->conflictsAdvancedList1->clear(); + clear(); if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 894f9dd4..5843884a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -4,23 +4,33 @@ #include "modinfodialog.h" #include "expanderwidget.h" -class GeneralConflictsTab +class ConflictsTab; + +class GeneralConflictsTab : public QObject { + Q_OBJECT; + public: - GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: struct Expanders { ExpanderWidget overwrite, overwritten, nonconflict; }; + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -41,21 +51,34 @@ private: const QString& fileName, const QString& relativeName); void onOverwriteActivated(QTreeWidgetItem* item, int); + void onOverwrittenActivated(QTreeWidgetItem *item, int); + + void onOverwriteTreeContext(const QPoint &pos); + void onOverwrittenTreeContext(const QPoint &pos); + void onNoConflictTreeContext(const QPoint &pos); }; -class AdvancedConflictsTab +class AdvancedConflictsTab : public QObject { + Q_OBJECT; + public: - AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -71,16 +94,59 @@ private: class ConflictsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin); + + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void update() override; + void clear() override; + bool feedFile(const QString& rootPath, const QString& filename) override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + void openItems(const QList& items); + void previewItems(const QList& items); + + void changeItemsVisibility( + const QList& items, bool visible); + + void showContextMenu(const QPoint &pos, QTreeWidget* tree); + private: + struct Actions + { + QAction* hide; + QAction* unhide; + QAction* open; + QAction* preview; + QMenu* gotoMenu; + std::vector gotoActions; + + Actions() : + hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), + gotoMenu(nullptr) + { + } + }; + + QWidget* m_parent; Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + PluginContainer& m_plugin; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; + + Actions createMenuActions(const QList& selection); + + std::vector createGotoActions( + const QList& selection); }; #endif // MODINFODIALOGCONFLICTS_H diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 32dcdb35..6c87b10d 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -123,20 +123,20 @@ private: }; -ESPsTab::ESPsTab(Ui::ModInfoDialog* ui) - : ui(ui) +ESPsTab::ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui) + : m_parent(parent), ui(ui) { QObject::connect( - ui->activateESP1, &QToolButton::clicked, [&]{ onActivate(); }); + ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); QObject::connect( - ui->deactivateESP1, &QToolButton::clicked, [&]{ onDeactivate(); }); + ui->deactivateESP, &QToolButton::clicked, [&]{ onDeactivate(); }); } void ESPsTab::clear() { - ui->inactiveESPList1->clear(); - ui->activeESPList1->clear(); + ui->inactiveESPList->clear(); + ui->activeESPList->clear(); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -152,9 +152,9 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) auto* item = new ESPItem({rootPath, relativePath}); if (item->esp().isActive()) { - ui->activeESPList1->addItem(item); + ui->activeESPList->addItem(item); } else { - ui->inactiveESPList1->addItem(item); + ui->inactiveESPList->addItem(item); } return true; @@ -188,7 +188,7 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - ui->tabESPs, + m_parent, QObject::tr("File Exists"), QObject::tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, file.fileName(), &okClicked); @@ -203,8 +203,8 @@ void ESPsTab::onActivate() } if (esp.activate(newName)) { - ui->inactiveESPList1->takeItem(ui->inactiveESPList1->row(item)); - ui->activeESPList1->addItem(item); + ui->inactiveESPList->takeItem(ui->inactiveESPList->row(item)); + ui->activeESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -246,8 +246,8 @@ void ESPsTab::onDeactivate() } if (esp.deactivate(newName)) { - ui->activeESPList1->takeItem(ui->activeESPList1->row(item)); - ui->inactiveESPList1->addItem(item); + ui->activeESPList->takeItem(ui->activeESPList->row(item)); + ui->inactiveESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -256,7 +256,7 @@ void ESPsTab::onDeactivate() ESPItem* ESPsTab::selectedInactive() { - auto* item = ui->inactiveESPList1->currentItem(); + auto* item = ui->inactiveESPList->currentItem(); if (!item) { return nullptr; } @@ -272,7 +272,7 @@ ESPItem* ESPsTab::selectedInactive() ESPItem* ESPsTab::selectedActive() { - auto* item = ui->activeESPList1->currentItem(); + auto* item = ui->activeESPList->currentItem(); if (!item) { return nullptr; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index a46677cb..8da70377 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -7,13 +7,16 @@ class ESPItem; class ESPsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ESPsTab(Ui::ModInfoDialog* ui); + ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: + QWidget* m_parent; Ui::ModInfoDialog* ui; void onActivate(); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1659bed3..752c8d0c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -80,7 +80,7 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) } -ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) +ImagesTab::ImagesTab(QWidget*, Ui::ModInfoDialog* ui) : ui(ui), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 73fb5936..a40904f7 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -31,8 +31,10 @@ private: class ImagesTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ImagesTab(Ui::ModInfoDialog* ui); + ImagesTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index cf800aa6..0dd6f06e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -21,8 +21,9 @@ private: }; -GenericFilesTab::GenericFilesTab(QListWidget* list, QSplitter* sp, TextEditor* e) - : m_list(list), m_editor(e) +GenericFilesTab::GenericFilesTab( + QWidget* parent, QListWidget* list, QSplitter* sp, TextEditor* e) + : m_parent(parent), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -48,7 +49,7 @@ bool GenericFilesTab::canClose() } const int res = QMessageBox::question( - m_list, + m_parent, QObject::tr("Save changes?"), QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); @@ -108,8 +109,9 @@ void GenericFilesTab::select(FileListItem* item) } -TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +TextFilesTab::TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -128,8 +130,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +IniFilesTab::IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 8725ea09..d30e20d7 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -10,17 +10,22 @@ class TextEditor; class GenericFilesTab : public ModInfoDialogTab { -public: - GenericFilesTab(QListWidget* list, QSplitter* splitter, TextEditor* editor); + Q_OBJECT; +public: void clear() override; bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; protected: + QWidget* m_parent; QListWidget* m_list; TextEditor* m_editor; + GenericFilesTab( + QWidget* parent, + QListWidget* list, QSplitter* splitter, TextEditor* editor); + virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; private: @@ -32,7 +37,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab(Ui::ModInfoDialog* ui); + TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -42,7 +47,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab(Ui::ModInfoDialog* ui); + IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From 90280399aaa1abb571c32d4ff063b4f8d5efc39b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:32:37 -0400 Subject: renamed widgets to their proper name --- src/modinfodialog.ui | 50 +++++++++---------- src/modinfodialogconflicts.cpp | 108 ++++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a26d15ab..c8ffd470 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -128,7 +128,7 @@ - + @@ -386,23 +386,23 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + Conflicts - + 1 - + General - + 0 @@ -423,11 +423,11 @@ Most mods do not have optional esps, so chances are good you are looking at an e 0 - + - + - + 0 @@ -444,7 +444,7 @@ text-align: left; - + QFrame::Sunken @@ -461,7 +461,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -502,11 +502,11 @@ text-align: left; - + - + - + 0 @@ -523,7 +523,7 @@ text-align: left; - + QFrame::Sunken @@ -537,7 +537,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -578,11 +578,11 @@ text-align: left; - + - + - + 0 @@ -599,7 +599,7 @@ text-align: left; - + QFrame::Sunken @@ -613,7 +613,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -696,7 +696,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -751,7 +751,7 @@ text-align: left; 0 - + Whether files that have no conflicts should be visible in the list @@ -764,7 +764,7 @@ text-align: left; - + Shows all mods overwriting or being overwritten by this mod @@ -777,7 +777,7 @@ text-align: left; - + Shows only the nearest conflicting mods, in order of priority @@ -802,7 +802,7 @@ text-align: left; 0 - + Filter diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 22306d0c..0107b8a1 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -158,7 +158,7 @@ bool ConflictsTab::feedFile(const QString&, const QString&) void ConflictsTab::saveState(Settings& s) { s.directInterface().setValue( - "mod_info_conflicts_tab", ui->tabConflictsTabs1->currentIndex()); + "mod_info_conflicts_tab", ui->tabConflictsTabs->currentIndex()); m_general.saveState(s); m_advanced.saveState(s); @@ -166,7 +166,7 @@ void ConflictsTab::saveState(Settings& s) void ConflictsTab::restoreState(const Settings& s) { - ui->tabConflictsTabs1->setCurrentIndex( + ui->tabConflictsTabs->setCurrentIndex( s.directInterface().value("mod_info_conflicts_tab", 0).toInt()); m_general.restoreState(s); @@ -475,40 +475,40 @@ GeneralConflictsTab::GeneralConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { - m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); - m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); - m_expanders.nonconflict.set(ui->noConflictExpander1, ui->noConflictTree1); + m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); + m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); + m_expanders.nonconflict.set(ui->noConflictExpander, ui->noConflictTree); QObject::connect( - ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, + ui->overwriteTree, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwriteActivated(item, col); }); QObject::connect( - ui->overwrittenTree1, &QTreeWidget::itemDoubleClicked, + ui->overwrittenTree, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwrittenActivated(item, col); }); QObject::connect( - ui->overwriteTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree1); }); + ui->overwriteTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); }); QObject::connect( - ui->overwrittenTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree1); }); + ui->overwrittenTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); }); QObject::connect( - ui->noConflictTree1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree1); }); + ui->noConflictTree, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); }); } void GeneralConflictsTab::clear() { - ui->overwriteTree1->clear(); - ui->overwrittenTree1->clear(); - ui->noConflictTree1->clear(); + ui->overwriteTree->clear(); + ui->overwrittenTree->clear(); + ui->noConflictTree->clear(); - ui->overwriteCount1->display(0); - ui->overwrittenCount1->display(0); - ui->noConflictCount1->display(0); + ui->overwriteCount->display(0); + ui->overwrittenCount->display(0); + ui->noConflictCount->display(0); } void GeneralConflictsTab::saveState(Settings& s) @@ -526,15 +526,15 @@ void GeneralConflictsTab::saveState(Settings& s) s.directInterface().setValue( "mod_info_conflicts_general_overwrite", - ui->overwriteTree1->header()->saveState()); + ui->overwriteTree->header()->saveState()); s.directInterface().setValue( "mod_info_conflicts_general_noconflict", - ui->noConflictTree1->header()->saveState()); + ui->noConflictTree->header()->saveState()); s.directInterface().setValue( "mod_info_conflicts_general_overwritten", - ui->overwrittenTree1->header()->saveState()); + ui->overwrittenTree->header()->saveState()); } void GeneralConflictsTab::restoreState(const Settings& s) @@ -554,13 +554,13 @@ void GeneralConflictsTab::restoreState(const Settings& s) m_expanders.nonconflict.toggle(noConflictExpanded); } - ui->overwriteTree1->header()->restoreState(s.directInterface() + ui->overwriteTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_overwrite").toByteArray()); - ui->noConflictTree1->header()->restoreState(s.directInterface() + ui->noConflictTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_noconflict").toByteArray()); - ui->overwrittenTree1->header()->restoreState(s.directInterface() + ui->overwrittenTree->header()->restoreState(s.directInterface() .value("mod_info_conflicts_general_overwritten").toByteArray()); } @@ -591,19 +591,19 @@ void GeneralConflictsTab::update() if (fileOrigin == m_origin->getID()) { if (!alternatives.empty()) { - ui->overwriteTree1->addTopLevelItem(createOverwriteItem( + ui->overwriteTree->addTopLevelItem(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - ui->noConflictTree1->addTopLevelItem(createNoConflictItem( + ui->noConflictTree->addTopLevelItem(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - ui->overwrittenTree1->addTopLevelItem(createOverwrittenItem( + ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -611,9 +611,9 @@ void GeneralConflictsTab::update() } } - ui->overwriteCount1->display(numOverwrite); - ui->overwrittenCount1->display(numOverwritten); - ui->noConflictCount1->display(numNonConflicting); + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); } QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( @@ -691,53 +691,53 @@ AdvancedConflictsTab::AdvancedConflictsTab( : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { // left-elide the overwrites column so that the nearest are visible - ui->conflictsAdvancedList1->setItemDelegateForColumn( - 0, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + ui->conflictsAdvancedList->setItemDelegateForColumn( + 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); // left-elide the file column to see filenames - ui->conflictsAdvancedList1->setItemDelegateForColumn( - 1, new ElideLeftDelegate(ui->conflictsAdvancedList1)); + ui->conflictsAdvancedList->setItemDelegateForColumn( + 1, new ElideLeftDelegate(ui->conflictsAdvancedList)); // don't elide the overwritten by column so that the nearest are visible QObject::connect( - ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, + ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, + ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, + ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); QObject::connect( - ui->conflictsAdvancedList1, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList1); }); + ui->conflictsAdvancedList, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); - m_filter.set(ui->conflictsAdvancedFilter1); + m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; } void AdvancedConflictsTab::clear() { - ui->conflictsAdvancedList1->clear(); + ui->conflictsAdvancedList->clear(); } void AdvancedConflictsTab::saveState(Settings& s) { s.directInterface().setValue( "mod_info_conflicts_advanced_list", - ui->conflictsAdvancedList1->header()->saveState()); + ui->conflictsAdvancedList->header()->saveState()); QByteArray result; QDataStream stream(&result, QIODevice::WriteOnly); stream - << ui->conflictsAdvancedShowNoConflict1->isChecked() - << ui->conflictsAdvancedShowAll1->isChecked() - << ui->conflictsAdvancedShowNearest1->isChecked(); + << ui->conflictsAdvancedShowNoConflict->isChecked() + << ui->conflictsAdvancedShowAll->isChecked() + << ui->conflictsAdvancedShowNearest->isChecked(); s.directInterface().setValue( "mod_info_conflicts_advanced_options", result); @@ -745,7 +745,7 @@ void AdvancedConflictsTab::saveState(Settings& s) void AdvancedConflictsTab::restoreState(const Settings& s) { - ui->conflictsAdvancedList1->header()->restoreState( + ui->conflictsAdvancedList->header()->restoreState( s.directInterface().value("mod_info_conflicts_advanced_list").toByteArray()); QDataStream stream(s.directInterface() @@ -758,9 +758,9 @@ void AdvancedConflictsTab::restoreState(const Settings& s) stream >> noConflictChecked >> showAllChecked >> showNearestChecked; if (stream.status() == QDataStream::Ok) { - ui->conflictsAdvancedShowNoConflict1->setChecked(noConflictChecked); - ui->conflictsAdvancedShowAll1->setChecked(showAllChecked); - ui->conflictsAdvancedShowNearest1->setChecked(showNearestChecked); + ui->conflictsAdvancedShowNoConflict->setChecked(noConflictChecked); + ui->conflictsAdvancedShowAll->setChecked(showAllChecked); + ui->conflictsAdvancedShowNearest->setChecked(showNearestChecked); } } @@ -790,7 +790,7 @@ void AdvancedConflictsTab::update() fileName, relativeName, alternatives); if (advancedItem) { - ui->conflictsAdvancedList1->addTopLevelItem(advancedItem); + ui->conflictsAdvancedList->addTopLevelItem(advancedItem); } } } @@ -813,7 +813,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( { const auto altOrigin = ds.getOriginByID(alt.first); - if (ui->conflictsAdvancedShowAll1->isChecked()) { + if (ui->conflictsAdvancedShowAll->isChecked()) { // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority @@ -868,7 +868,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // nearest mods, it's not worth checking for the primary origin because it // will always have a higher priority than the alternatives (or it wouldn't // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll1->isChecked()) { + if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); // if no mods overwrite this file, the primary origin is the same as this @@ -885,7 +885,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( bool hasAlts = !before.isEmpty() || !after.isEmpty(); - if (!ui->conflictsAdvancedShowNoConflict1->isChecked()) { + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { -- cgit v1.3.1 From a2e5f7d38214c9872a5c3a360065b5790bb29e2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:43:16 -0400 Subject: split ModInfoDialogTab --- src/CMakeLists.txt | 3 +++ src/modinfodialog.cpp | 36 ------------------------------------ src/modinfodialog.h | 33 +-------------------------------- src/modinfodialogconflicts.cpp | 1 + src/modinfodialogconflicts.h | 5 ++++- src/modinfodialogesps.h | 2 +- src/modinfodialogimages.h | 2 +- src/modinfodialogtab.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/modinfodialogtab.h | 42 ++++++++++++++++++++++++++++++++++++++++++ src/modinfodialogtextfiles.h | 2 +- 10 files changed, 90 insertions(+), 72 deletions(-) create mode 100644 src/modinfodialogtab.cpp create mode 100644 src/modinfodialogtab.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eead6541..adaddb77 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ SET(organizer_SRCS modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp + modinfodialogtab.cpp modinfodialogtextfiles.cpp modinfo.cpp modinfobackup.cpp @@ -161,6 +162,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h + modinfodialogtab.h modinfodialogtextfiles.h modinfo.h modinfobackup.h @@ -363,6 +365,7 @@ set(modinfo modinfodialogconflicts modinfodialogesps modinfodialogimages + modinfodialogtab modinfodialogtextfiles modinfoforeign modinfooverwrite diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index d52103b9..8231d366 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -66,42 +66,6 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; -bool ModInfoDialogTab::canClose() -{ - return true; -} - -void ModInfoDialogTab::saveState(Settings&) -{ - // no-op -} - -void ModInfoDialogTab::restoreState(const Settings& s) -{ - // no-op -} - -void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) -{ - // no-op -} - -void ModInfoDialogTab::update() -{ - // no-op -} - -void ModInfoDialogTab::emitOriginModified(int originID) -{ - emit originModified(originID); -} - -void ModInfoDialogTab::emitModOpen(QString name) -{ - emit modOpen(name); -} - - class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); public: diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 2a3ee2d8..8e7fed17 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -43,7 +43,6 @@ along with Mod Organizer. If not, see . #include #include - namespace Ui { class ModInfoDialog; } @@ -52,37 +51,7 @@ class QFileSystemModel; class QTreeView; class CategoryFactory; class TextEditor; - - -class ModInfoDialogTab : public QObject -{ - Q_OBJECT; - -public: - ModInfoDialogTab() = default; - ModInfoDialogTab(const ModInfoDialogTab&) = delete; - ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; - ModInfoDialogTab(ModInfoDialogTab&&) = default; - ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; - virtual ~ModInfoDialogTab() = default; - - virtual void clear() = 0; - virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; - virtual bool canClose(); - virtual void saveState(Settings& s); - virtual void restoreState(const Settings& s); - - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - virtual void update(); - -signals: - void originModified(int originID); - void modOpen(QString name); - -protected: - void emitOriginModified(int originID); - void emitModOpen(QString name); -}; +class ModInfoDialogTab; class ElideLeftDelegate : public QStyledItemDelegate diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 0107b8a1..1734f4f9 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -1,5 +1,6 @@ #include "modinfodialogconflicts.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include "utility.h" using namespace MOShared; diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 5843884a..4d4f279c 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -1,10 +1,13 @@ #ifndef MODINFODIALOGCONFLICTS_H #define MODINFODIALOGCONFLICTS_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" #include "expanderwidget.h" +#include "filterwidget.h" +#include "directoryentry.h" class ConflictsTab; +class OrganizerCore; class GeneralConflictsTab : public QObject { diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index 8da70377..d71ad3ff 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGESPS_H #define MODINFODIALOGESPS_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" class ESPItem; diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index a40904f7..708045c8 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGIMAGES_H #define MODINFODIALOGIMAGES_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" class ScalableImage : public QWidget { diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp new file mode 100644 index 00000000..b4734526 --- /dev/null +++ b/src/modinfodialogtab.cpp @@ -0,0 +1,36 @@ +#include "modinfodialogtab.h" + +bool ModInfoDialogTab::canClose() +{ + return true; +} + +void ModInfoDialogTab::saveState(Settings&) +{ + // no-op +} + +void ModInfoDialogTab::restoreState(const Settings& s) +{ + // no-op +} + +void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +{ + // no-op +} + +void ModInfoDialogTab::update() +{ + // no-op +} + +void ModInfoDialogTab::emitOriginModified(int originID) +{ + emit originModified(originID); +} + +void ModInfoDialogTab::emitModOpen(QString name) +{ + emit modOpen(name); +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h new file mode 100644 index 00000000..93a495f4 --- /dev/null +++ b/src/modinfodialogtab.h @@ -0,0 +1,42 @@ +#ifndef MODINFODIALOGTAB_H +#define MODINFODIALOGTAB_H + +#include "modinfo.h" +#include + +namespace MOShared { class FilesOrigin; } +namespace Ui { class ModInfoDialog; } + +class Settings; + +class ModInfoDialogTab : public QObject +{ + Q_OBJECT; + +public: + ModInfoDialogTab() = default; + ModInfoDialogTab(const ModInfoDialogTab&) = delete; + ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; + ModInfoDialogTab(ModInfoDialogTab&&) = default; + ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; + virtual ~ModInfoDialogTab() = default; + + virtual void clear() = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool canClose(); + virtual void saveState(Settings& s); + virtual void restoreState(const Settings& s); + + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void update(); + +signals: + void originModified(int originID); + void modOpen(QString name); + +protected: + void emitOriginModified(int originID); + void emitModOpen(QString name); +}; + +#endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d30e20d7..11691d64 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGTEXTFILES_H #define MODINFODIALOGTEXTFILES_H -#include "modinfodialog.h" +#include "modinfodialogtab.h" #include #include -- cgit v1.3.1 From a72573b385a941adf7d662b8df5c8e35309fdd45 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 22:42:14 -0400 Subject: split categories tab --- src/CMakeLists.txt | 3 + src/modinfodialog.cpp | 88 ++------------------------- src/modinfodialog.h | 7 --- src/modinfodialogcategories.cpp | 129 ++++++++++++++++++++++++++++++++++++++++ src/modinfodialogcategories.h | 29 +++++++++ src/modinfodialogconflicts.cpp | 5 -- src/modinfodialogconflicts.h | 1 - src/modinfodialogesps.cpp | 1 - src/modinfodialogtab.cpp | 6 ++ src/modinfodialogtab.h | 2 +- 10 files changed, 173 insertions(+), 98 deletions(-) create mode 100644 src/modinfodialogcategories.cpp create mode 100644 src/modinfodialogcategories.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index adaddb77..6ebfaddd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -54,6 +54,7 @@ SET(organizer_SRCS modlist.cpp modidlineedit.cpp modinfodialog.cpp + modinfodialogcategories.cpp modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp @@ -159,6 +160,7 @@ SET(organizer_HDRS modlist.h modidlineedit.h modinfodialog.h + modinfodialogcategories.h modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h @@ -362,6 +364,7 @@ set(modinfo modinfo modinfobackup modinfodialog + modinfodialogcategories modinfodialogconflicts modinfodialogesps modinfodialogimages diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 8231d366..3263a511 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -42,6 +42,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogimages.h" #include "modinfodialogesps.h" #include "modinfodialogconflicts.h" +#include "modinfodialogcategories.h" #include #include @@ -223,9 +224,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_IMAGES, false); ui->tabWidget->setTabEnabled(TAB_ESPS, false); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); - //ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); - refreshPrimaryCategoriesBox(); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); //ui->tabWidget->setTabEnabled(TAB_NOTES, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); @@ -249,12 +248,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); initFiletree(modInfo); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); - refreshPrimaryCategoriesBox(); } - - ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); @@ -279,12 +274,13 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ModInfoDialog::~ModInfoDialog() { m_ModInfo->setComments(ui->commentsEdit->text()); + //Avoid saving html stump if notes field is empty. if (ui->notesEdit->toPlainText().isEmpty()) m_ModInfo->setNotes(ui->notesEdit->toPlainText()); else m_ModInfo->setNotes(ui->notesEdit->toHtml()); - saveCategories(ui->categoriesTree->invisibleRootItem()); + delete ui->descriptionView->page(); delete ui->descriptionView; delete ui; @@ -301,6 +297,7 @@ std::vector> ModInfoDialog::createTabs() v.push_back(std::make_unique(this, ui)); v.push_back(std::make_unique( this, ui, *m_OrganizerCore, *m_PluginContainer)); + v.push_back(std::make_unique(this, ui)); return v; } @@ -437,38 +434,6 @@ void ModInfoDialog::refreshFiles() } } -void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) -{ - for (int i = 0; i < static_cast(factory.numCategories()); ++i) { - if (factory.getParentID(i) != rootLevel) { - continue; - } - int categoryID = factory.getCategoryID(i); - QTreeWidgetItem *newItem - = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, enabledCategories.find(categoryID) - != enabledCategories.end() - ? Qt::Checked - : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, categoryID); - if (factory.hasChildren(i)) { - addCategories(factory, enabledCategories, newItem, categoryID); - } - root->addChild(newItem); - } -} - - -void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) -{ - for (int i = 0; i < currentNode->childCount(); ++i) { - QTreeWidgetItem *childNode = currentNode->child(i); - m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); - saveCategories(childNode); - } -} - void ModInfoDialog::on_closeButton_clicked() { @@ -1011,49 +976,6 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } -void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem *item, int) -{ - QTreeWidgetItem *parent = item->parent(); - while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { - parent->setCheckState(0, Qt::Checked); - parent = parent->parent(); - } - refreshPrimaryCategoriesBox(); -} - - -void ModInfoDialog::addCheckedCategories(QTreeWidgetItem *tree) -{ - for (int i = 0; i < tree->childCount(); ++i) { - QTreeWidgetItem *child = tree->child(i); - if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); - addCheckedCategories(child); - } - } -} - - -void ModInfoDialog::refreshPrimaryCategoriesBox() -{ - ui->primaryCategoryBox->clear(); - int primaryCategory = m_ModInfo->getPrimaryCategory(); - addCheckedCategories(ui->categoriesTree->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); - break; - } - } -} - -void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) -{ - if (index != -1) { - m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); - } -} - void ModInfoDialog::on_refreshButton_clicked() { if (m_ModInfo->getNexusID() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 8e7fed17..384aafce 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -157,8 +157,6 @@ private: void refreshLists(); - void addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel); - void updateVersionColor(); void refreshNexusData(int modID); @@ -166,9 +164,6 @@ private: QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); - void saveCategories(QTreeWidgetItem *currentNode); - void addCheckedCategories(QTreeWidgetItem *tree); - void refreshPrimaryCategoriesBox(); int tabIndex(const QString &tabId); @@ -194,8 +189,6 @@ private slots: void on_versionEdit_editingFinished(); void on_customUrlLineEdit_editingFinished(); void on_tabWidget_currentChanged(int index); - void on_primaryCategoryBox_currentIndexChanged(int index); - void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp new file mode 100644 index 00000000..7df13aea --- /dev/null +++ b/src/modinfodialogcategories.cpp @@ -0,0 +1,129 @@ +#include "modinfodialogcategories.h" +#include "ui_modinfodialog.h" +#include "categories.h" +#include "modinfo.h" + +CategoriesTab::CategoriesTab(QWidget*, Ui::ModInfoDialog* ui) + : ui(ui) +{ + connect( + ui->categoriesTree, &QTreeWidget::itemChanged, + [&](auto* item, int col){ onCategoryChanged(item, col); }); + + connect( + ui->primaryCategoryBox, + static_cast(&QComboBox::currentIndexChanged), + [&](int index){ onPrimaryChanged(index); }); +} + +void CategoriesTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; +} + +void CategoriesTab::clear() +{ + ui->categoriesTree->clear(); + ui->primaryCategoryBox->clear(); +} + +void CategoriesTab::update() +{ + clear(); + + add( + CategoryFactory::instance(), m_mod->getCategories(), + ui->categoriesTree->invisibleRootItem(), 0); + + updatePrimary(); +} + +void CategoriesTab::add( + const CategoryFactory &factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel) +{ + for (int i=0; i(factory.numCategories()); ++i) { + if (factory.getParentID(i) != rootLevel) { + continue; + } + + int categoryID = factory.getCategoryID(i); + + QTreeWidgetItem *newItem + = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); + + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + + newItem->setCheckState(0, enabledCategories.find(categoryID) + != enabledCategories.end() + ? Qt::Checked + : Qt::Unchecked); + + newItem->setData(0, Qt::UserRole, categoryID); + + if (factory.hasChildren(i)) { + add(factory, enabledCategories, newItem, categoryID); + } + + root->addChild(newItem); + } +} + +void CategoriesTab::updatePrimary() +{ + ui->primaryCategoryBox->clear(); + + int primaryCategory = m_mod->getPrimaryCategory(); + + addChecked(ui->categoriesTree->invisibleRootItem()); + + for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { + if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { + ui->primaryCategoryBox->setCurrentIndex(i); + break; + } + } +} + +void CategoriesTab::addChecked(QTreeWidgetItem* tree) +{ + for (int i = 0; i < tree->childCount(); ++i) { + QTreeWidgetItem *child = tree->child(i); + if (child->checkState(0) == Qt::Checked) { + ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + addChecked(child); + } + } +} + +void CategoriesTab::save(QTreeWidgetItem* currentNode) +{ + for (int i = 0; i < currentNode->childCount(); ++i) { + QTreeWidgetItem *childNode = currentNode->child(i); + + m_mod->setCategory( + childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); + + save(childNode); + } +} + +void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) +{ + QTreeWidgetItem *parent = item->parent(); + + while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { + parent->setCheckState(0, Qt::Checked); + parent = parent->parent(); + } + + updatePrimary(); + save(ui->categoriesTree->invisibleRootItem()); +} + +void CategoriesTab::onPrimaryChanged(int index) +{ + if (index != -1) { + m_mod->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + } +} diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h new file mode 100644 index 00000000..c0644c7f --- /dev/null +++ b/src/modinfodialogcategories.h @@ -0,0 +1,29 @@ +#include "modinfodialogtab.h" + +class CategoryFactory; + +class CategoriesTab : public ModInfoDialogTab +{ +public: + CategoriesTab(QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void update() override; + +private: + Ui::ModInfoDialog* ui; + ModInfo::Ptr m_mod; + + void add( + const CategoryFactory& factory, const std::set& enabledCategories, + QTreeWidgetItem* root, int rootLevel); + + void updatePrimary(); + void addChecked(QTreeWidgetItem* tree); + + void save(QTreeWidgetItem* currentNode); + + void onCategoryChanged(QTreeWidgetItem* item, int col); + void onPrimaryChanged(int index); +}; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 1734f4f9..d2dca492 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -151,11 +151,6 @@ void ConflictsTab::clear() m_advanced.clear(); } -bool ConflictsTab::feedFile(const QString&, const QString&) -{ - return false; -} - void ConflictsTab::saveState(Settings& s) { s.directInterface().setValue( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 4d4f279c..212a196d 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -108,7 +108,6 @@ public: void update() override; void clear() override; - bool feedFile(const QString& rootPath, const QString& filename) override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 6c87b10d..cb3dcc84 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -4,7 +4,6 @@ using MOBase::reportError; - class ESP { public: diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index b4734526..6dcff0ac 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,5 +1,11 @@ #include "modinfodialogtab.h" +bool ModInfoDialogTab::feedFile(const QString&, const QString&) +{ + // no-op + return false; +} + bool ModInfoDialogTab::canClose() { return true; diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 93a495f4..ead29d10 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -22,7 +22,7 @@ public: virtual ~ModInfoDialogTab() = default; virtual void clear() = 0; - virtual bool feedFile(const QString& rootPath, const QString& filename) = 0; + virtual bool feedFile(const QString& rootPath, const QString& filename); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); -- cgit v1.3.1 From 8d1c121f648f2f6a8e0a5e2ad76cd245e318290d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 02:24:34 -0400 Subject: split nexus tab added OrganizerCore::loggedInAction() to execute a function only when logged in, replaces a bunch of copy/pasted stuff in mainwindow moved common variables in ModInfoDialogTab moved DescriptionPage to modinfodialognexus.h, renamed to NexusTabWebpage, deleted now empty descriptionpage.h --- src/CMakeLists.txt | 5 +- src/descriptionpage.h | 28 ----- src/mainwindow.cpp | 167 ++++++------------------ src/modinfodialog.cpp | 233 +++------------------------------- src/modinfodialog.h | 29 ----- src/modinfodialog.ui | 222 +++++++++++++++----------------- src/modinfodialogcategories.cpp | 43 +++---- src/modinfodialogcategories.h | 8 +- src/modinfodialogconflicts.cpp | 91 ++++++-------- src/modinfodialogconflicts.h | 17 +-- src/modinfodialogesps.cpp | 8 +- src/modinfodialogesps.h | 7 +- src/modinfodialogimages.cpp | 6 +- src/modinfodialogimages.h | 5 +- src/modinfodialognexus.cpp | 273 ++++++++++++++++++++++++++++++++++++++++ src/modinfodialognexus.h | 67 ++++++++++ src/modinfodialogtab.cpp | 39 +++++- src/modinfodialogtab.h | 25 +++- src/modinfodialogtextfiles.cpp | 26 ++-- src/modinfodialogtextfiles.h | 12 +- src/modinforegular.cpp | 20 ++- src/modinforegular.h | 1 + src/organizercore.cpp | 15 +++ src/organizercore.h | 1 + 24 files changed, 690 insertions(+), 658 deletions(-) delete mode 100644 src/descriptionpage.h create mode 100644 src/modinfodialognexus.cpp create mode 100644 src/modinfodialognexus.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ebfaddd..7dc39fd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ SET(organizer_SRCS modinfodialogconflicts.cpp modinfodialogesps.cpp modinfodialogimages.cpp + modinfodialognexus.cpp modinfodialogtab.cpp modinfodialogtextfiles.cpp modinfo.cpp @@ -164,6 +165,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogimages.h + modinfodialognexus.h modinfodialogtab.h modinfodialogtextfiles.h modinfo.h @@ -223,7 +225,6 @@ SET(organizer_HDRS instancemanager.h usvfsconnector.h eventfilter.h - descriptionpage.h moshortcut.h listdialog.h lcdnumber.h @@ -368,6 +369,7 @@ set(modinfo modinfodialogconflicts modinfodialogesps modinfodialogimages + modinfodialognexus modinfodialogtab modinfodialogtextfiles modinfoforeign @@ -424,7 +426,6 @@ set(utilities ) set(widgets - descriptionpage expanderwidget genericicondelegate filerenamer diff --git a/src/descriptionpage.h b/src/descriptionpage.h deleted file mode 100644 index f6158ee0..00000000 --- a/src/descriptionpage.h +++ /dev/null @@ -1,28 +0,0 @@ -#include - -#ifndef DESCRIPTIONPAGE_H -#define DESCRIPTIONPAGE_H - -class DescriptionPage : public QWebEnginePage -{ - Q_OBJECT - -public: - DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent){} - - bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame) - { - if (type == QWebEnginePage::NavigationTypeLinkClicked) - { - emit linkClicked(url); - return false; - } - return true; - } - -signals: - void linkClicked(const QUrl&); - -}; - -#endif //DESCRIPTIONPAGE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index da7c721d..8eb0838e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3073,66 +3073,34 @@ void MainWindow::backupMod_clicked() void MainWindow::resumeDownload(int downloadIndex) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this, downloadIndex] { m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([this, downloadIndex] () { - this->resumeDownload(downloadIndex); - }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); - } - } + }); } void MainWindow::endorseMod(ModInfo::Ptr mod) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this, mod] { mod->endorse(true); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(mod); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } + }); } void MainWindow::endorse_clicked() { QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); - } } - else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - MessageDialog::showMessage(tr("Endorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->endorseMod(modInfo); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - return; - } + + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(true); } - } - else { - endorseMod(ModInfo::getByIndex(m_ContextRow)); - } + }); } void MainWindow::dontendorse_clicked() @@ -3151,114 +3119,53 @@ void MainWindow::dontendorse_clicked() void MainWindow::unendorseMod(ModInfo::Ptr mod) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::getByIndex(m_ContextRow)->endorse(false); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(mod); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } + m_OrganizerCore.loggedInAction(this, [mod] { + mod->endorse(false); + }); } void MainWindow::unendorse_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - MessageDialog::showMessage(tr("Unendorsing multiple mods will take a while. Please wait..."), this); - for (QModelIndex idx : selection->selectedRows()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->unendorseMod(modInfo); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - return; - } } - } - else { - unendorseMod(ModInfo::getByIndex(m_ContextRow)); - } + + for (QModelIndex idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->endorse(false); + } + }); } void MainWindow::trackMod(ModInfo::Ptr mod, bool doTrack) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - ModInfo::getByIndex(m_ContextRow)->track(doTrack); - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(mod, doTrack); }); - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } - } + m_OrganizerCore.loggedInAction(this, [mod, doTrack] { + mod->track(doTrack); + }); } void MainWindow::track_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - for (auto idx : selection->selectedRows()) { - auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, true); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(true); } - } else { - trackMod(ModInfo::getByIndex(m_ContextRow), true); - } + }); } void MainWindow::untrack_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - if (NexusInterface::instance(&m_PluginContainer)->getAccessManager()->validated()) { - for (auto idx : selection->selectedRows()) { - ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); - } - } else { - QString apiKey; - if (m_OrganizerCore.settings().getNexusApiKey(apiKey)) { - for (auto idx : selection->selectedRows()) { - auto modInfo = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.doAfterLogin([&]() { this->trackMod(modInfo, false); }); - } - NexusInterface::instance(&m_PluginContainer)->getAccessManager()->apiCheck(apiKey); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to track"), this); - } + m_OrganizerCore.loggedInAction(this, [this] { + QItemSelectionModel *selection = ui->modList->selectionModel(); + for (auto idx : selection->selectedRows()) { + ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->track(false); } - } else { - trackMod(ModInfo::getByIndex(m_ContextRow), false); - } + }); } void MainWindow::validationFailed(const QString &error) @@ -3316,13 +3223,11 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer, this); - connect(&dialog, SIGNAL(linkActivated(QString)), this, SLOT(linkClicked(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(modOpenNext(int)), this, SLOT(modOpenNext(int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3263a511..30110d14 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -43,6 +43,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogesps.h" #include "modinfodialogconflicts.h" #include "modinfodialogcategories.h" +#include "modinfodialognexus.h" #include #include @@ -145,7 +146,6 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa 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_RequestStarted(false), m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), @@ -173,38 +173,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); - m_Settings = new QSettings(metaFileName, QSettings::IniFormat); - - QLineEdit *modIDEdit = findChild("modIDEdit"); - ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); - ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); - - connect(ui->modIDEdit, SIGNAL(linkClicked(QString)), this, SLOT(linkClicked(QString))); - - QString gameName = modInfo->getGameName(); - ui->sourceGameEdit->addItem(organizerCore->managedGame()->gameName(), organizerCore->managedGame()->gameShortName()); - if (organizerCore->managedGame()->validShortNames().size() == 0) { - ui->sourceGameEdit->setDisabled(true); - } else { - for (auto game : pluginContainer->plugins()) { - for (QString gameName : organizerCore->managedGame()->validShortNames()) { - if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { - ui->sourceGameEdit->addItem(game->gameName(), game->gameShortName()); - break; - } - } - } - } - ui->sourceGameEdit->setCurrentIndex(ui->sourceGameEdit->findData(gameName)); - ui->commentsEdit->setText(modInfo->comments()); ui->notesEdit->setText(modInfo->notes()); - ui->descriptionView->setPage(new DescriptionPage()); - - connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); - connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); //TODO: No easy way to delegate links //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); @@ -246,14 +217,12 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_IMAGES, true); ui->tabWidget->setTabEnabled(TAB_ESPS, true); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); + ui->tabWidget->setTabEnabled(TAB_NEXUS, true); initFiletree(modInfo); } - ui->endorseBtn->setVisible(Settings::instance().endorsementIntegration()); - ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || - (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); - // activate first enabled tab for (int i = 0; i < ui->tabWidget->count(); ++i) { if (ui->tabWidget->isTabEnabled(i)) { @@ -262,10 +231,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - if (ui->tabWidget->currentIndex() == TAB_NEXUS) { - activateNexusTab(); - } - for (auto& tab : m_tabs) { tab->setMod(m_ModInfo, m_Origin); } @@ -281,27 +246,28 @@ ModInfoDialog::~ModInfoDialog() else m_ModInfo->setNotes(ui->notesEdit->toHtml()); - delete ui->descriptionView->page(); - delete ui->descriptionView; delete ui; - delete m_Settings; } -std::vector> ModInfoDialog::createTabs() +template +std::vector> createTabsImpl( + OrganizerCore& oc, PluginContainer& plugin, + ModInfoDialog* self, Ui::ModInfoDialog* ui) { std::vector> v; - - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique(this, ui)); - v.push_back(std::make_unique( - this, ui, *m_OrganizerCore, *m_PluginContainer)); - v.push_back(std::make_unique(this, ui)); + (v.push_back(std::make_unique(oc, plugin, self, ui)), ...); return v; } +std::vector> ModInfoDialog::createTabs() +{ + return createTabsImpl< + TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, + ConflictsTab, CategoriesTab, NexusTab>( + *m_OrganizerCore, *m_PluginContainer, this, ui); +} + int ModInfoDialog::exec() { refreshLists(); @@ -434,7 +400,6 @@ void ModInfoDialog::refreshFiles() } } - void ModInfoDialog::on_closeButton_clicked() { for (auto& tab : m_tabs) { @@ -446,19 +411,6 @@ void ModInfoDialog::on_closeButton_clicked() close(); } - - -QString ModInfoDialog::getModVersion() const -{ - return m_Settings->value("version", "").toString(); -} - - -const int ModInfoDialog::getModID() const -{ - return m_Settings->value("modid", 0).toInt(); -} - void ModInfoDialog::openTab(int tab) { QTabWidget *tabWidget = findChild("tabWidget"); @@ -467,40 +419,6 @@ void ModInfoDialog::openTab(int tab) } } -void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) -{ - emit linkActivated(link); -} - -void ModInfoDialog::linkClicked(const QUrl &url) -{ - //Ideally we'd ask the mod for the game and the web service then pass the game - //and URL to the web service - if (NexusInterface::instance(m_PluginContainer)->isURLGameRelated(url)) { - emit linkActivated(url.toString()); - } else { - shell::OpenLink(url); - } -} - -void ModInfoDialog::linkClicked(QString url) -{ - emit linkActivated(url); -} - - -void ModInfoDialog::refreshNexusData(int modID) -{ - if ((!m_RequestStarted) && (modID > 0)) { - m_RequestStarted = true; - - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); - } -} - - QString ModInfoDialog::getFileCategory(int categoryID) { switch (categoryID) { @@ -514,111 +432,8 @@ QString ModInfoDialog::getFileCategory(int categoryID) } } - -void ModInfoDialog::updateVersionColor() -{ -// QPalette versionColor; - if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { - ui->versionEdit->setStyleSheet("color: red"); -// versionColor.setColor(QPalette::Text, Qt::red); - ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); - } else { - ui->versionEdit->setStyleSheet("color: green"); -// versionColor.setColor(QPalette::Text, Qt::green); - ui->versionEdit->setToolTip(tr("No update available")); - } -// ui->versionEdit->setPalette(versionColor); -} - - -void ModInfoDialog::modDetailsUpdated(bool success) -{ - QString nexusDescription = m_ModInfo->getNexusDescription(); - QString descriptionAsHTML = "" - "" - "%1" - ""; - - if (!nexusDescription.isEmpty()) { - descriptionAsHTML = descriptionAsHTML.arg(BBCode::convertToHTML(nexusDescription)); - } else { - descriptionAsHTML = descriptionAsHTML.arg(tr("

Uh oh!

Sorry, there is no description available for this mod. :(

")); - } - - ui->descriptionView->page()->setHtml(descriptionAsHTML); - - updateVersionColor(); -} - - -void ModInfoDialog::activateNexusTab() -{ - QLineEdit *modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - if (modID > 0) { - QString nexusLink = NexusInterface::instance(m_PluginContainer)->getModURL(modID, m_ModInfo->getGameName()); - QLabel *visitNexusLabel = findChild("visitNexusLabel"); - visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); - visitNexusLabel->setToolTip(nexusLink); - m_ModInfo->setURL(nexusLink); - - if (m_ModInfo->getNexusDescription().isEmpty() || QDateTime::currentDateTimeUtc() >= m_ModInfo->getLastNexusQuery().addDays(1)) { - refreshNexusData(modID); - } else { - modDetailsUpdated(true); - } - } else - modDetailsUpdated(true); - QLineEdit *versionEdit = findChild("versionEdit"); - QString currentVersion = m_Settings->value("version", "???").toString(); - versionEdit->setText(currentVersion); - ui->customUrlLineEdit->setText(m_ModInfo->getURL()); -} - - void ModInfoDialog::on_tabWidget_currentChanged(int index) { - if (index == TAB_NEXUS || m_RealTabPos[index] == TAB_NEXUS) { - activateNexusTab(); - } -} - - -void ModInfoDialog::on_modIDEdit_editingFinished() -{ - int oldID = m_Settings->value("modid", 0).toInt(); - int modID = ui->modIDEdit->text().toInt(); - if (oldID != modID){ - m_ModInfo->setNexusID(modID); - - ui->descriptionView->page()->setHtml(""); - if (modID != 0) { - m_RequestStarted = false; - refreshNexusData(modID); - } - } -} - -void ModInfoDialog::on_sourceGameEdit_currentIndexChanged(int) -{ - for (auto game : m_PluginContainer->plugins()) { - if (game->gameName() == ui->sourceGameEdit->currentText()) { - m_ModInfo->setGameName(game->gameShortName()); - return; - } - } -} - -void ModInfoDialog::on_versionEdit_editingFinished() -{ - VersionInfo version(ui->versionEdit->text()); - m_ModInfo->setVersion(version); - updateVersionColor(); -} - -void ModInfoDialog::on_customUrlLineEdit_editingFinished() -{ - m_ModInfo->setURL(ui->customUrlLineEdit->text()); } bool ModInfoDialog::recursiveDelete(const QModelIndex &index) @@ -975,22 +790,6 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); } - -void ModInfoDialog::on_refreshButton_clicked() -{ - if (m_ModInfo->getNexusID() > 0) { - QLineEdit *modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - refreshNexusData(modID); - } else - qInfo("Mod has no valid Nexus ID, info can't be updated."); -} - -void ModInfoDialog::on_endorseBtn_clicked() -{ - emit endorseMod(m_ModInfo); -} - void ModInfoDialog::on_nextButton_clicked() { int currentTab = ui->tabWidget->currentIndex(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 384aafce..7b96d21a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -138,18 +138,11 @@ public: void restoreState(const Settings& s); signals: - - void linkActivated(const QString &link); void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); void modOpenNext(int tab=-1); void modOpenPrev(int tab=-1); void originModified(int originID); - void endorseMod(ModInfo::Ptr nexusID); - -public slots: - - void modDetailsUpdated(bool success); private: @@ -157,10 +150,6 @@ private: void refreshLists(); - void updateVersionColor(); - - void refreshNexusData(int modID); - void activateNexusTab(); QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); @@ -168,9 +157,6 @@ private: int tabIndex(const QString &tabId); private slots: - void linkClicked(const QUrl &url); - void linkClicked(QString url); - void delete_activated(); void createDirectoryTriggered(); @@ -183,20 +169,10 @@ private slots: void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); - void on_visitNexusLabel_linkActivated(const QString &link); - void on_modIDEdit_editingFinished(); - void on_sourceGameEdit_currentIndexChanged(int); - void on_versionEdit_editingFinished(); - void on_customUrlLineEdit_editingFinished(); void on_tabWidget_currentChanged(int index); void on_fileTree_customContextMenuRequested(const QPoint &pos); - void on_refreshButton_clicked(); - - void on_endorseBtn_clicked(); - void on_nextButton_clicked(); - void on_prevButton_clicked(); private: @@ -217,11 +193,6 @@ private: QTreeView *m_FileTree; QModelIndexList m_FileSelection; - QSettings *m_Settings; - - std::set m_RequestIDs; - bool m_RequestStarted; - QAction *m_NewFolderAction; QAction *m_OpenAction; QAction *m_PreviewAction; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c8ffd470..29a7400f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -829,7 +829,7 @@ text-align: left; - + true @@ -853,13 +853,13 @@ text-align: left; - +
- + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png @@ -870,6 +870,60 @@ text-align: left; + + + + + 0 + 0 + + + + Refresh + + + Refresh all information from Nexus. + + + Refresh + + + + :/MO/gui/refresh:/MO/gui/refresh + + + Qt::ToolButtonTextBesideIcon + + + + + + + Open in Browser + + + + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png + + + Qt::ToolButtonTextBesideIcon + + + + + + + Endorse + + + + :/MO/gui/icon_favorite:/MO/gui/icon_favorite + + + Qt::ToolButtonTextBesideIcon + + + @@ -878,7 +932,7 @@ text-align: left; - + Mod ID for this mod on Nexus. @@ -891,22 +945,6 @@ p, li { white-space: pre-wrap; } - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - @@ -915,7 +953,7 @@ p, li { white-space: pre-wrap; } - + Source game for this mod. @@ -925,20 +963,7 @@ p, li { white-space: pre-wrap; } - - - Qt::Horizontal - - - - 40 - 20 - - - - - - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -952,7 +977,7 @@ p, li { white-space: pre-wrap; } - + 150 @@ -967,102 +992,61 @@ p, li { white-space: pre-wrap; } - - - - - - 0 - 0 - - - - Refresh - - - Refresh all information from Nexus. - - - - - - - :/MO/gui/refresh:/MO/gui/refresh - - - - - - - Description - - - - - - - - - - 0 - 0 - - - - - 0 - 200 - + + + QFrame::StyledPanel - - - about:blank - + + QFrame::Sunken + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + about:blank + + + + + - - - - - - - 1 - 0 - - - - - - - false - - - - - - - Endorse - - - - :/MO/gui/icon_favorite:/MO/gui/icon_favorite - - - - - - Web page URL (only used if invalid Nexus ID) : + URL - + diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 7df13aea..69c7c6b5 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -3,28 +3,25 @@ #include "categories.h" #include "modinfo.h" -CategoriesTab::CategoriesTab(QWidget*, Ui::ModInfoDialog* ui) - : ui(ui) +CategoriesTab::CategoriesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) { connect( - ui->categoriesTree, &QTreeWidget::itemChanged, + ui->categories, &QTreeWidget::itemChanged, [&](auto* item, int col){ onCategoryChanged(item, col); }); connect( - ui->primaryCategoryBox, + ui->primaryCategories, static_cast(&QComboBox::currentIndexChanged), [&](int index){ onPrimaryChanged(index); }); } -void CategoriesTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; -} - void CategoriesTab::clear() { - ui->categoriesTree->clear(); - ui->primaryCategoryBox->clear(); + ui->categories->clear(); + ui->primaryCategories->clear(); } void CategoriesTab::update() @@ -32,8 +29,8 @@ void CategoriesTab::update() clear(); add( - CategoryFactory::instance(), m_mod->getCategories(), - ui->categoriesTree->invisibleRootItem(), 0); + CategoryFactory::instance(), mod()->getCategories(), + ui->categories->invisibleRootItem(), 0); updatePrimary(); } @@ -71,15 +68,15 @@ void CategoriesTab::add( void CategoriesTab::updatePrimary() { - ui->primaryCategoryBox->clear(); + ui->primaryCategories->clear(); - int primaryCategory = m_mod->getPrimaryCategory(); + int primaryCategory = mod()->getPrimaryCategory(); - addChecked(ui->categoriesTree->invisibleRootItem()); + addChecked(ui->categories->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); + for (int i = 0; i < ui->primaryCategories->count(); ++i) { + if (ui->primaryCategories->itemData(i).toInt() == primaryCategory) { + ui->primaryCategories->setCurrentIndex(i); break; } } @@ -90,7 +87,7 @@ void CategoriesTab::addChecked(QTreeWidgetItem* tree) for (int i = 0; i < tree->childCount(); ++i) { QTreeWidgetItem *child = tree->child(i); if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + ui->primaryCategories->addItem(child->text(0), child->data(0, Qt::UserRole)); addChecked(child); } } @@ -101,7 +98,7 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) for (int i = 0; i < currentNode->childCount(); ++i) { QTreeWidgetItem *childNode = currentNode->child(i); - m_mod->setCategory( + mod()->setCategory( childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); save(childNode); @@ -118,12 +115,12 @@ void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) } updatePrimary(); - save(ui->categoriesTree->invisibleRootItem()); + save(ui->categories->invisibleRootItem()); } void CategoriesTab::onPrimaryChanged(int index) { if (index != -1) { - m_mod->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + mod()->setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); } } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index c0644c7f..76426a5d 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -5,16 +5,14 @@ class CategoryFactory; class CategoriesTab : public ModInfoDialogTab { public: - CategoriesTab(QWidget* parent, Ui::ModInfoDialog* ui); + CategoriesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; void update() override; private: - Ui::ModInfoDialog* ui; - ModInfo::Ptr m_mod; - void add( const CategoryFactory& factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d2dca492..4176ac3e 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -116,10 +116,10 @@ public: ConflictsTab::ConflictsTab( - QWidget* parent, Ui::ModInfoDialog* ui, - OrganizerCore& oc, PluginContainer& plugin) : - m_parent(parent), ui(ui), m_core(oc), m_plugin(plugin), - m_origin(nullptr), m_general(this, ui, oc), m_advanced(this, ui, oc) + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) : + ModInfoDialogTab(oc, plugin, parent, ui), + m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( &m_general, &GeneralConflictsTab::modOpen, @@ -130,15 +130,6 @@ ConflictsTab::ConflictsTab( [&](const QString& name){ emitModOpen(name); }); } -void ConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; - - m_general.setMod(mod, origin); - m_advanced.setMod(mod, origin); -} - void ConflictsTab::update() { m_general.update(); @@ -186,7 +177,7 @@ void ConflictsTab::changeItemsVisibility( flags |= FileRenamer::MULTIPLE; } - FileRenamer renamer(m_parent, flags); + FileRenamer renamer(parentWidget(), flags); for (const auto* item : items) { if (stop) { @@ -242,8 +233,8 @@ void ConflictsTab::changeItemsVisibility( if (changed) { qDebug().nospace() << "triggering refresh"; - if (m_origin) { - emit originModified(m_origin->getID()); + if (origin()) { + emit originModified(origin()->getID()); } update(); @@ -256,7 +247,7 @@ void ConflictsTab::openItems(const QList& items) // in case this changes for (auto* item : items) { if (auto* ci=dynamic_cast(item)) { - m_core.executeFileVirtualized(m_parent, ci->fileName()); + core().executeFileVirtualized(parentWidget(), ci->fileName()); } } } @@ -267,7 +258,7 @@ void ConflictsTab::previewItems(const QList& items) // in case this changes for (auto* item : items) { if (auto* ci=dynamic_cast(item)) { - m_core.previewFileWithAlternatives(m_parent, ci->fileName()); + core().previewFileWithAlternatives(parentWidget(), ci->fileName()); } } } @@ -355,7 +346,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( enableHide = ci->canHide(); enableUnhide = ci->canUnhide(); enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(m_plugin); + enablePreview = ci->canPreview(plugin()); enableGoto = ci->hasAlts(); } else { @@ -394,21 +385,21 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( Actions actions; - actions.hide = new QAction(tr("Hide"), m_parent); + actions.hide = new QAction(tr("Hide"), parentWidget()); actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), m_parent); + actions.unhide = new QAction(tr("Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("Open/Execute"), m_parent); + actions.open = new QAction(tr("Open/Execute"), parentWidget()); actions.open->setEnabled(enableOpen); - actions.preview = new QAction(tr("Preview"), m_parent); + actions.preview = new QAction(tr("Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); - actions.gotoMenu = new QMenu(tr("Go to..."), m_parent); + actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); if (enableGoto) { @@ -421,7 +412,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( std::vector ConflictsTab::createGotoActions( const QList& selection) { - if (!m_origin || selection.size() != 1) { + if (!origin() || selection.size() != 1) { return {}; } @@ -430,26 +421,26 @@ std::vector ConflictsTab::createGotoActions( return {}; } - auto file = m_origin->findFile(item->fileIndex()); + auto file = origin()->findFile(item->fileIndex()); if (!file) { return {}; } std::vector mods; - const auto& ds = *m_core.directoryStructure(); + const auto& ds = *core().directoryStructure(); // add all alternatives for (const auto& alt : file->getAlternatives()) { const auto& o = ds.getOriginByID(alt.first); - if (o.getID() != m_origin->getID()) { + if (o.getID() != origin()->getID()) { mods.push_back(ToQString(o.getName())); } } // add the real origin if different from this mod const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin()); - if (realOrigin.getID() != m_origin->getID()) { + if (realOrigin.getID() != origin()->getID()) { mods.push_back(ToQString(realOrigin.getName())); } @@ -460,7 +451,7 @@ std::vector ConflictsTab::createGotoActions( std::vector actions; for (const auto& name : mods) { - actions.push_back(new QAction(name, m_parent)); + actions.push_back(new QAction(name, parentWidget())); } return actions; @@ -469,7 +460,7 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) + : m_tab(tab), ui(ui), m_core(oc) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); @@ -560,12 +551,6 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::setMod(ModInfo::Ptr mod, FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; -} - void GeneralConflictsTab::update() { clear(); @@ -574,10 +559,10 @@ void GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; - if (m_origin != nullptr) { - const auto rootPath = m_mod->absolutePath(); + if (m_tab->origin() != nullptr) { + const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_origin->getFiles()) { + for (const auto& file : m_tab->origin()->getFiles()) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -585,7 +570,7 @@ void GeneralConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - if (fileOrigin == m_origin->getID()) { + if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { ui->overwriteTree->addTopLevelItem(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); @@ -684,7 +669,7 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) AdvancedConflictsTab::AdvancedConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) + : m_tab(tab), ui(ui), m_core(oc) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( @@ -760,20 +745,14 @@ void AdvancedConflictsTab::restoreState(const Settings& s) } } -void AdvancedConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) -{ - m_mod = mod; - m_origin = origin; -} - void AdvancedConflictsTab::update() { clear(); - if (m_origin != nullptr) { - const auto rootPath = m_mod->absolutePath(); + if (m_tab->origin() != nullptr) { + const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_origin->getFiles()) { + for (const auto& file : m_tab->origin()->getFiles()) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -813,14 +792,14 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority - if (altOrigin.getPriority() < m_origin->getPriority()) { + if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // add all the mods having a lower priority than this one if (!before.isEmpty()) { before += ", "; } before += ToQString(altOrigin.getName()); - } else if (altOrigin.getPriority() > m_origin->getPriority()) { + } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // add all the mods having a higher priority than this one if (!after.isEmpty()) { after += ", "; @@ -832,7 +811,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // keep track of the nearest mods that come before and after this one // in terms of priority - if (altOrigin.getPriority() < m_origin->getPriority()) { + if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // the alternative has a lower priority than this mod if (altOrigin.getPriority() > beforePrio) { @@ -843,7 +822,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( } } - if (altOrigin.getPriority() > m_origin->getPriority()) { + if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // the alternative has a higher priority than this mod if (altOrigin.getPriority() < afterPrio) { @@ -869,7 +848,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // if no mods overwrite this file, the primary origin is the same as this // mod, so ignore that - if (realOrigin.getID() != m_origin->getID()) { + if (realOrigin.getID() != m_tab->origin()->getID()) { if (!after.isEmpty()) { after += ", "; } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 212a196d..20362575 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -21,7 +21,6 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); signals: @@ -36,8 +35,6 @@ private: ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; Expanders m_expanders; QTreeWidgetItem* createOverwriteItem( @@ -74,7 +71,6 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); signals: @@ -84,8 +80,6 @@ private: ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; FilterWidget m_filter; QTreeWidgetItem* createItem( @@ -101,10 +95,9 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( - QWidget* parent, Ui::ModInfoDialog* ui, - OrganizerCore& oc, PluginContainer& plugin); + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; void update() override; void clear() override; @@ -136,12 +129,6 @@ private: } }; - QWidget* m_parent; - Ui::ModInfoDialog* ui; - OrganizerCore& m_core; - PluginContainer& m_plugin; - ModInfo::Ptr m_mod; - MOShared::FilesOrigin* m_origin; GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index cb3dcc84..ea7eb3b0 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -122,8 +122,10 @@ private: }; -ESPsTab::ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui) - : m_parent(parent), ui(ui) +ESPsTab::ESPsTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); @@ -187,7 +189,7 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - m_parent, + parentWidget(), QObject::tr("File Exists"), QObject::tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, file.fileName(), &okClicked); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index d71ad3ff..e1a7a4f7 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -10,15 +10,14 @@ class ESPsTab : public ModInfoDialogTab Q_OBJECT; public: - ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui); + ESPsTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: - QWidget* m_parent; - Ui::ModInfoDialog* ui; - void onActivate(); void onDeactivate(); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 752c8d0c..1c7dcc1f 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -80,8 +80,10 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) } -ImagesTab::ImagesTab(QWidget*, Ui::ModInfoDialog* ui) - : ui(ui), m_image(new ScalableImage) +ImagesTab::ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); ui->imagesThumbnails->setLayout(new QVBoxLayout); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 708045c8..7853935a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -34,13 +34,14 @@ class ImagesTab : public ModInfoDialogTab Q_OBJECT; public: - ImagesTab(QWidget* parent, Ui::ModInfoDialog* ui); + ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: - Ui::ModInfoDialog* ui; ScalableImage* m_image; void add(const QString& fullPath); diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp new file mode 100644 index 00000000..753d43de --- /dev/null +++ b/src/modinfodialognexus.cpp @@ -0,0 +1,273 @@ +#include "modinfodialognexus.h" +#include "ui_modinfodialog.h" +#include "settings.h" +#include "organizercore.h" +#include "iplugingame.h" +#include "bbcode.h" +#include +#include + +NexusTab::NexusTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_requestStarted(false) +{ + ui->modID->setValidator(new QIntValidator(ui->modID)); + ui->endorse->setVisible(core().settings().endorsementIntegration()); + + connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); + connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); + connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); + connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); + connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); + connect(ui->refresh, &QToolButton::clicked, [&]{ updateWebpage(); }); + + connect( + ui->sourceGame, + static_cast(&QComboBox::currentIndexChanged), + [&]{ onSourceGameChanged(); }); +} + +NexusTab::~NexusTab() +{ + cleanup(); +} + +void NexusTab::cleanup() +{ + if (m_modConnection) { + disconnect(m_modConnection); + m_modConnection = {}; + } +} + +void NexusTab::clear() +{ + ui->modID->clear(); + ui->sourceGame->clear(); + ui->version->clear(); + ui->browser->setPage(new NexusTabWebpage(ui->browser)); + ui->url->clear(); +} + +void NexusTab::update() +{ + clear(); + + ui->modID->setText(QString("%1").arg(mod()->getNexusID())); + + QString gameName = mod()->getGameName(); + ui->sourceGame->addItem( + core().managedGame()->gameName(), + core().managedGame()->gameShortName()); + + if (core().managedGame()->validShortNames().size() == 0) { + ui->sourceGame->setDisabled(true); + } else { + for (auto game : plugin().plugins()) { + for (QString gameName : core().managedGame()->validShortNames()) { + if (game->gameShortName().compare(gameName, Qt::CaseInsensitive) == 0) { + ui->sourceGame->addItem(game->gameName(), game->gameShortName()); + break; + } + } + } + } + + ui->sourceGame->setCurrentIndex(ui->sourceGame->findData(gameName)); + + auto* page = new NexusTabWebpage(ui->browser); + ui->browser->setPage(page); + + connect( + page, &NexusTabWebpage::linkClicked, + [&](const QUrl& url){ MOBase::shell::OpenLink(url); }); + + ui->endorse->setEnabled( + (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || + (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); + + updateWebpage(); +} + +void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + cleanup(); + + ModInfoDialogTab::setMod(mod, origin); + + m_modConnection = connect( + mod.data(), &ModInfo::modDetailsUpdated, [&]{ onModChanged(); }); +} + +void NexusTab::updateVersionColor() +{ + if (mod()->getVersion() != mod()->getNewestVersion()) { + ui->version->setStyleSheet("color: red"); + ui->version->setToolTip(tr("Current Version: %1").arg( + mod()->getNewestVersion().canonicalString())); + } else { + ui->version->setStyleSheet("color: green"); + ui->version->setToolTip(tr("No update available")); + } +} + +void NexusTab::updateWebpage() +{ + const int modID = mod()->getNexusID(); + + if (modID > 0) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + ui->openInBrowser->setToolTip(nexusLink); + mod()->setURL(nexusLink); + refreshData(modID); + } else { + onModChanged(); + } + + ui->version->setText(mod()->getVersion().displayString()); + ui->url->setText(mod()->getURL()); +} + +void NexusTab::onModChanged() +{ + m_requestStarted = false; + + const QString nexusDescription = mod()->getNexusDescription(); + + QString descriptionAsHTML = R"( + + + + + %1 +)"; + + if (nexusDescription.isEmpty()) { + descriptionAsHTML = descriptionAsHTML.arg(tr( + "
" + "

Uh oh!

" + "

Sorry, there is no description available for this mod. :(

" + "
")); + + } else { + descriptionAsHTML = descriptionAsHTML.arg( + BBCode::convertToHTML(nexusDescription)); + } + + ui->browser->page()->setHtml(descriptionAsHTML); + updateVersionColor(); +} + +void NexusTab::onModIDChanged() +{ + const int oldID = mod()->getNexusID(); + const int newID = ui->modID->text().toInt(); + + if (oldID != newID){ + mod()->setNexusID(newID); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + + ui->browser->page()->setHtml(""); + + if (newID != 0) { + refreshData(newID); + } + } +} + +void NexusTab::onSourceGameChanged() +{ + for (auto game : plugin().plugins()) { + if (game->gameName() == ui->sourceGame->currentText()) { + mod()->setGameName(game->gameShortName()); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + refreshData(mod()->getNexusID()); + return; + } + } +} + +void NexusTab::onVersionChanged() +{ + MOBase::VersionInfo version(ui->version->text()); + mod()->setVersion(version); + updateVersionColor(); +} + +void NexusTab::onUrlChanged() +{ + mod()->setURL(ui->url->text()); + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); +} + +void NexusTab::onOpenLink() +{ + const int modID = mod()->getNexusID(); + + if (modID > 0) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + MOBase::shell::OpenLink(QUrl(nexusLink)); + } +} + +void NexusTab::onRefreshBrowser() +{ + const auto modID = mod()->getNexusID(); + + if (modID > 0) { + refreshData(modID); + } else + qInfo("Mod has no valid Nexus ID, info can't be updated."); +} + +void NexusTab::onEndorse() +{ + core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); +} + +void NexusTab::refreshData(int modID) +{ + if (tryRefreshData(modID)) { + m_requestStarted = true; + } else { + onModChanged(); + } + + //MessageDialog::showMessage(tr("Info requested, please wait"), this); +} + +bool NexusTab::tryRefreshData(int modID) +{ + if (modID <= 0) { + qDebug() << "NexusTab: can't refresh, no mod id"; + return false; + } + + if (m_requestStarted) { + qDebug() << "NexusTab: a refresh request is already running"; + return false; + } + + if (!mod()->updateNXMInfo()) { + qDebug() << "NexusTab: nexus description does not need an update"; + return false; + } + + return true; +} diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h new file mode 100644 index 00000000..2e328c6d --- /dev/null +++ b/src/modinfodialognexus.h @@ -0,0 +1,67 @@ +#ifndef MODINFODIALOGNEXUS_H +#define MODINFODIALOGNEXUS_H + +#include "modinfodialogtab.h" + +class NexusTabWebpage : public QWebEnginePage +{ + Q_OBJECT + +public: + NexusTabWebpage(QObject* parent = 0) + : QWebEnginePage(parent) + { + } + + bool acceptNavigationRequest( + const QUrl & url, QWebEnginePage::NavigationType type, bool) override + { + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { + emit linkClicked(url); + return false; + } + + return true; + } + +signals: + void linkClicked(const QUrl&); +}; + + +class NexusTab : public ModInfoDialogTab +{ +public: + NexusTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + ~NexusTab(); + + void clear() override; + void update() override; + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + +private: + QMetaObject::Connection m_modConnection; + bool m_requestStarted; + + void cleanup(); + void updateVersionColor(); + void updateWebpage(); + + void refreshData(int modID); + bool tryRefreshData(int modID); + + void onModChanged(); + void onOpenLink(); + void onModIDChanged(); + void onSourceGameChanged(); + void onVersionChanged(); + void onRefreshBrowser(); + void onEndorse(); + void onUrlChanged(); +}; + +#endif // MODINFODIALOGNEXUS_H diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 6dcff0ac..ae0de5e8 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,5 +1,12 @@ #include "modinfodialogtab.h" +ModInfoDialogTab::ModInfoDialogTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), m_origin(nullptr) +{ +} + bool ModInfoDialogTab::feedFile(const QString&, const QString&) { // no-op @@ -21,14 +28,40 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } -void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +void ModInfoDialogTab::update() { // no-op } -void ModInfoDialogTab::update() +void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { - // no-op + m_mod = mod; + m_origin = origin; +} + +ModInfo::Ptr ModInfoDialogTab::mod() const +{ + return m_mod; +} + +MOShared::FilesOrigin* ModInfoDialogTab::origin() const +{ + return m_origin; +} + +OrganizerCore& ModInfoDialogTab::core() +{ + return m_core; +} + +PluginContainer& ModInfoDialogTab::plugin() +{ + return m_plugin; +} + +QWidget* ModInfoDialogTab::parentWidget() +{ + return m_parent; } void ModInfoDialogTab::emitOriginModified(int originID) diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index ead29d10..dd851b31 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -8,13 +8,13 @@ namespace MOShared { class FilesOrigin; } namespace Ui { class ModInfoDialog; } class Settings; +class OrganizerCore; class ModInfoDialogTab : public QObject { Q_OBJECT; public: - ModInfoDialogTab() = default; ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; ModInfoDialogTab(ModInfoDialogTab&&) = default; @@ -22,21 +22,42 @@ public: virtual ~ModInfoDialogTab() = default; virtual void clear() = 0; + virtual void update(); virtual bool feedFile(const QString& rootPath, const QString& filename); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - virtual void update(); + + ModInfo::Ptr mod() const; + MOShared::FilesOrigin* origin() const; signals: void originModified(int originID); void modOpen(QString name); protected: + Ui::ModInfoDialog* ui; + + ModInfoDialogTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + OrganizerCore& core(); + PluginContainer& plugin(); + + QWidget* parentWidget(); + void emitOriginModified(int originID); void emitModOpen(QString name); + +private: + OrganizerCore& m_core; + PluginContainer& m_plugin; + QWidget* m_parent; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; }; #endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 0dd6f06e..f48557b0 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -22,8 +22,10 @@ private: GenericFilesTab::GenericFilesTab( - QWidget* parent, QListWidget* list, QSplitter* sp, TextEditor* e) - : m_parent(parent), m_list(list), m_editor(e) + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, + QListWidget* list, QSplitter* sp, TextEditor* e) + : ModInfoDialogTab(oc, plugin, parent, ui), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -49,7 +51,7 @@ bool GenericFilesTab::canClose() } const int res = QMessageBox::question( - m_parent, + parentWidget(), QObject::tr("Save changes?"), QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); @@ -109,9 +111,12 @@ void GenericFilesTab::select(FileListItem* item) } -TextFilesTab::TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) - : GenericFilesTab( - parent, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +TextFilesTab::TextFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + oc, plugin, parent, ui, + ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -130,9 +135,12 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) - : GenericFilesTab( - parent, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +IniFilesTab::IniFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + oc, plugin, parent, ui, + ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 11691d64..0dc5ec89 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -18,12 +18,12 @@ public: bool feedFile(const QString& rootPath, const QString& fullPath) override; protected: - QWidget* m_parent; QListWidget* m_list; TextEditor* m_editor; GenericFilesTab( - QWidget* parent, + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -37,7 +37,9 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); + TextFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -47,7 +49,9 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); + IniFilesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index a271c4e8..babbd665 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -118,7 +118,7 @@ void ModInfoRegular::readMeta() m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; } } - + QString categoriesString = metaFile.value("category", "").toString(); QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); @@ -291,15 +291,27 @@ void ModInfoRegular::nxmRequestFailed(QString, int, int, QVariant userData, QNet bool ModInfoRegular::updateNXMInfo() { - QDateTime time = QDateTime::currentDateTimeUtc(); - QDateTime target = m_LastNexusQuery.addDays(1); - if (m_NexusID > 0 && time >= target) { + if (needsDescriptionUpdate()) { m_NexusBridge.requestDescription(m_GameName, m_NexusID, QVariant()); return true; } + return false; } +bool ModInfoRegular::needsDescriptionUpdate() const +{ + if (m_NexusID > 0) { + QDateTime time = QDateTime::currentDateTimeUtc(); + QDateTime target = m_LastNexusQuery.addDays(1); + + if (time >= target) { + return true; + } + } + + return false; +} void ModInfoRegular::setCategory(int categoryID, bool active) { diff --git a/src/modinforegular.h b/src/modinforegular.h index f70487a2..cfe713ca 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -464,6 +464,7 @@ private: mutable std::vector m_CachedContent; mutable QTime m_LastContentCheck; + bool needsDescriptionUpdate() const; }; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 2cad9ce8..e073924b 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -2204,6 +2204,21 @@ void OrganizerCore::updateModsInDirectoryStructure(QMap f) +{ + if (NexusInterface::instance(m_PluginContainer)->getAccessManager()->validated()) { + f(); + } else { + QString apiKey; + if (settings().getNexusApiKey(apiKey)) { + doAfterLogin([f]{ f(); }); + NexusInterface::instance(m_PluginContainer)->getAccessManager()->apiCheck(apiKey); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus"), parent); + } + } +} + void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) { if (m_PluginContainer != nullptr) { diff --git a/src/organizercore.h b/src/organizercore.h index a4a57496..4dd11831 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -146,6 +146,7 @@ public: void updateModsInDirectoryStructure(QMap modInfos); void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } + void loggedInAction(QWidget* parent, std::function f); static QString findJavaInstallation(const QString& jarFile={}); -- cgit v1.3.1 From cbdc4cc3284f13477bfbf292d15c4a5742627091 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 03:03:41 -0400 Subject: split notes tab added new HTMLEditor that triggers an editingFinished() on focus our, used by notes tab --- src/modinfodialog.cpp | 14 +------------- src/modinfodialog.ui | 17 +++++++++++------ src/modinfodialognexus.cpp | 2 -- src/modinfodialogtab.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/modinfodialogtab.h | 16 ++++++++++++++++ src/texteditor.cpp | 10 ++++++++++ src/texteditor.h | 17 +++++++++++++++++ 7 files changed, 94 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 30110d14..b78f4515 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,7 +19,6 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" -#include "descriptionpage.h" #include "mainwindow.h" #include "modidlineedit.h" @@ -173,9 +172,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - ui->commentsEdit->setText(modInfo->comments()); - ui->notesEdit->setText(modInfo->notes()); - //TODO: No easy way to delegate links //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); @@ -238,14 +234,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ModInfoDialog::~ModInfoDialog() { - m_ModInfo->setComments(ui->commentsEdit->text()); - - //Avoid saving html stump if notes field is empty. - if (ui->notesEdit->toPlainText().isEmpty()) - m_ModInfo->setNotes(ui->notesEdit->toPlainText()); - else - m_ModInfo->setNotes(ui->notesEdit->toHtml()); - delete ui; } @@ -264,7 +252,7 @@ std::vector> ModInfoDialog::createTabs() { return createTabsImpl< TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab>( + ConflictsTab, CategoriesTab, NexusTab, NotesTab>( *m_OrganizerCore, *m_PluginContainer, this, ui); } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 29a7400f..360ecc79 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -6,7 +6,7 @@ 0 0 - 790 + 735 534 @@ -20,7 +20,7 @@ QTabWidget::Rounded - 0 + 7 true @@ -180,7 +180,7 @@ 0 0 - 741 + 686 436 @@ -859,7 +859,7 @@ text-align: left;
- + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png @@ -963,7 +963,7 @@ p, li { white-space: pre-wrap; }
- + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1071,7 +1071,7 @@ p, li { white-space: pre-wrap; } - + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. @@ -1210,6 +1210,11 @@ p, li { white-space: pre-wrap; } QPlainTextEdit
texteditor.h
+ + HTMLEditor + QTextEdit +
texteditor.h
+
diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 753d43de..55b55439 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -248,8 +248,6 @@ void NexusTab::refreshData(int modID) } else { onModChanged(); } - - //MessageDialog::showMessage(tr("Info requested, please wait"), this); } bool NexusTab::tryRefreshData(int modID) diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index ae0de5e8..b59f4dcc 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,4 +1,6 @@ #include "modinfodialogtab.h" +#include "ui_modinfodialog.h" +#include "texteditor.h" ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, @@ -73,3 +75,40 @@ void ModInfoDialogTab::emitModOpen(QString name) { emit modOpen(name); } + + +NotesTab::NotesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui) +{ + connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); + connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); +} + +void NotesTab::clear() +{ + ui->commentsEdit->clear(); + ui->notesEdit->clear(); +} + +void NotesTab::update() +{ + ui->commentsEdit->setText(mod()->comments()); + ui->notesEdit->setText(mod()->notes()); +} + +void NotesTab::onComments() +{ + mod()->setComments(ui->commentsEdit->text()); +} + +void NotesTab::onNotes() +{ + // Avoid saving html stub if notes field is empty. + if (ui->notesEdit->toPlainText().isEmpty()) { + mod()->setNotes({}); + } else { + mod()->setNotes(ui->notesEdit->toHtml()); + } +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index dd851b31..60371954 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -60,4 +60,20 @@ private: MOShared::FilesOrigin* m_origin; }; + +class NotesTab : public ModInfoDialogTab +{ +public: + NotesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void update() override; + +private: + void onComments(); + void onNotes(); +}; + #endif // MODINFODIALOGTAB_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 99490b22..6c1685da 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -458,3 +458,13 @@ void TextEditorToolbar::onWordWrap(bool b) { m_wordWrap->setChecked(b); } + + +void HTMLEditor::focusOutEvent(QFocusEvent* e) +{ + if (document() && document()->isModified()) { + emit editingFinished(); + } + + QTextEdit::focusInEvent(e); +} diff --git a/src/texteditor.h b/src/texteditor.h index eef5ca52..f3031731 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -137,4 +137,21 @@ private: void paintLineNumbers(QPaintEvent* e, const QColor& textColor); }; + +class HTMLEditor : public QTextEdit +{ + Q_OBJECT; + +public: + using QTextEdit::QTextEdit; + +signals: + void editingFinished(); + +protected: + void focusOutEvent(QFocusEvent* e); + +private: +}; + #endif // MO_TEXTEDITOR_H -- cgit v1.3.1 From 895883571b2b71c891dbaad4662adc7b39256286 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 04:04:54 -0400 Subject: splitting filetree tab moved mod info dialog classes to a sub filter --- src/CMakeLists.txt | 19 +- src/modinfodialog.cpp | 409 +-------------------------------------- src/modinfodialog.h | 28 +-- src/modinfodialog.ui | 8 +- src/modinfodialogconflicts.cpp | 14 ++ src/modinfodialogconflicts.h | 16 +- src/modinfodialogfiletree.cpp | 420 +++++++++++++++++++++++++++++++++++++++++ src/modinfodialogfiletree.h | 39 ++++ 8 files changed, 502 insertions(+), 451 deletions(-) create mode 100644 src/modinfodialogfiletree.cpp create mode 100644 src/modinfodialogfiletree.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7dc39fd9..1d27f444 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,6 +57,7 @@ SET(organizer_SRCS modinfodialogcategories.cpp modinfodialogconflicts.cpp modinfodialogesps.cpp + modinfodialogfiletree.cpp modinfodialogimages.cpp modinfodialognexus.cpp modinfodialogtab.cpp @@ -164,6 +165,7 @@ SET(organizer_HDRS modinfodialogcategories.h modinfodialogconflicts.h modinfodialogesps.h + modinfodialogfiletree.h modinfodialogimages.h modinfodialognexus.h modinfodialogtab.h @@ -364,21 +366,24 @@ set(locking set(modinfo modinfo modinfobackup + modinfoforeign + modinfooverwrite + modinforegular + modinfoseparator + modinfowithconflictinfo +) + +set(modinfo\\dialog modinfodialog modinfodialogcategories modinfodialogconflicts modinfodialogesps + modinfodialogfiletree modinfodialogimages modinfodialognexus modinfodialogtab modinfodialogtextfiles - modinfoforeign - modinfooverwrite - modinforegular - modinfoseparator - modinfowithconflictinfo ) - set(modlist modlist modlistsortproxy @@ -443,7 +448,7 @@ set(widgets ) set(src_filters - application core browser dialogs downloads executables locking modinfo + application core browser dialogs downloads executables locking modinfo modinfo\\dialog modlist plugins previews profiles settings utilities widgets ) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b78f4515..6463a5a6 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -43,6 +43,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogconflicts.h" #include "modinfodialogcategories.h" #include "modinfodialognexus.h" +#include "modinfodialogfiletree.h" #include #include @@ -193,7 +194,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - //ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_NOTES, true); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); } else if (unmanaged) @@ -205,8 +206,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_FILETREE, false); } else { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); ui->tabWidget->setTabEnabled(TAB_INIFILES, true); @@ -215,8 +216,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); ui->tabWidget->setTabEnabled(TAB_NEXUS, true); - - initFiletree(modInfo); + ui->tabWidget->setTabEnabled(TAB_NOTES, true); + ui->tabWidget->setTabEnabled(TAB_FILETREE, true); } // activate first enabled tab @@ -252,7 +253,7 @@ std::vector> ModInfoDialog::createTabs() { return createTabsImpl< TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab, NotesTab>( + ConflictsTab, CategoriesTab, NexusTab, NotesTab, FileTreeTab>( *m_OrganizerCore, *m_PluginContainer, this, ui); } @@ -262,35 +263,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) -{ - ui->fileTree = findChild("fileTree"); - - m_FileSystemModel = new QFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - m_FileSystemModel->setRootPath(m_RootPath); - ui->fileTree->setModel(m_FileSystemModel); - ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); - ui->fileTree->setColumnWidth(0, 300); - - m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); - m_OpenAction = new QAction(tr("&Open"), ui->fileTree); - m_PreviewAction = new QAction(tr("&Preview"), ui->fileTree); - m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); - m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); - m_HideAction = new QAction(tr("&Hide"), ui->fileTree); - m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - - connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); - connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - connect(m_PreviewAction, SIGNAL(triggered()), this, SLOT(previewTriggered())); - connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); - connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); -} - - int ModInfoDialog::tabIndex(const QString &tabId) { for (int i = 0; i < ui->tabWidget->count(); ++i) { @@ -301,7 +273,6 @@ int ModInfoDialog::tabIndex(const QString &tabId) return -1; } - void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); @@ -340,6 +311,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) m_RealTabPos[newPos] = newPos; } } + // then actually move the tabs QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad ui->tabWidget->blockSignals(true); @@ -407,377 +379,10 @@ void ModInfoDialog::openTab(int tab) } } -QString ModInfoDialog::getFileCategory(int categoryID) -{ - switch (categoryID) { - case 1: return tr("Main"); - case 2: return tr("Update"); - case 3: return tr("Optional"); - case 4: return tr("Old"); - case 5: return tr("Miscellaneous"); - case 6: return tr("Deleted"); - default: return tr("Unknown"); - } -} - void ModInfoDialog::on_tabWidget_currentChanged(int index) { } -bool ModInfoDialog::recursiveDelete(const QModelIndex &index) -{ - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; -} - - -void ModInfoDialog::on_openInExplorerButton_clicked() -{ - shell::ExploreFile(m_ModInfo->absolutePath()); -} - -void ModInfoDialog::deleteFile(const QModelIndex &index) -{ - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); - if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete %1").arg(fileName)); - } -} - -void ModInfoDialog::delete_activated() -{ - if (ui->fileTree->hasFocus()) { - QItemSelectionModel *selection = ui->fileTree->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() >= 1) { - - if (selection->selectedRows().count() == 0) { - return; - } - else if (selection->selectedRows().count() == 1) { - QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, selection->selectedRows()) { - deleteFile(index); - } - } - } -} - -void ModInfoDialog::deleteTriggered() -{ - if (m_FileSelection.count() == 0) { - return; - } else if (m_FileSelection.count() == 1) { - QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, m_FileSelection) { - deleteFile(index); - } -} - - -void ModInfoDialog::renameTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - - ui->fileTree->edit(index); -} - - -void ModInfoDialog::hideTriggered() -{ - changeFiletreeVisibility(false); -} - - -void ModInfoDialog::unhideTriggered() -{ - changeFiletreeVisibility(true); -} - -void ModInfoDialog::changeFiletreeVisibility(bool visible) -{ - bool changed = false; - bool stop = false; - - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << m_FileSelection.size() << " filetree files"; - - QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - - 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 (visible) { - if (!canUnhideFile(false, path)) { - qDebug().nospace() << "cannot unhide " << path << ", skipping"; - continue; - } - result = unhideFile(renamer, path); - } else { - if (!canHideFile(false, path)) { - qDebug().nospace() << "cannot hide " << path << ", skipping"; - continue; - } - result = hideFile(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() << (visible ? "unhiding" : "hiding") << " filetree files done"; - - if (changed) { - qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); - } - refreshLists(); - } -} - - -void ModInfoDialog::openTriggered() -{ - if (m_FileSelection.size() == 1) { - const auto index = m_FileSelection.at(0); - if (!index.isValid()) { - return; - } - - QString fileName = m_FileSystemModel->filePath(index); - shell::OpenFile(fileName); - } -} - -void ModInfoDialog::previewTriggered() -{ - if (m_FileSelection.size() == 1) { - const auto index = m_FileSelection.at(0); - if (!index.isValid()) { - return; - } - - QString fileName = m_FileSystemModel->filePath(index); - m_OrganizerCore->previewFile(this, m_ModInfo->name(), fileName); - } -} - -void ModInfoDialog::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->fileTree->setCurrentIndex(newIndex); - ui->fileTree->edit(newIndex); -} - - -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - - QMenu menu(ui->fileTree); - - menu.addAction(m_NewFolderAction); - - if (selectionModel->hasSelection()) { - bool enableOpen = true; - bool enablePreview = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; - - 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; - enablePreview = false; - } - - const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - - if (!canPreviewFile(*m_PluginContainer, false, fileName)) { - enablePreview = false; - } - - 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; - enablePreview = false; - enableRename = false; - - if (m_FileSelection.size() < max_scan_for_context_menu) { - // 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) { - menu.addAction(m_OpenAction); - } - - if (enablePreview) { - menu.addAction(m_PreviewAction); - } - - 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)); -} - void ModInfoDialog::on_nextButton_clicked() { int currentTab = ui->tabWidget->currentIndex(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 7b96d21a..b367f647 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -54,19 +54,6 @@ class TextEditor; class ModInfoDialogTab; -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const - { - QStyledItemDelegate::initStyleOption(o, i); - o->textElideMode = Qt::ElideLeft; - } -}; - bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); @@ -145,12 +132,6 @@ signals: void originModified(int originID); private: - - void initFiletree(ModInfo::Ptr modInfo); - - void refreshLists(); - - QString getFileCategory(int categoryID); bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); @@ -189,17 +170,9 @@ private: OrganizerCore *m_OrganizerCore; PluginContainer *m_PluginContainer; - QFileSystemModel *m_FileSystemModel; QTreeView *m_FileTree; QModelIndexList m_FileSelection; - QAction *m_NewFolderAction; - QAction *m_OpenAction; - QAction *m_PreviewAction; - QAction *m_RenameAction; - QAction *m_DeleteAction; - QAction *m_HideAction; - QAction *m_UnhideAction; const MOShared::DirectoryEntry *m_Directory; MOShared::FilesOrigin *m_Origin; @@ -209,6 +182,7 @@ private: std::vector> createTabs(); + void refreshLists(); void refreshFiles(); void restoreTabState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 360ecc79..04282e81 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 7 + 8 true @@ -1088,7 +1088,7 @@ p, li { white-space: pre-wrap; }
- + Filetree @@ -1096,7 +1096,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -1124,7 +1124,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 4176ac3e..e7f189c2 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -24,6 +24,20 @@ int naturalCompare(const QString& a, const QString& b) } +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + + class ConflictItem : public QTreeWidgetItem { public: diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 20362575..9c011163 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -115,18 +115,12 @@ public: private: struct Actions { - QAction* hide; - QAction* unhide; - QAction* open; - QAction* preview; - QMenu* gotoMenu; + QAction* hide = nullptr; + QAction* unhide = nullptr; + QAction* open = nullptr; + QAction* preview = nullptr; + QMenu* gotoMenu = nullptr; std::vector gotoActions; - - Actions() : - hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), - gotoMenu(nullptr) - { - } }; GeneralConflictsTab m_general; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp new file mode 100644 index 00000000..e277b04b --- /dev/null +++ b/src/modinfodialogfiletree.cpp @@ -0,0 +1,420 @@ +#include "modinfodialogfiletree.h" +#include "ui_modinfodialog.h" +#include "organizercore.h" +#include +#include + +using MOBase::reportError; +namespace shell = MOBase::shell; + +FileTreeTab::FileTreeTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui) + : ModInfoDialogTab(oc, plugin, parent, ui), m_fs(nullptr) +{ + m_fs = new QFileSystemModel(this); + m_fs->setReadOnly(false); + ui->fileTree1->setModel(m_fs); + ui->fileTree1->setColumnWidth(0, 300); + + m_actions.newFolder = new QAction(tr("&New Folder"), ui->fileTree1); + m_actions.open = new QAction(tr("&Open"), ui->fileTree1); + m_actions.preview = new QAction(tr("&Preview"), ui->fileTree1); + m_actions.rename = new QAction(tr("&Rename"), ui->fileTree1); + m_actions.del = new QAction(tr("&Delete"), ui->fileTree1); + m_actions.hide = new QAction(tr("&Hide"), ui->fileTree1); + m_actions.unhide = new QAction(tr("&Unhide"), ui->fileTree1); + + connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); + connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); + connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); + connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); + connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); + connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); + connect(m_actions.unhide, &QAction::triggered, [&]{ onUnhide(); }); +} + +void FileTreeTab::clear() +{ + m_fs->setRootPath({}); + //ui->fileTree1-> +} + +void FileTreeTab::update() +{ + const auto rootPath = mod()->absolutePath(); + + m_fs->setRootPath(rootPath); + ui->fileTree1->setRootIndex(m_fs->index(rootPath)); +} + +QModelIndex FileTreeTab::singleSelection() const +{ + const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + if (rows.size() != 1) { + return {}; + } + + return rows[0]; +} + +void FileTreeTab::onCreateDirectory() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + QModelIndex index = m_fs->isDir(selection) ? selection : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_fs->filePath(index).append("/"); + + QModelIndex existingIndex = m_fs->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_fs->index(path + name); + } + + QModelIndex newIndex = m_fs->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->fileTree1->setCurrentIndex(newIndex); + ui->fileTree1->edit(newIndex); +} + +void FileTreeTab::onOpen() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + shell::OpenFile(m_fs->filePath(selection)); +} + +void FileTreeTab::onPreview() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); +} + +void FileTreeTab::onRename() +{ + auto selection = singleSelection(); + if (!selection.isValid()) { + return; + } + + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_fs->isReadOnly()) { + return; + } + + ui->fileTree1->edit(index); +} + +void FileTreeTab::onDelete() +{ + const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + if (rows.count() == 0) { + return; + } + + QString message; + + if (rows.count() == 1) { + QString fileName = m_fs->fileName(rows[0]); + message = tr("Are sure you want to delete \"%1\"?").arg(fileName); + } else { + message = tr("Are sure you want to delete the selected files?"); + } + + if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != QMessageBox::Yes) { + return; + } + + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + + +bool FileTreeTab::recursiveDelete(const QModelIndex &index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; +} + + +void ModInfoDialog::on_openInExplorerButton_clicked() +{ + shell::ExploreFile(m_ModInfo->absolutePath()); +} + +void ModInfoDialog::deleteFile(const QModelIndex &index) +{ + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete %1").arg(fileName)); + } +} + +void ModInfoDialog::delete_activated() +{ + if (ui->fileTree->hasFocus()) { + QItemSelectionModel *selection = ui->fileTree->selectionModel(); + + if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + + if (selection->selectedRows().count() == 0) { + return; + } + else if (selection->selectedRows().count() == 1) { + QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach(QModelIndex index, selection->selectedRows()) { + deleteFile(index); + } + } + } +} + + + + + +void ModInfoDialog::hideTriggered() +{ + changeFiletreeVisibility(false); +} + + +void ModInfoDialog::unhideTriggered() +{ + changeFiletreeVisibility(true); +} + +void ModInfoDialog::changeFiletreeVisibility(bool visible) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (visible ? "unhiding" : "hiding") << " " + << m_FileSelection.size() << " filetree files"; + + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + + 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 (visible) { + if (!canUnhideFile(false, path)) { + qDebug().nospace() << "cannot unhide " << path << ", skipping"; + continue; + } + result = unhideFile(renamer, path); + } else { + if (!canHideFile(false, path)) { + qDebug().nospace() << "cannot hide " << path << ", skipping"; + continue; + } + result = hideFile(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() << (visible ? "unhiding" : "hiding") << " filetree files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + if (m_Origin) { + emit originModified(m_Origin->getID()); + } + refreshLists(); + } +} + + + + +void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + + QMenu menu(ui->fileTree); + + menu.addAction(m_NewFolderAction); + + if (selectionModel->hasSelection()) { + bool enableOpen = true; + bool enablePreview = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; + + 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; + enablePreview = false; + } + + const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + + if (!canPreviewFile(*m_PluginContainer, false, fileName)) { + enablePreview = false; + } + + 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; + enablePreview = false; + enableRename = false; + + if (m_FileSelection.size() < max_scan_for_context_menu) { + // 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) { + menu.addAction(m_OpenAction); + } + + if (enablePreview) { + menu.addAction(m_PreviewAction); + } + + 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)); +} diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h new file mode 100644 index 00000000..e7708ab8 --- /dev/null +++ b/src/modinfodialogfiletree.h @@ -0,0 +1,39 @@ +#ifndef MODINFODIALOGFILETREE_H +#define MODINFODIALOGFILETREE_H + +#include "modinfodialogtab.h" + +class FileTreeTab : public ModInfoDialogTab +{ +public: + FileTreeTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui); + + void clear() override; + void update() override; + +private: + struct Actions + { + QAction *newFolder = nullptr; + QAction *open = nullptr; + QAction *preview = nullptr; + QAction *rename = nullptr; + QAction *del = nullptr; + QAction *hide = nullptr; + QAction *unhide = nullptr; + }; + + QFileSystemModel* m_fs; + Actions m_actions; + + QModelIndex singleSelection() const; + void onCreateDirectory(); + void onOpen(); + void onPreview(); + void onRename(); + void onDelete(); +}; + +#endif // MODINFODIALOGFILETREE_H -- cgit v1.3.1 From 3edad124635291b5d07794ad088ff8840391257f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 06:00:17 -0400 Subject: finished splitting filetree tab forward delete shortcut to tabs --- src/mainwindow.cpp | 45 ++++---- src/modinfodialog.cpp | 32 +++--- src/modinfodialog.h | 42 +------- src/modinfodialog.ui | 13 ++- src/modinfodialogconflicts.cpp | 6 +- src/modinfodialogfiletree.cpp | 232 ++++++++++++++++++++--------------------- src/modinfodialogfiletree.h | 11 +- src/modinfodialogtab.cpp | 15 ++- src/modinfodialogtab.h | 3 +- 9 files changed, 197 insertions(+), 202 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8eb0838e..f9bfaafb 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3222,34 +3222,37 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } } else { modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer, this); - connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + + ModInfoDialog dialog( + modInfo, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), + &m_OrganizerCore, &m_PluginContainer, this); + connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(modOpenNext(int)), this, SLOT(modOpenNext(int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - //Open the tab first if we want to use the standard indexes of the tabs. - if (tab != -1) { - dialog.openTab(tab); - } + //Open the tab first if we want to use the standard indexes of the tabs. + if (tab != -1) { + dialog.openTab(tab); + } - dialog.restoreState(m_OrganizerCore.settings()); - QSettings &settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(dialog.objectName()); - if (settings.contains(key)) { - dialog.restoreGeometry(settings.value(key).toByteArray()); - } + dialog.restoreState(m_OrganizerCore.settings()); + QSettings &settings = m_OrganizerCore.settings().directInterface(); + QString key = QString("geometry/%1").arg(dialog.objectName()); + if (settings.contains(key)) { + dialog.restoreGeometry(settings.value(key).toByteArray()); + } - //If no tab was specified use the first tab from the left based on the user order. - if (tab == -1) { - for (int i = 0; i < dialog.findChild("tabWidget")->count(); ++i) { - if (dialog.findChild("tabWidget")->isTabEnabled(i)) { - dialog.findChild("tabWidget")->setCurrentIndex(i); - break; - } - } - } + //If no tab was specified use the first tab from the left based on the user order. + if (tab == -1) { + for (int i = 0; i < dialog.findChild("tabWidget")->count(); ++i) { + if (dialog.findChild("tabWidget")->isTabEnabled(i)) { + dialog.findChild("tabWidget")->setCurrentIndex(i); + break; + } + } + } dialog.exec(); dialog.saveState(m_OrganizerCore.settings()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 6463a5a6..71e514b2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -144,12 +144,9 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) +ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_NewFolderAction(nullptr), m_OpenAction(nullptr), m_PreviewAction(nullptr), - m_RenameAction(nullptr), m_DeleteAction(nullptr), m_HideAction(nullptr), - m_UnhideAction(nullptr), m_Directory(directory), m_Origin(nullptr), - m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) + m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); @@ -173,13 +170,12 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_RootPath = modInfo->absolutePath(); - //TODO: No easy way to delegate links - //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); + auto* sc = new QShortcut(QKeySequence::Delete, this); + connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); - new QShortcut(QKeySequence::Delete, this, SLOT(delete_activated())); - - if (directory->originExists(ToWString(modInfo->name()))) { - m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); + auto* ds = m_OrganizerCore->directoryStructure(); + if (ds->originExists(ToWString(modInfo->name()))) { + m_Origin = &ds->getOriginByName(ToWString(modInfo->name())); if (m_Origin->isDisabled()) { m_Origin = nullptr; } @@ -335,17 +331,21 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +void ModInfoDialog::onDeleteShortcut() +{ + for (auto& t : m_tabs) { + if (t->deleteRequested()) { + break; + } + } +} + void ModInfoDialog::refreshLists() { for (auto& tab : m_tabs) { tab->update(); } - refreshFiles(); -} - -void ModInfoDialog::refreshFiles() -{ if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index b367f647..49007c87 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -73,7 +73,6 @@ class ModInfoDialog : public MOBase::TutorableDialog Q_OBJECT public: - enum ETabs { TAB_TEXTFILES, TAB_INIFILES, @@ -87,14 +86,16 @@ public: }; public: - /** * @brief constructor * * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent = 0); + explicit ModInfoDialog( + ModInfo::Ptr modInfo, + bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, + QWidget *parent = 0); ~ModInfoDialog(); @@ -125,34 +126,17 @@ public: void restoreState(const Settings& s); signals: - void downloadRequest(const QString &link); void modOpen(const QString &modName, int tab); void modOpenNext(int tab=-1); void modOpenPrev(int tab=-1); void originModified(int originID); private: - bool recursiveDelete(const QModelIndex &index); - void deleteFile(const QModelIndex &index); - int tabIndex(const QString &tabId); private slots: - void delete_activated(); - - void createDirectoryTriggered(); - void openTriggered(); - void previewTriggered(); - void renameTriggered(); - void deleteTriggered(); - void hideTriggered(); - void unhideTriggered(); - - void on_openInExplorerButton_clicked(); void on_closeButton_clicked(); void on_tabWidget_currentChanged(int index); - void on_fileTree_customContextMenuRequested(const QPoint &pos); - void on_nextButton_clicked(); void on_prevButton_clicked(); @@ -160,35 +144,19 @@ private: using FileEntry = MOShared::FileEntry; Ui::ModInfoDialog *ui; - ModInfo::Ptr m_ModInfo; - std::vector> m_tabs; - QString m_RootPath; - OrganizerCore *m_OrganizerCore; PluginContainer *m_PluginContainer; - - QTreeView *m_FileTree; - QModelIndexList m_FileSelection; - - - const MOShared::DirectoryEntry *m_Directory; MOShared::FilesOrigin *m_Origin; - std::map m_RealTabPos; - std::vector> createTabs(); - void refreshLists(); - void refreshFiles(); - void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; - - void changeFiletreeVisibility(bool visible); + void onDeleteShortcut(); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 04282e81..65b89621 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1096,7 +1096,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -1124,7 +1124,7 @@ p, li { white-space: pre-wrap; } - + Qt::CustomContextMenu @@ -1161,6 +1161,9 @@ p, li { white-space: pre-wrap; } Previous + + false + @@ -1168,6 +1171,9 @@ p, li { white-space: pre-wrap; } Next + + false + @@ -1188,6 +1194,9 @@ p, li { white-space: pre-wrap; } Close + + false +
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index e7f189c2..adde27ca 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -6,8 +6,8 @@ using namespace MOShared; using namespace MOBase; -// if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether menu items apply to them, just show all of them +// if there are more than 50 selected items in the conflict tree, don't bother +// checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; @@ -248,7 +248,7 @@ void ConflictsTab::changeItemsVisibility( qDebug().nospace() << "triggering refresh"; if (origin()) { - emit originModified(origin()->getID()); + emitOriginModified(); } update(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index e277b04b..b73a9e24 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -1,12 +1,18 @@ #include "modinfodialogfiletree.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include "organizercore.h" +#include "filerenamer.h" #include #include using MOBase::reportError; namespace shell = MOBase::shell; +// if there are more than 50 selected items in the filetree, don't bother +// checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; + FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui) @@ -14,16 +20,16 @@ FileTreeTab::FileTreeTab( { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); - ui->fileTree1->setModel(m_fs); - ui->fileTree1->setColumnWidth(0, 300); + ui->filetree->setModel(m_fs); + ui->filetree->setColumnWidth(0, 300); - m_actions.newFolder = new QAction(tr("&New Folder"), ui->fileTree1); - m_actions.open = new QAction(tr("&Open"), ui->fileTree1); - m_actions.preview = new QAction(tr("&Preview"), ui->fileTree1); - m_actions.rename = new QAction(tr("&Rename"), ui->fileTree1); - m_actions.del = new QAction(tr("&Delete"), ui->fileTree1); - m_actions.hide = new QAction(tr("&Hide"), ui->fileTree1); - m_actions.unhide = new QAction(tr("&Unhide"), ui->fileTree1); + m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); + m_actions.open = new QAction(tr("&Open"), ui->filetree); + m_actions.preview = new QAction(tr("&Preview"), ui->filetree); + m_actions.rename = new QAction(tr("&Rename"), ui->filetree); + m_actions.del = new QAction(tr("&Delete"), ui->filetree); + m_actions.hide = new QAction(tr("&Hide"), ui->filetree); + m_actions.unhide = new QAction(tr("&Unhide"), ui->filetree); connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); @@ -32,12 +38,18 @@ FileTreeTab::FileTreeTab( connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); connect(m_actions.unhide, &QAction::triggered, [&]{ onUnhide(); }); + + connect(ui->openInExplorer, &QToolButton::clicked, [&]{ onOpenInExplorer(); }); + + connect( + ui->filetree, &QTreeView::customContextMenuRequested, + [&](const QPoint& pos){ onContextMenu(pos); }); } void FileTreeTab::clear() { m_fs->setRootPath({}); - //ui->fileTree1-> + //ui->filetree-> } void FileTreeTab::update() @@ -45,12 +57,22 @@ void FileTreeTab::update() const auto rootPath = mod()->absolutePath(); m_fs->setRootPath(rootPath); - ui->fileTree1->setRootIndex(m_fs->index(rootPath)); + ui->filetree->setRootIndex(m_fs->index(rootPath)); +} + +bool FileTreeTab::deleteRequested() +{ + if (!ui->filetree->hasFocus()) { + return false; + } + + onDelete(); + return true; } QModelIndex FileTreeTab::singleSelection() const { - const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + const auto rows = ui->filetree->selectionModel()->selectedRows(); if (rows.size() != 1) { return {}; } @@ -60,11 +82,19 @@ QModelIndex FileTreeTab::singleSelection() const void FileTreeTab::onCreateDirectory() { - auto selection = singleSelection(); - if (!selection.isValid()) { + const auto selectedRows = ui->filetree->selectionModel()->selectedRows(); + if (selectedRows.size() > 1) { return; } + QModelIndex selection; + + if (selectedRows.size() == 0) { + selection = m_fs->index(m_fs->rootPath(), 0); + } else { + selection = selectedRows[0]; + } + QModelIndex index = m_fs->isDir(selection) ? selection : selection.parent(); index = index.sibling(index.row(), 0); @@ -84,8 +114,8 @@ void FileTreeTab::onCreateDirectory() return; } - ui->fileTree1->setCurrentIndex(newIndex); - ui->fileTree1->edit(newIndex); + ui->filetree->setCurrentIndex(newIndex); + ui->filetree->edit(newIndex); } void FileTreeTab::onOpen() @@ -120,12 +150,12 @@ void FileTreeTab::onRename() return; } - ui->fileTree1->edit(index); + ui->filetree->edit(index); } void FileTreeTab::onDelete() { - const auto rows = ui->fileTree1->selectionModel()->selectedRows(); + const auto rows = ui->filetree->selectionModel()->selectedRows(); if (rows.count() == 0) { return; } @@ -143,121 +173,95 @@ void FileTreeTab::onDelete() return; } - foreach(QModelIndex index, m_FileSelection) { + for (const auto& index : rows) { deleteFile(index); } } - -bool FileTreeTab::recursiveDelete(const QModelIndex &index) +void FileTreeTab::onHide() { - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; + changeVisibility(false); } +void FileTreeTab::onUnhide() +{ + changeVisibility(true); +} -void ModInfoDialog::on_openInExplorerButton_clicked() +void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(m_ModInfo->absolutePath()); + shell::ExploreFile(mod()->absolutePath()); } -void ModInfoDialog::deleteFile(const QModelIndex &index) +bool FileTreeTab::deleteFile(const QModelIndex& index) { - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); + bool res = false; + + if (m_fs->isDir(index)) { + res = deleteFileRecursive(index); + } else { + res = m_fs->remove(index); + } + if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete %1").arg(fileName)); + reportError(tr("Failed to delete %1").arg(m_fs->fileName(index))); } + + return res; } -void ModInfoDialog::delete_activated() +bool FileTreeTab::deleteFileRecursive(const QModelIndex& parent) { - if (ui->fileTree->hasFocus()) { - QItemSelectionModel *selection = ui->fileTree->selectionModel(); - - if (selection->hasSelection() && selection->selectedRows().count() >= 1) { + for (int row = 0; rowrowCount(parent); ++row) { + QModelIndex index = m_fs->index(row, 0, parent); - if (selection->selectedRows().count() == 0) { - return; - } - else if (selection->selectedRows().count() == 1) { - QString fileName = m_FileSystemModel->fileName(selection->selectedRows().at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } + if (m_fs->isDir(index)) { + if (!deleteFileRecursive(index)) { + qCritical() << "failed to delete" << m_fs->fileName(index); + return false; } - - foreach(QModelIndex index, selection->selectedRows()) { - deleteFile(index); + } else { + if (!m_fs->remove(index)) { + qCritical() << "failed to delete", m_fs->fileName(index); + return false; } } } -} - - - + if (!m_fs->remove(parent)) { + qCritical() << "failed to delete" << m_fs->fileName(parent); + return false; + } -void ModInfoDialog::hideTriggered() -{ - changeFiletreeVisibility(false); + return true; } - -void ModInfoDialog::unhideTriggered() +void FileTreeTab::changeVisibility(bool visible) { - changeFiletreeVisibility(true); -} + const auto selection = ui->filetree->selectionModel()->selectedRows(); -void ModInfoDialog::changeFiletreeVisibility(bool visible) -{ bool changed = false; bool stop = false; qDebug().nospace() << (visible ? "unhiding" : "hiding") << " " - << m_FileSelection.size() << " filetree files"; + << selection.size() << " filetree files"; QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - if (m_FileSelection.size() > 1) { + if (selection.size() > 1) { flags |= FileRenamer::MULTIPLE; } - FileRenamer renamer(this, flags); + FileRenamer renamer(parentWidget(), flags); - for (const auto& index : m_FileSelection) { + for (const auto& index : selection) { if (stop) { break; } - const QString path = m_FileSystemModel->filePath(index); + const QString path = m_fs->filePath(index); auto result = FileRenamer::RESULT_CANCEL; if (visible) { @@ -298,26 +302,21 @@ void ModInfoDialog::changeFiletreeVisibility(bool visible) if (changed) { qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); + if (origin()) { + emitOriginModified(); } - refreshLists(); } } - - - -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +void FileTreeTab::onContextMenu(const QPoint &pos) { - QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); + const auto selection = ui->filetree->selectionModel()->selectedRows(); - QMenu menu(ui->fileTree); + QMenu menu(ui->filetree); - menu.addAction(m_NewFolderAction); + menu.addAction(m_actions.newFolder); - if (selectionModel->hasSelection()) { + if (selection.size() > 0) { bool enableOpen = true; bool enablePreview = true; bool enableRename = true; @@ -325,14 +324,14 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) bool enableHide = true; bool enableUnhide = true; - if (m_FileSelection.size() == 1) { + if (selection.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()) { + for (auto index : selection) { + if (m_fs->fileInfo(index).isFile()) { hasFiles = true; break; } @@ -343,9 +342,9 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; } - const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + const QString fileName = m_fs->fileName(selection[0]); - if (!canPreviewFile(*m_PluginContainer, false, fileName)) { + if (!canPreviewFile(plugin(), false, fileName)) { enablePreview = false; } @@ -363,14 +362,14 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) enablePreview = false; enableRename = false; - if (m_FileSelection.size() < max_scan_for_context_menu) { + if (selection.size() < max_scan_for_context_menu) { // 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); + for (const auto& index : selection) { + const QString fileName = m_fs->fileName(index); if (canHideFile(false, fileName)) { enableHide = true; @@ -389,32 +388,29 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) } if (enableOpen) { - menu.addAction(m_OpenAction); + menu.addAction(m_actions.open); } if (enablePreview) { - menu.addAction(m_PreviewAction); + menu.addAction(m_actions.preview); } if (enableRename) { - menu.addAction(m_RenameAction); + menu.addAction(m_actions.rename); } if (enableDelete) { - menu.addAction(m_DeleteAction); + menu.addAction(m_actions.del); } if (enableHide) { - menu.addAction(m_HideAction); + menu.addAction(m_actions.hide); } if (enableUnhide) { - menu.addAction(m_UnhideAction); + menu.addAction(m_actions.unhide); } - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); } - menu.exec(ui->fileTree->viewport()->mapToGlobal(pos)); + menu.exec(ui->filetree->viewport()->mapToGlobal(pos)); } diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index e7708ab8..dcc096fe 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -12,6 +12,7 @@ public: void clear() override; void update() override; + bool deleteRequested() override; private: struct Actions @@ -28,12 +29,20 @@ private: QFileSystemModel* m_fs; Actions m_actions; - QModelIndex singleSelection() const; void onCreateDirectory(); void onOpen(); void onPreview(); void onRename(); void onDelete(); + void onHide(); + void onUnhide(); + void onOpenInExplorer(); + void onContextMenu(const QPoint &pos); + + QModelIndex singleSelection() const; + bool deleteFile(const QModelIndex& index); + bool deleteFileRecursive(const QModelIndex& index); + void changeVisibility(bool visible); }; #endif // MODINFODIALOGFILETREE_H diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index b59f4dcc..58745220 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -1,6 +1,7 @@ #include "modinfodialogtab.h" #include "ui_modinfodialog.h" #include "texteditor.h" +#include "directoryentry.h" ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, @@ -9,6 +10,11 @@ ModInfoDialogTab::ModInfoDialogTab( { } +void ModInfoDialogTab::update() +{ + // no-op +} + bool ModInfoDialogTab::feedFile(const QString&, const QString&) { // no-op @@ -30,9 +36,10 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } -void ModInfoDialogTab::update() +bool ModInfoDialogTab::deleteRequested() { // no-op + return false; } void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) @@ -66,9 +73,11 @@ QWidget* ModInfoDialogTab::parentWidget() return m_parent; } -void ModInfoDialogTab::emitOriginModified(int originID) +void ModInfoDialogTab::emitOriginModified() { - emit originModified(originID); + if (m_origin) { + emit originModified(m_origin->getID()); + } } void ModInfoDialogTab::emitModOpen(QString name) diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 60371954..0dc977a8 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -27,6 +27,7 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + virtual bool deleteRequested(); virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); @@ -49,7 +50,7 @@ protected: QWidget* parentWidget(); - void emitOriginModified(int originID); + void emitOriginModified(); void emitModOpen(QString name); private: -- cgit v1.3.1 From 949e451379d63fe4c6bff82a7a059c6792fbebb5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sun, 23 Jun 2019 06:48:35 -0400 Subject: added missing icons now passing tab index to allow enabling/disabling depending on mod type modinfodialog cleanup --- src/modinfodialog.cpp | 173 +++++++++++---------------------- src/modinfodialog.h | 48 +++------ src/modinfodialog.ui | 5 +- src/modinfodialogcategories.cpp | 9 +- src/modinfodialogcategories.h | 3 +- src/modinfodialogconflicts.cpp | 11 ++- src/modinfodialogconflicts.h | 4 +- src/modinfodialogesps.cpp | 4 +- src/modinfodialogesps.h | 2 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfiletree.h | 2 +- src/modinfodialogimages.cpp | 5 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 4 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 29 +++++- src/modinfodialogtab.h | 11 ++- src/modinfodialogtextfiles.cpp | 12 +-- src/modinfodialogtextfiles.h | 6 +- src/resources.qrc | 210 ++++++++++++++++++++-------------------- 20 files changed, 258 insertions(+), 288 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 71e514b2..be7d4aa4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,24 +19,8 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" -#include "mainwindow.h" - -#include "modidlineedit.h" -#include "iplugingame.h" -#include "nexusinterface.h" -#include "report.h" -#include "utility.h" -#include "messagedialog.h" -#include "bbcode.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "categories.h" +#include "plugincontainer.h" #include "organizercore.h" -#include "pluginlistsortproxy.h" -#include "previewgenerator.h" -#include "previewdialog.h" -#include "texteditor.h" - #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -45,23 +29,6 @@ along with Mod Organizer. If not, see . #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - - using namespace MOBase; using namespace MOShared; @@ -144,13 +111,32 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) - : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) +ModInfoDialog::ModInfoDialog( + ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, + PluginContainer *pluginContainer, QWidget *parent) : + TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), + m_ModInfo(modInfo), m_RootPath(modInfo->absolutePath()), + m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), + m_Origin(nullptr) { ui->setupUi(this); + auto* ds = m_OrganizerCore->directoryStructure(); + if (ds->originExists(ToWString(m_ModInfo->name()))) { + m_Origin = &ds->getOriginByName(ToWString(m_ModInfo->name())); + if (m_Origin->isDisabled()) { + m_Origin = nullptr; + } + } + + this->setWindowTitle(m_ModInfo->name()); + this->setWindowModality(Qt::WindowModal); + + auto* sc = new QShortcut(QKeySequence::Delete, this); + connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + m_tabs = createTabs(); + bool tabSelected = false; for (std::size_t i=0; i(i)); }); - } - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); + bool enabled = true; - m_RootPath = modInfo->absolutePath(); - - auto* sc = new QShortcut(QKeySequence::Delete, this); - connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); - - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(modInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; + if (unmanaged) { + enabled = m_tabs[i]->canHandleUnmanaged(); + } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { + enabled = m_tabs[i]->canHandleSeparators(); } - } - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } - else if (unmanaged) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } else { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); - ui->tabWidget->setTabEnabled(TAB_INIFILES, true); - ui->tabWidget->setTabEnabled(TAB_IMAGES, true); - ui->tabWidget->setTabEnabled(TAB_ESPS, true); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, true); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, true); - } + ui->tabWidget->setTabEnabled(static_cast(i), enabled); - // activate first enabled tab - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->isTabEnabled(i)) { - ui->tabWidget->setCurrentIndex(i); - break; + if (!tabSelected && enabled) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + tabSelected = true; } } @@ -234,23 +176,21 @@ ModInfoDialog::~ModInfoDialog() delete ui; } -template -std::vector> createTabsImpl( - OrganizerCore& oc, PluginContainer& plugin, - ModInfoDialog* self, Ui::ModInfoDialog* ui) +std::vector> ModInfoDialog::createTabs() { std::vector> v; - (v.push_back(std::make_unique(oc, plugin, self, ui)), ...); - return v; -} + v.push_back(createTab(TAB_TEXTFILES)); + v.push_back(createTab(TAB_INIFILES)); + v.push_back(createTab(TAB_IMAGES)); + v.push_back(createTab(TAB_ESPS)); + v.push_back(createTab(TAB_CONFLICTS)); + v.push_back(createTab(TAB_CATEGORIES)); + v.push_back(createTab(TAB_NEXUS)); + v.push_back(createTab(TAB_NOTES)); + v.push_back(createTab(TAB_FILETREE)); -std::vector> ModInfoDialog::createTabs() -{ - return createTabsImpl< - TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab, NotesTab, FileTreeTab>( - *m_OrganizerCore, *m_PluginContainer, this, ui); + return v; } int ModInfoDialog::exec() @@ -259,16 +199,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -int ModInfoDialog::tabIndex(const QString &tabId) -{ - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; -} - void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); @@ -309,7 +239,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) } // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad + QTabBar *tabBar = ui->tabWidget->tabBar(); ui->tabWidget->blockSignals(true); for (int newPos = 0; newPos < count; ++newPos) { QString tabId = tabIds.at(newPos); @@ -331,6 +261,16 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +int ModInfoDialog::tabIndex(const QString& tabId) +{ + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->widget(i)->objectName() == tabId) { + return i; + } + } + return -1; +} + void ModInfoDialog::onDeleteShortcut() { for (auto& t : m_tabs) { @@ -373,9 +313,8 @@ void ModInfoDialog::on_closeButton_clicked() void ModInfoDialog::openTab(int tab) { - QTabWidget *tabWidget = findChild("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); + if (ui->tabWidget->isTabEnabled(tab)) { + ui->tabWidget->setCurrentIndex(tab); } } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 49007c87..020e7958 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -23,36 +23,15 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" -#include "plugincontainer.h" -#include "organizercore.h" -#include "filterwidget.h" #include "filerenamer.h" -#include "expanderwidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Ui { - class ModInfoDialog; -} - -class QFileSystemModel; -class QTreeView; -class CategoryFactory; -class TextEditor; -class ModInfoDialogTab; +namespace Ui { class ModInfoDialog; } +namespace MOShared { class FilesOrigin; } + +class PluginContainer; +class OrganizerCore; +class Settings; +class ModInfoDialogTab; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); @@ -131,9 +110,6 @@ signals: void modOpenPrev(int tab=-1); void originModified(int originID); -private: - int tabIndex(const QString &tabId); - private slots: void on_closeButton_clicked(); void on_tabWidget_currentChanged(int index); @@ -141,8 +117,6 @@ private slots: void on_prevButton_clicked(); private: - using FileEntry = MOShared::FileEntry; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; std::vector> m_tabs; @@ -157,6 +131,14 @@ private: void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void onDeleteShortcut(); + int tabIndex(const QString &tabId); + + template + std::unique_ptr createTab(int index) + { + return std::make_unique( + *m_OrganizerCore, *m_PluginContainer, this, ui, index); + } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 65b89621..93550de3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 8 + 0 true @@ -1148,6 +1148,9 @@ p, li { white-space: pre-wrap; } QAbstractItemView::ExtendedSelection + + true +
diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 69c7c6b5..321c22b8 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -5,8 +5,8 @@ CategoriesTab::CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -35,6 +35,11 @@ void CategoriesTab::update() updatePrimary(); } +bool CategoriesTab::canHandleSeparators() const +{ + return true; +} + void CategoriesTab::add( const CategoryFactory &factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 76426a5d..29d0b2a5 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -7,10 +7,11 @@ class CategoriesTab : public ModInfoDialogTab public: CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void add( diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index adde27ca..15bb7ed4 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -2,6 +2,8 @@ #include "ui_modinfodialog.h" #include "modinfodialog.h" #include "utility.h" +#include "settings.h" +#include "organizercore.h" using namespace MOShared; using namespace MOBase; @@ -131,8 +133,8 @@ public: ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) : - ModInfoDialogTab(oc, plugin, parent, ui), + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( @@ -174,6 +176,11 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +bool ConflictsTab::canHandleUnmanaged() const +{ + return true; +} + void ConflictsTab::changeItemsVisibility( const QList& items, bool visible) { diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 9c011163..a05682ba 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -5,6 +5,7 @@ #include "expanderwidget.h" #include "filterwidget.h" #include "directoryentry.h" +#include class ConflictsTab; class OrganizerCore; @@ -96,13 +97,14 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + bool canHandleUnmanaged() const override; void openItems(const QList& items); void previewItems(const QList& items); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index ea7eb3b0..dd4fff0b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -124,8 +124,8 @@ private: ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index e1a7a4f7..d8c8997e 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -12,7 +12,7 @@ class ESPsTab : public ModInfoDialogTab public: ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index b73a9e24..3e233ccc 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -15,8 +15,8 @@ const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_fs(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index dcc096fe..d0c36edc 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -8,7 +8,7 @@ class FileTreeTab : public ModInfoDialogTab public: FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1c7dcc1f..332a0984 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -82,8 +82,9 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_image(new ScalableImage) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), + m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); ui->imagesThumbnails->setLayout(new QVBoxLayout); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 7853935a..689b8e93 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -36,7 +36,7 @@ class ImagesTab : public ModInfoDialogTab public: ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 55b55439..9d51871c 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -9,8 +9,8 @@ NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_requestStarted(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 2e328c6d..7fe10171 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -35,7 +35,7 @@ class NexusTab : public ModInfoDialogTab public: NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 58745220..1b7fadbb 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,8 +5,9 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), m_origin(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), + m_origin(nullptr), m_tabIndex(index) { } @@ -42,6 +43,16 @@ bool ModInfoDialogTab::deleteRequested() return false; } +bool ModInfoDialogTab::canHandleSeparators() const +{ + return false; +} + +bool ModInfoDialogTab::canHandleUnmanaged() const +{ + return false; +} + void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; @@ -58,6 +69,11 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } +int ModInfoDialogTab::tabIndex() const +{ + return m_tabIndex; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -88,8 +104,8 @@ void ModInfoDialogTab::emitModOpen(QString name) NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -107,6 +123,11 @@ void NotesTab::update() ui->notesEdit->setText(mod()->notes()); } +bool NotesTab::canHandleSeparators() const +{ + return true; +} + void NotesTab::onComments() { mod()->setComments(ui->commentsEdit->text()); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 0dc977a8..1f99344f 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -29,11 +29,16 @@ public: virtual void restoreState(const Settings& s); virtual bool deleteRequested(); + virtual bool canHandleSeparators() const; + virtual bool canHandleUnmanaged() const; + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; + int tabIndex() const; + signals: void originModified(int originID); void modOpen(QString name); @@ -43,7 +48,7 @@ protected: ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); OrganizerCore& core(); PluginContainer& plugin(); @@ -59,6 +64,7 @@ private: QWidget* m_parent; ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; + int m_tabIndex; }; @@ -67,10 +73,11 @@ class NotesTab : public ModInfoDialogTab public: NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void onComments(); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index f48557b0..fddfafba 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -23,9 +23,9 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui), m_list(list), m_editor(e) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -113,9 +113,9 @@ void GenericFilesTab::select(FileListItem* item) TextFilesTab::TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -137,9 +137,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c IniFilesTab::IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 0dc5ec89..f618a6bb 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -23,7 +23,7 @@ protected: GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -39,7 +39,7 @@ class TextFilesTab : public GenericFilesTab public: TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -51,7 +51,7 @@ class IniFilesTab : public GenericFilesTab public: IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; diff --git a/src/resources.qrc b/src/resources.qrc index 8645b27e..6fc33293 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,106 +1,108 @@ - - resources/help-browser.png - resources/list-add.png - resources/document-save.png - resources/edit-find-replace.png - resources/go-jump.png - resources/media-playback-start.png - resources/process-stop.png - resources/system-search.png - resources/view-refresh.png - resources/system-installer.png - resources/start-here.png - resources/list-remove.png - resources/document-properties.png - resources/go-up.png - resources/go-down.png - resources/switch-instance-icon.png - resources/contact-new.png - resources/preferences-system.png - resources/application-x-executable.png - resources/dialog-information.png - resources/emblem-readonly.png - resources/go-next_16.png - resources/go-previous_16.png - resources/view-refresh_16.png - resources/software-update-available.png - resources/emblem-important.png - resources/check.png - resources/dialog-warning.png - resources/symbol-backup.png - resources/applications-accessories.png - resources/emblem-unreadable.png - resources/internet-web-browser.png - resources/system-software-update.png - resources/help-browser_32.png - resources/system-installer.png - resources/function.png - resources/plugins.png - resources/edit-clear.png - resources/dynamic-blue-right.png - resources/icon-favorite.png - resources/emblem-favorite.png - resources/error.png - resources/show.png - splash.png - resources/conflict-mixed.png - resources/conflict-overwrite.png - resources/conflict-overwritten.png - resources/conflict-redundant.png - resources/conflict-mixed-blue.png - resources/conflict-overwrite-blue.png - resources/red-archive-conflict-loser.png - resources/accessories-text-editor.png - resources/x-office-calendar.png - resources/dialog-warning_16.png - resources/mail-attachment.png - resources/document-save_32.png - resources/edit-undo.png - resources/arrange-boxes.png - resources/badge_1.png - resources/badge_2.png - resources/badge_3.png - resources/badge_4.png - resources/badge_5.png - resources/badge_6.png - resources/badge_7.png - resources/badge_8.png - resources/badge_9.png - resources/badge_more.png - resources/status_active.png - resources/status_awaiting.png - resources/status_inactive.png - resources/mo_icon.png - resources/package.png - resources/switch-instance-icon.png - resources/open-Folder-Icon.png - resources/multiply-red.png - resources/archive-conflict-loser.png - resources/archive-conflict-mixed.png - resources/archive-conflict-neutral.png - resources/archive-conflict-winner.png - resources/game-warning.png - resources/game-warning-16.png - resources/tracked.png - - - resources/contents/jigsaw-piece.png - resources/contents/hand-of-god.png - resources/contents/empty-chessboard.png - resources/contents/double-quaver.png - resources/contents/lyre.png - resources/contents/usable.png - resources/contents/checkbox-tree.png - resources/contents/tinker.png - resources/contents/breastplate.png - resources/contents/conversation.png - resources/contents/locked-chest.png - resources/contents/config.png - resources/contents/feather-and-scroll.png - resources/contents/xedit.png - - - qt.conf - + + resources/save.svg + resources/word-wrap.svg + resources/help-browser.png + resources/list-add.png + resources/document-save.png + resources/edit-find-replace.png + resources/go-jump.png + resources/media-playback-start.png + resources/process-stop.png + resources/system-search.png + resources/view-refresh.png + resources/system-installer.png + resources/start-here.png + resources/list-remove.png + resources/document-properties.png + resources/go-up.png + resources/go-down.png + resources/switch-instance-icon.png + resources/contact-new.png + resources/preferences-system.png + resources/application-x-executable.png + resources/dialog-information.png + resources/emblem-readonly.png + resources/go-next_16.png + resources/go-previous_16.png + resources/view-refresh_16.png + resources/software-update-available.png + resources/emblem-important.png + resources/check.png + resources/dialog-warning.png + resources/symbol-backup.png + resources/applications-accessories.png + resources/emblem-unreadable.png + resources/internet-web-browser.png + resources/system-software-update.png + resources/help-browser_32.png + resources/system-installer.png + resources/function.png + resources/plugins.png + resources/edit-clear.png + resources/dynamic-blue-right.png + resources/icon-favorite.png + resources/emblem-favorite.png + resources/error.png + resources/show.png + splash.png + resources/conflict-mixed.png + resources/conflict-overwrite.png + resources/conflict-overwritten.png + resources/conflict-redundant.png + resources/conflict-mixed-blue.png + resources/conflict-overwrite-blue.png + resources/red-archive-conflict-loser.png + resources/accessories-text-editor.png + resources/x-office-calendar.png + resources/dialog-warning_16.png + resources/mail-attachment.png + resources/document-save_32.png + resources/edit-undo.png + resources/arrange-boxes.png + resources/badge_1.png + resources/badge_2.png + resources/badge_3.png + resources/badge_4.png + resources/badge_5.png + resources/badge_6.png + resources/badge_7.png + resources/badge_8.png + resources/badge_9.png + resources/badge_more.png + resources/status_active.png + resources/status_awaiting.png + resources/status_inactive.png + resources/mo_icon.png + resources/package.png + resources/switch-instance-icon.png + resources/open-Folder-Icon.png + resources/multiply-red.png + resources/archive-conflict-loser.png + resources/archive-conflict-mixed.png + resources/archive-conflict-neutral.png + resources/archive-conflict-winner.png + resources/game-warning.png + resources/game-warning-16.png + resources/tracked.png + + + resources/contents/jigsaw-piece.png + resources/contents/hand-of-god.png + resources/contents/empty-chessboard.png + resources/contents/double-quaver.png + resources/contents/lyre.png + resources/contents/usable.png + resources/contents/checkbox-tree.png + resources/contents/tinker.png + resources/contents/breastplate.png + resources/contents/conversation.png + resources/contents/locked-chest.png + resources/contents/config.png + resources/contents/feather-and-scroll.png + resources/contents/xedit.png + + + qt.conf + -- cgit v1.3.1 From eb8140afadc5aa4e6d1d2611f69dc6e38f469978 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 07:28:02 -0400 Subject: grey out tab names when they have no data remove tabs if they're can't handle the selected mod next/previous now load mods in place without reopening the dialog tab reordering is broken --- src/mainwindow.cpp | 104 +++++++----- src/mainwindow.h | 5 +- src/modinfodialog.cpp | 358 ++++++++++++++++++++++++++-------------- src/modinfodialog.h | 60 ++++--- src/modinfodialogcategories.cpp | 2 + src/modinfodialogconflicts.cpp | 7 +- src/modinfodialogconflicts.h | 3 +- src/modinfodialogesps.cpp | 2 + src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogimages.cpp | 12 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 2 + src/modinfodialogtab.cpp | 22 ++- src/modinfodialogtab.h | 3 + src/modinfodialogtextfiles.cpp | 2 + 15 files changed, 375 insertions(+), 213 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f9bfaafb..67dc8418 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,18 +3223,14 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog( - modInfo, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), - &m_OrganizerCore, &m_PluginContainer, this); - - connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenNext(int)), this, SLOT(modOpenNext(int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + dialog.setMod(modInfo); + //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { - dialog.openTab(tab); + dialog.setTab(tab); } dialog.restoreState(m_OrganizerCore.settings()); @@ -3244,16 +3240,6 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, dialog.restoreGeometry(settings.value(key).toByteArray()); } - //If no tab was specified use the first tab from the left based on the user order. - if (tab == -1) { - for (int i = 0; i < dialog.findChild("tabWidget")->count(); ++i) { - if (dialog.findChild("tabWidget")->isTabEnabled(i)) { - dialog.findChild("tabWidget")->setCurrentIndex(i); - break; - } - } - } - dialog.exec(); dialog.saveState(m_OrganizerCore.settings()); settings.setValue(key, dialog.saveGeometry()); @@ -3296,43 +3282,71 @@ void MainWindow::setWindowEnabled(bool enabled) } -void MainWindow::modOpenNext(int tab) +ModInfo::Ptr MainWindow::nextModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenNext(tab); - } else { - displayModInformation(m_ContextRow,tab); + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } -void MainWindow::modOpenPrev(int tab) +ModInfo::Ptr MainWindow::previousModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + int row = index.row() - 1; + if (row == -1) { + row = m_ModListSortProxy->rowCount() - 1; + } + + index = m_ModListSortProxy->index(row, 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenPrev(tab); - } else { - displayModInformation(m_ContextRow,tab); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } void MainWindow::displayModInformation(const QString &modName, int tab) diff --git a/src/mainwindow.h b/src/mainwindow.h index b6283a26..f204211e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -160,6 +160,9 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + ModInfo::Ptr nextModInList(); + ModInfo::Ptr previousModInList(); + public slots: void displayColumnSelection(const QPoint &pos); @@ -549,8 +552,6 @@ private slots: void deselectFilters(); void displayModInformation(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index be7d4aa4..ad704ce8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_modinfodialog.h" #include "plugincontainer.h" #include "organizercore.h" +#include "mainwindow.h" #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -34,23 +35,6 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; - -class ModFileListWidget : public QListWidgetItem { - friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); -public: - ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) - : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} -private: - int m_SortValue; -}; - - -static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) -{ - return LHS.m_SortValue < RHS.m_SortValue; -} - - bool canPreviewFile( PluginContainer& pluginContainer, bool isArchive, const QString& filename) { @@ -111,74 +95,54 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } +ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) + : tab(std::move(tab)), realPos(-1), widget(nullptr) +{ +} + ModInfoDialog::ModInfoDialog( - ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, - PluginContainer *pluginContainer, QWidget *parent) : - TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), - m_ModInfo(modInfo), m_RootPath(modInfo->absolutePath()), - m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), - m_Origin(nullptr) + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + TutorableDialog("ModInfoDialog", mw), + ui(new Ui::ModInfoDialog), m_mainWindow(mw), + m_core(core), m_plugin(plugin), m_initialTab(-1) { ui->setupUi(this); - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(m_ModInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(m_ModInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; - } - } - - this->setWindowTitle(m_ModInfo->name()); - this->setWindowModality(Qt::WindowModal); - auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); m_tabs = createTabs(); - bool tabSelected = false; - for (std::size_t i=0; itabWidget->count(); ++i) { + if (static_cast(i) >= m_tabs.size()) { + qCritical() << "mod info dialog has more tabs than expected"; + break; + } + + auto& tabInfo = m_tabs[static_cast(i)]; + tabInfo.widget = ui->tabWidget->widget(i); + tabInfo.caption = ui->tabWidget->tabText(i); + tabInfo.icon = ui->tabWidget->tabIcon(i); + tabInfo.realPos = i; + connect( - m_tabs[i].get(), &ModInfoDialogTab::originModified, + tabInfo.tab.get(), &ModInfoDialogTab::originModified, [&](int originID){ emit originModified(originID); }); connect( - m_tabs[i].get(), &ModInfoDialogTab::modOpen, + tabInfo.tab.get(), &ModInfoDialogTab::modOpen, [&](const QString& name){ - close(); - emit modOpen(name, static_cast(i)); + setMod(name); + update(); }); - - bool enabled = true; - - if (unmanaged) { - enabled = m_tabs[i]->canHandleUnmanaged(); - } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { - enabled = m_tabs[i]->canHandleSeparators(); - } - - ui->tabWidget->setTabEnabled(static_cast(i), enabled); - - if (!tabSelected && enabled) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - tabSelected = true; - } - } - - for (auto& tab : m_tabs) { - tab->setMod(m_ModInfo, m_Origin); } } -ModInfoDialog::~ModInfoDialog() -{ - delete ui; -} +ModInfoDialog::~ModInfoDialog() = default; -std::vector> ModInfoDialog::createTabs() +std::vector ModInfoDialog::createTabs() { - std::vector> v; + std::vector v; v.push_back(createTab(TAB_TEXTFILES)); v.push_back(createTab(TAB_INIFILES)); @@ -195,31 +159,206 @@ std::vector> ModInfoDialog::createTabs() int ModInfoDialog::exec() { - refreshLists(); + update(); return TutorableDialog::exec(); } +void ModInfoDialog::setMod(ModInfo::Ptr mod) +{ + m_mod = mod; +} + +void ModInfoDialog::setMod(const QString& name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + qCritical() << "failed to resolve mod name " << name; + return; + } + + auto mod = ModInfo::getByIndex(index); + if (!mod) { + qCritical() << "mod by index " << index << " is null"; + return; + } + + setMod(mod); +} + +void ModInfoDialog::setTab(int index) +{ + if (!isVisible()) { + m_initialTab = index; + return; + } + + switchToTab(index); +} + +void ModInfoDialog::update() +{ + setWindowTitle(m_mod->name()); + setTabsVisibility(); + updateTabs(); + feedFiles(); + setTabsColors(); + + if (m_initialTab >= 0) { + switchToTab(m_initialTab); + m_initialTab = -1; + } +} + +void ModInfoDialog::setTabsVisibility() +{ + std::vector visibility(m_tabs.size()); + bool changed = false; + + for (std::size_t i=0; ihasFlag(ModInfo::FLAG_FOREIGN)) { + visible = tabInfo.tab->canHandleUnmanaged(); + } else if (m_mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + visible = tabInfo.tab->canHandleSeparators(); + } + + const auto currentlyVisible = (ui->tabWidget->indexOf(tabInfo.widget) != -1); + + if (visible != currentlyVisible) { + changed = true; + } + + visibility[i] = visible; + } + + if (!changed) { + return; + } + + // remember selection + const int sel = ui->tabWidget->currentIndex(); + + // remove all tabs + ui->tabWidget->clear(); + + // add visible tabs + for (std::size_t i=0; itabWidget->addTab(m_tabs[i].widget, m_tabs[i].icon, m_tabs[i].caption); + + if (static_cast(i) == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } + } + } +} + +void ModInfoDialog::updateTabs() +{ + auto* origin = getOrigin(); + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->setMod(m_mod, origin); + tabInfo.tab->clear(); + tabInfo.tab->update(); + } +} + +void ModInfoDialog::feedFiles() +{ + const auto rootPath = m_mod->absolutePath(); + + if (rootPath.length() > 0) { + QDirIterator dirIterator(rootPath, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->feedFile(rootPath, fileName)) { + break; + } + } + } + } +} + +void ModInfoDialog::setTabsColors() +{ + for (const auto& tabInfo : m_tabs) { + const auto c = tabInfo.tab->hasData() ? + QColor::Invalid : + ui->tabWidget->palette().color(QPalette::Disabled, QPalette::WindowText); + + ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); + } +} + +void ModInfoDialog::switchToTab(std::size_t index) +{ + if (index >= m_tabs.size()) { + qCritical() << "tab index " << index << "out of range"; + return; + } + + if (ui->tabWidget->indexOf(m_tabs[index].widget) == -1) { + qCritical() << "can't switch to tab " << index << ", not available"; + return; + } + + ui->tabWidget->setCurrentIndex(m_tabs[index].realPos); +} + +MOShared::FilesOrigin* ModInfoDialog::getOrigin() +{ + MOShared::FilesOrigin* origin = nullptr; + + auto* ds = m_core->directoryStructure(); + if (ds->originExists(ToWString(m_mod->name()))) { + auto* origin = &ds->getOriginByName(ToWString(m_mod->name())); + if (!origin->isDisabled()) { + return origin; + } + } + + return nullptr; +} + void ModInfoDialog::saveState(Settings& s) const { - s.directInterface().setValue("mod_info_tabs", saveTabState()); + //s.directInterface().setValue("mod_info_tabs", saveTabState()); - for (const auto& tab : m_tabs) { - tab->saveState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->saveState(s); } } void ModInfoDialog::restoreState(const Settings& s) { - restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); + //restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - for (const auto& tab : m_tabs) { - tab->restoreState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->restoreState(s); } } +QByteArray ModInfoDialog::saveTabState() const +{ + QByteArray result; + /*QDataStream stream(&result, QIODevice::WriteOnly); + stream << ui->tabWidget->count(); + for (int i = 0; i < ui->tabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName(); + }*/ + + return result; +} + void ModInfoDialog::restoreTabState(const QByteArray &state) { - QDataStream stream(state); + /*QDataStream stream(state); int count = 0; stream >> count; @@ -232,9 +371,9 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) tabIds.append(tabId); int oldPos = tabIndex(tabId); if (oldPos != -1) { - m_RealTabPos[newPos] = oldPos; + m_realTabPos[newPos] = oldPos; } else { - m_RealTabPos[newPos] = newPos; + m_realTabPos[newPos] = newPos; } } @@ -246,19 +385,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) int oldPos = tabIndex(tabId); tabBar->moveTab(oldPos, newPos); } - ui->tabWidget->blockSignals(false); -} - -QByteArray ModInfoDialog::saveTabState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); - } - - return result; + ui->tabWidget->blockSignals(false);*/ } int ModInfoDialog::tabIndex(const QString& tabId) @@ -273,37 +400,17 @@ int ModInfoDialog::tabIndex(const QString& tabId) void ModInfoDialog::onDeleteShortcut() { - for (auto& t : m_tabs) { - if (t->deleteRequested()) { + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->deleteRequested()) { break; } } } -void ModInfoDialog::refreshLists() -{ - for (auto& tab : m_tabs) { - tab->update(); - } - - if (m_RootPath.length() > 0) { - QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - for (auto& tab : m_tabs) { - if (tab->feedFile(m_RootPath, fileName)) { - break; - } - } - } - } -} - void ModInfoDialog::on_closeButton_clicked() { - for (auto& tab : m_tabs) { - if (!tab->canClose()) { + for (auto& tabInfo : m_tabs) { + if (!tabInfo.tab->canClose()) { return; } } @@ -311,31 +418,28 @@ void ModInfoDialog::on_closeButton_clicked() close(); } -void ModInfoDialog::openTab(int tab) -{ - if (ui->tabWidget->isTabEnabled(tab)) { - ui->tabWidget->setCurrentIndex(tab); - } -} - void ModInfoDialog::on_tabWidget_currentChanged(int index) { } void ModInfoDialog::on_nextButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->nextModInList(); + if (mod == m_mod) { + return; + } - emit modOpenNext(tab); - this->accept(); + setMod(mod); + update(); } void ModInfoDialog::on_prevButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->previousModInList(); + if (mod == m_mod) { + return; + } - emit modOpenPrev(tab); - this->accept(); + setMod(mod); + update(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 020e7958..1cefc71a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -32,7 +32,7 @@ class PluginContainer; class OrganizerCore; class Settings; class ModInfoDialogTab; - +class MainWindow; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); @@ -71,10 +71,7 @@ public: * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog( - ModInfo::Ptr modInfo, - bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, - QWidget *parent = 0); + ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); ~ModInfoDialog(); @@ -92,12 +89,9 @@ public: **/ const int getModID() const; - /** - * @brief open the specified tab in the dialog if it's enabled - * - * @param tab the tab to activate - **/ - void openTab(int tab); + void setMod(ModInfo::Ptr mod); + void setMod(const QString& name); + void setTab(int index); int exec() override; @@ -105,9 +99,6 @@ public: void restoreState(const Settings& s); signals: - void modOpen(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void originModified(int originID); private slots: @@ -117,27 +108,42 @@ private slots: void on_prevButton_clicked(); private: - Ui::ModInfoDialog *ui; - ModInfo::Ptr m_ModInfo; - std::vector> m_tabs; - QString m_RootPath; - OrganizerCore *m_OrganizerCore; - PluginContainer *m_PluginContainer; - MOShared::FilesOrigin *m_Origin; - std::map m_RealTabPos; - - std::vector> createTabs(); - void refreshLists(); + struct TabInfo + { + std::unique_ptr tab; + int realPos; + QWidget* widget; + QString caption; + QIcon icon; + + TabInfo(std::unique_ptr tab); + }; + + std::unique_ptr ui; + MainWindow* m_mainWindow; + ModInfo::Ptr m_mod; + OrganizerCore* m_core; + PluginContainer* m_plugin; + std::vector m_tabs; + int m_initialTab; + + std::vector createTabs(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; + void update(); void onDeleteShortcut(); int tabIndex(const QString &tabId); + MOShared::FilesOrigin* getOrigin(); + void setTabsVisibility(); + void updateTabs(); + void feedFiles(); + void setTabsColors(); + void switchToTab(std::size_t index); template std::unique_ptr createTab(int index) { - return std::make_unique( - *m_OrganizerCore, *m_PluginContainer, this, ui, index); + return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); } }; diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 321c22b8..bce1162b 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -22,6 +22,7 @@ void CategoriesTab::clear() { ui->categories->clear(); ui->primaryCategories->clear(); + setHasData(false); } void CategoriesTab::update() @@ -33,6 +34,7 @@ void CategoriesTab::update() ui->categories->invisibleRootItem(), 0); updatePrimary(); + setHasData(ui->primaryCategories->count() > 0); } bool CategoriesTab::canHandleSeparators() const diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 15bb7ed4..dde00354 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -148,7 +148,7 @@ ConflictsTab::ConflictsTab( void ConflictsTab::update() { - m_general.update(); + setHasData(m_general.update()); m_advanced.update(); } @@ -156,6 +156,7 @@ void ConflictsTab::clear() { m_general.clear(); m_advanced.clear(); + setHasData(false); } void ConflictsTab::saveState(Settings& s) @@ -572,7 +573,7 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::update() +bool GeneralConflictsTab::update() { clear(); @@ -616,6 +617,8 @@ void GeneralConflictsTab::update() ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); ui->noConflictCount->display(numNonConflicting); + + return (numOverwrite > 0 || numOverwritten > 0); } QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a05682ba..38fa6a74 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -22,7 +22,7 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void update(); + bool update(); signals: void modOpen(QString name); @@ -100,7 +100,6 @@ public: QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; - void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index dd4fff0b..d0dcaf2b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -138,6 +138,7 @@ void ESPsTab::clear() { ui->inactiveESPList->clear(); ui->activeESPList->clear(); + setHasData(false); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -158,6 +159,7 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) ui->inactiveESPList->addItem(item); } + setHasData(true); return true; } } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 3e233ccc..dae37f25 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -49,7 +49,9 @@ FileTreeTab::FileTreeTab( void FileTreeTab::clear() { m_fs->setRootPath({}); - //ui->filetree-> + + // always has data; even if the mod is empty, it still has a meta.ini + setHasData(true); } void FileTreeTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 332a0984..9a60fc8e 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -105,6 +105,7 @@ void ImagesTab::clear() } static_cast(ui->imagesThumbnails->layout())->addStretch(1); + setHasData(false); } bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -115,7 +116,10 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - add(fullPath); + if (add(fullPath)) { + setHasData(true); + } + return true; } } @@ -123,13 +127,13 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } -void ImagesTab::add(const QString& fullPath) +bool ImagesTab::add(const QString& fullPath) { QImage image = QImage(fullPath); if (image.isNull()) { qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return; + return false; } auto* thumbnail = new ScalableImage(std::move(image)); @@ -140,6 +144,8 @@ void ImagesTab::add(const QString& fullPath) static_cast(ui->imagesThumbnails->layout())->insertWidget( ui->imagesThumbnails->layout()->count() - 1, thumbnail); + + return true; } void ImagesTab::onClicked(const QImage& original) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 689b8e93..60271da0 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -44,7 +44,7 @@ public: private: ScalableImage* m_image; - void add(const QString& fullPath); + bool add(const QString& fullPath); void onClicked(const QImage& image); }; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 9d51871c..61b868d1 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -48,6 +48,7 @@ void NexusTab::clear() ui->version->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); ui->url->clear(); + setHasData(false); } void NexusTab::update() @@ -88,6 +89,7 @@ void NexusTab::update() (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); updateWebpage(); + setHasData(mod()->getNexusID() >= 0); } void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 1b7fadbb..e50aec29 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -7,7 +7,7 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int index) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabIndex(index) + m_origin(nullptr), m_tabIndex(index), m_hasData(false) { } @@ -74,6 +74,11 @@ int ModInfoDialogTab::tabIndex() const return m_tabIndex; } +bool ModInfoDialogTab::hasData() const +{ + return m_hasData; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -101,6 +106,11 @@ void ModInfoDialogTab::emitModOpen(QString name) emit modOpen(name); } +void ModInfoDialogTab::setHasData(bool b) +{ + m_hasData = b; +} + NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, @@ -115,12 +125,18 @@ void NotesTab::clear() { ui->commentsEdit->clear(); ui->notesEdit->clear(); + setHasData(false); } void NotesTab::update() { - ui->commentsEdit->setText(mod()->comments()); - ui->notesEdit->setText(mod()->notes()); + const auto comments = mod()->comments(); + const auto notes = mod()->notes(); + + ui->commentsEdit->setText(comments); + ui->notesEdit->setText(notes); + + setHasData(!comments.isEmpty() || !notes.isEmpty()); } bool NotesTab::canHandleSeparators() const diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 1f99344f..8fe7d2d4 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -38,6 +38,7 @@ public: MOShared::FilesOrigin* origin() const; int tabIndex() const; + bool hasData() const; signals: void originModified(int originID); @@ -57,6 +58,7 @@ protected: void emitOriginModified(); void emitModOpen(QString name); + void setHasData(bool b); private: OrganizerCore& m_core; @@ -65,6 +67,7 @@ private: ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; int m_tabIndex; + bool m_hasData; }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index fddfafba..bd175c24 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -42,6 +42,7 @@ void GenericFilesTab::clear() { m_list->clear(); select(nullptr); + setHasData(false); } bool GenericFilesTab::canClose() @@ -76,6 +77,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { m_list->addItem(new FileListItem(rootPath, fullPath)); + setHasData(true); return true; } } -- cgit v1.3.1 From ad4a90692b73bd73fa7a88690a95daa348839add Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 07:37:26 -0400 Subject: fixed crash in conflict tabs --- src/modinfodialogconflicts.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index dde00354..ad3b5e5f 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -481,8 +481,8 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); @@ -692,8 +692,8 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) AdvancedConflictsTab::AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) - : m_tab(tab), ui(ui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) + : m_tab(tab), ui(pui), m_core(oc) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( -- cgit v1.3.1 From 581cfacbbdee17f2b4df8195487e5934702a430e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 09:29:41 -0400 Subject: changed "tab index" to "tab id", this was confusing the order in the widget and the id from the enum fixed reordering --- src/mainwindow.cpp | 2 +- src/modinfodialog.cpp | 208 +++++++++++++++++++++++++++------------- src/modinfodialog.h | 17 ++-- src/modinfodialog.ui | 2 +- src/modinfodialogcategories.cpp | 4 +- src/modinfodialogcategories.h | 2 +- src/modinfodialogconflicts.cpp | 4 +- src/modinfodialogconflicts.h | 2 +- src/modinfodialogesps.cpp | 4 +- src/modinfodialogesps.h | 2 +- src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogfiletree.h | 2 +- src/modinfodialogimages.cpp | 4 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 4 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 8 +- src/modinfodialogtab.h | 6 +- src/modinfodialogtextfiles.cpp | 12 +-- src/modinfodialogtextfiles.h | 6 +- 20 files changed, 184 insertions(+), 113 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 67dc8418..d75e8d9d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3230,7 +3230,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { - dialog.setTab(tab); + dialog.setTab(ModInfoDialog::ETabs(tab)); } dialog.restoreState(m_OrganizerCore.settings()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ad704ce8..c03739ca 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -104,7 +104,7 @@ ModInfoDialog::ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), - m_core(core), m_plugin(plugin), m_initialTab(-1) + m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)) { ui->setupUi(this); @@ -123,7 +123,6 @@ ModInfoDialog::ModInfoDialog( tabInfo.widget = ui->tabWidget->widget(i); tabInfo.caption = ui->tabWidget->tabText(i); tabInfo.icon = ui->tabWidget->tabIcon(i); - tabInfo.realPos = i; connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, @@ -159,7 +158,16 @@ std::vector ModInfoDialog::createTabs() int ModInfoDialog::exec() { - update(); + const auto selectFirst = (m_initialTab == -1); + + update(true); + + if (selectFirst) { + if (ui->tabWidget->count() > 0) { + ui->tabWidget->setCurrentIndex(0); + } + } + return TutorableDialog::exec(); } @@ -185,33 +193,34 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(int index) +void ModInfoDialog::setTab(ETabs id) { if (!isVisible()) { - m_initialTab = index; + m_initialTab = id; return; } - switchToTab(index); + switchToTab(id); } -void ModInfoDialog::update() +void ModInfoDialog::update(bool firstTime) { setWindowTitle(m_mod->name()); - setTabsVisibility(); + setTabsVisibility(firstTime); updateTabs(); feedFiles(); setTabsColors(); if (m_initialTab >= 0) { switchToTab(m_initialTab); - m_initialTab = -1; + m_initialTab = ETabs(-1); } } -void ModInfoDialog::setTabsVisibility() +void ModInfoDialog::setTabsVisibility(bool firstTime) { std::vector visibility(m_tabs.size()); + bool changed = false; for (std::size_t i=0; itabWidget->currentIndex(); - - // remove all tabs - ui->tabWidget->clear(); - - // add visible tabs - for (std::size_t i=0; itabWidget->addTab(m_tabs[i].widget, m_tabs[i].icon, m_tabs[i].caption); + const int selIndex = ui->tabWidget->currentIndex(); + ETabs sel = ETabs(-1); - if (static_cast(i) == sel) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - } + for (const auto& tabInfo : m_tabs) { + if (tabInfo.realPos == selIndex) { + sel = ETabs(tabInfo.tab->tabID()); + break; } } + + reAddTabs(visibility, sel); } void ModInfoDialog::updateTabs() @@ -296,19 +301,16 @@ void ModInfoDialog::setTabsColors() } } -void ModInfoDialog::switchToTab(std::size_t index) +void ModInfoDialog::switchToTab(ETabs id) { - if (index >= m_tabs.size()) { - qCritical() << "tab index " << index << "out of range"; - return; - } - - if (ui->tabWidget->indexOf(m_tabs[index].widget) == -1) { - qCritical() << "can't switch to tab " << index << ", not available"; - return; + for (const auto& tabInfo : m_tabs) { + if (tabInfo.tab->tabID() == id) { + ui->tabWidget->setCurrentIndex(tabInfo.realPos); + return; + } } - ui->tabWidget->setCurrentIndex(m_tabs[index].realPos); + qDebug() << "can't switch to tab " << id << ", not available"; } MOShared::FilesOrigin* ModInfoDialog::getOrigin() @@ -328,7 +330,10 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() void ModInfoDialog::saveState(Settings& s) const { - //s.directInterface().setValue("mod_info_tabs", saveTabState()); + const auto tabState = saveTabState(); + if (!tabState.isEmpty()) { + s.directInterface().setValue("mod_info_tabs", tabState); + } for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); @@ -337,55 +342,120 @@ void ModInfoDialog::saveState(Settings& s) const void ModInfoDialog::restoreState(const Settings& s) { - //restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - for (const auto& tabInfo : m_tabs) { tabInfo.tab->restoreState(s); } } -QByteArray ModInfoDialog::saveTabState() const +QString ModInfoDialog::saveTabState() const { - QByteArray result; - /*QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); - }*/ + if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { + // only save tab state when all tabs are visible + return {}; + } + + QString result; + QTextStream stream(&result); + + for (int i=0; itabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName() << " "; + } + + return result.trimmed(); +} + +std::vector ModInfoDialog::getOrderedTabNames() const +{ + const auto value = Settings::instance() + .directInterface().value("mod_info_tabs"); + + std::vector v; + + if (value.type() == QVariant::ByteArray) { + // old byte array + QDataStream stream(value.toByteArray()); + + int count = 0; + stream >> count; + + for (int i=0; i> s; + v.emplace_back(std::move(s)); + } + } else { + // string list + QString string = value.toString(); + QTextStream stream(&string); + + while (!stream.atEnd()) { + QString s; + stream >> s; + v.emplace_back(std::move(s)); + } + } - return result; + return v; } -void ModInfoDialog::restoreTabState(const QByteArray &state) +void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) { - /*QDataStream stream(state); - int count = 0; - stream >> count; - - QStringList tabIds; - - // first, only determine the new mapping - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId; - stream >> tabId; - tabIds.append(tabId); - int oldPos = tabIndex(tabId); - if (oldPos != -1) { - m_realTabPos[newPos] = oldPos; - } else { - m_realTabPos[newPos] = newPos; + Q_ASSERT(visibility.size() == m_tabs.size()); + + // ordered tab names from settings + const auto orderedNames = getOrderedTabNames(); + + bool canSort = true; + + // gathering visible tabs + std::vector visibleTabs; + for (std::size_t i=0; iobjectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + if (itor == orderedNames.end()) { + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; + } + } } } - // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->tabBar(); - ui->tabWidget->blockSignals(true); - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId = tabIds.at(newPos); - int oldPos = tabIndex(tabId); - tabBar->moveTab(oldPos, newPos); + // sorting tabs + if (canSort) { + std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ + auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); + auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + + // this was checked above + Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); + + return (aItor < bItor); + }); + } + + + ui->tabWidget->clear(); + + // reset real positions + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // add visible tabs + for (std::size_t i=0; i(i); + ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + + if (tabInfo.tab->tabID() == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } } - ui->tabWidget->blockSignals(false);*/ } int ModInfoDialog::tabIndex(const QString& tabId) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 1cefc71a..54e056b8 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -64,7 +64,6 @@ public: TAB_FILETREE }; -public: /** * @brief constructor * @@ -91,7 +90,7 @@ public: void setMod(ModInfo::Ptr mod); void setMod(const QString& name); - void setTab(int index); + void setTab(ETabs id); int exec() override; @@ -125,20 +124,22 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; - int m_initialTab; + ETabs m_initialTab; std::vector createTabs(); - void restoreTabState(const QByteArray &state); - QByteArray saveTabState() const; - void update(); + void restoreTabState(const QString& state); + QString saveTabState() const; + void update(bool firstTime=false); void onDeleteShortcut(); int tabIndex(const QString &tabId); MOShared::FilesOrigin* getOrigin(); - void setTabsVisibility(); + void setTabsVisibility(bool firstTime); void updateTabs(); void feedFiles(); void setTabsColors(); - void switchToTab(std::size_t index); + void switchToTab(ETabs id); + void reAddTabs(const std::vector& visibility, ETabs sel); + std::vector getOrderedTabNames() const; template std::unique_ptr createTab(int index) diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 93550de3..40b3c7b4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1088,7 +1088,7 @@ p, li { white-space: pre-wrap; }
- + Filetree diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index bce1162b..4bd10028 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -5,8 +5,8 @@ CategoriesTab::CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id) { connect( ui->categories, &QTreeWidget::itemChanged, diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 29d0b2a5..738b4e4d 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -7,7 +7,7 @@ class CategoriesTab : public ModInfoDialogTab public: CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; void update() override; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index ad3b5e5f..7f297ec3 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -133,8 +133,8 @@ public: ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : - ModInfoDialogTab(oc, plugin, parent, ui, index), + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 38fa6a74..1f82a7c0 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -97,7 +97,7 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void update() override; void clear() override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index d0dcaf2b..6c4fc4dd 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -124,8 +124,8 @@ private: ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index d8c8997e..e82ed368 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -12,7 +12,7 @@ class ESPsTab : public ModInfoDialogTab public: ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index dae37f25..b57f6b5d 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -15,8 +15,8 @@ const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_fs(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index d0c36edc..2145f298 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -8,7 +8,7 @@ class FileTreeTab : public ModInfoDialogTab public: FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9a60fc8e..f4cdade8 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -82,8 +82,8 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : - ModInfoDialogTab(oc, plugin, parent, ui, index), + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 60271da0..6603660a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -36,7 +36,7 @@ class ImagesTab : public ModInfoDialogTab public: ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 61b868d1..172968ab 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -9,8 +9,8 @@ NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int id) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 7fe10171..ce1ef426 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -35,7 +35,7 @@ class NexusTab : public ModInfoDialogTab public: NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index e50aec29..2f5fbdb8 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,9 +5,9 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) : + QWidget* parent, Ui::ModInfoDialog* ui, int id) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabIndex(index), m_hasData(false) + m_origin(nullptr), m_tabID(id), m_hasData(false) { } @@ -69,9 +69,9 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } -int ModInfoDialogTab::tabIndex() const +int ModInfoDialogTab::tabID() const { - return m_tabIndex; + return m_tabID; } bool ModInfoDialogTab::hasData() const diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 8fe7d2d4..fae5bc41 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -37,7 +37,7 @@ public: ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; - int tabIndex() const; + int tabID() const; bool hasData() const; signals: @@ -49,7 +49,7 @@ protected: ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); OrganizerCore& core(); PluginContainer& plugin(); @@ -66,7 +66,7 @@ private: QWidget* m_parent; ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; - int m_tabIndex; + int m_tabID; bool m_hasData; }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index bd175c24..7c8e84c7 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -23,9 +23,9 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index, + QWidget* parent, Ui::ModInfoDialog* ui, int id, QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui, index), m_list(list), m_editor(e) + : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -115,9 +115,9 @@ void GenericFilesTab::select(FileListItem* item) TextFilesTab::TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( - oc, plugin, parent, ui, index, + oc, plugin, parent, ui, id, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -139,9 +139,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c IniFilesTab::IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( - oc, plugin, parent, ui, index, + oc, plugin, parent, ui, id, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index f618a6bb..75f31d88 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -23,7 +23,7 @@ protected: GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index, + QWidget* parent, Ui::ModInfoDialog* ui, int id, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -39,7 +39,7 @@ class TextFilesTab : public GenericFilesTab public: TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -51,7 +51,7 @@ class IniFilesTab : public GenericFilesTab public: IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + QWidget* parent, Ui::ModInfoDialog* ui, int id); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From f14f2bad3ba7440d6f36657f32d00c89cae54623 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 10:35:31 -0400 Subject: don't update invisible tabs update tabs when origin changes change setting name because 2.2.0 can't handle the text list --- src/modinfodialog.cpp | 57 ++++++++++++++++++++++++++++------------- src/modinfodialog.h | 7 ++--- src/modinfodialogcategories.cpp | 5 ++++ src/modinfodialogcategories.h | 1 + src/modinfodialognexus.cpp | 5 ++++ src/modinfodialognexus.h | 1 + src/modinfodialogtab.cpp | 10 ++++++++ src/modinfodialogtab.h | 3 +++ 8 files changed, 68 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c03739ca..4c169cc4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -100,6 +100,12 @@ ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) { } +bool ModInfoDialog::TabInfo::isVisible() const +{ + return (realPos != -1); +} + + ModInfoDialog::ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : TutorableDialog("ModInfoDialog", mw), @@ -126,7 +132,9 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, - [&](int originID){ emit originModified(originID); }); + [this, i](int originID) { + onOriginModified(static_cast(i), originID); + }); connect( tabInfo.tab.get(), &ModInfoDialogTab::modOpen, @@ -207,9 +215,8 @@ void ModInfoDialog::update(bool firstTime) { setWindowTitle(m_mod->name()); setTabsVisibility(firstTime); + updateTabs(); - feedFiles(); - setTabsColors(); if (m_initialTab >= 0) { switchToTab(m_initialTab); @@ -261,18 +268,29 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) reAddTabs(visibility, sel); } -void ModInfoDialog::updateTabs() +void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); for (auto& tabInfo : m_tabs) { + if (!tabInfo.isVisible()) { + continue; + } + + if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { + continue; + } + tabInfo.tab->setMod(m_mod, origin); tabInfo.tab->clear(); tabInfo.tab->update(); } + + feedFiles(becauseOriginChanged); + setTabsColors(); } -void ModInfoDialog::feedFiles() +void ModInfoDialog::feedFiles(bool becauseOriginChanged) { const auto rootPath = m_mod->absolutePath(); @@ -282,6 +300,14 @@ void ModInfoDialog::feedFiles() QString fileName = dirIterator.next(); for (auto& tabInfo : m_tabs) { + if (!tabInfo.isVisible()) { + continue; + } + + if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { + continue; + } + if (tabInfo.tab->feedFile(rootPath, fileName)) { break; } @@ -332,7 +358,7 @@ void ModInfoDialog::saveState(Settings& s) const { const auto tabState = saveTabState(); if (!tabState.isEmpty()) { - s.directInterface().setValue("mod_info_tabs", tabState); + s.directInterface().setValue("mod_info_tab_order", tabState); } for (const auto& tabInfo : m_tabs) { @@ -366,14 +392,13 @@ QString ModInfoDialog::saveTabState() const std::vector ModInfoDialog::getOrderedTabNames() const { - const auto value = Settings::instance() - .directInterface().value("mod_info_tabs"); + const auto& settings = Settings::instance().directInterface(); std::vector v; - if (value.type() == QVariant::ByteArray) { + if (settings.contains("mod_info_tabs")) { // old byte array - QDataStream stream(value.toByteArray()); + QDataStream stream(settings.value("mod_info_tabs").toByteArray()); int count = 0; stream >> count; @@ -385,7 +410,7 @@ std::vector ModInfoDialog::getOrderedTabNames() const } } else { // string list - QString string = value.toString(); + QString string = settings.value("mod_info_tab_order").toString(); QTextStream stream(&string); while (!stream.atEnd()) { @@ -458,14 +483,10 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } } -int ModInfoDialog::tabIndex(const QString& tabId) +void ModInfoDialog::onOriginModified(std::size_t tabIndex, int originID) { - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; + emit originModified(originID); + updateTabs(true); } void ModInfoDialog::onDeleteShortcut() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 54e056b8..31ea5536 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -116,6 +116,7 @@ private: QIcon icon; TabInfo(std::unique_ptr tab); + bool isVisible() const; }; std::unique_ptr ui; @@ -131,15 +132,15 @@ private: QString saveTabState() const; void update(bool firstTime=false); void onDeleteShortcut(); - int tabIndex(const QString &tabId); MOShared::FilesOrigin* getOrigin(); void setTabsVisibility(bool firstTime); - void updateTabs(); - void feedFiles(); + void updateTabs(bool becauseOriginChanged=false); + void feedFiles(bool becauseOriginChanged); void setTabsColors(); void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + void onOriginModified(std::size_t tabIndex, int originID); template std::unique_ptr createTab(int index) diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 4bd10028..0d739d1f 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -42,6 +42,11 @@ bool CategoriesTab::canHandleSeparators() const return true; } +bool CategoriesTab::usesOriginFiles() const +{ + return false; +} + void CategoriesTab::add( const CategoryFactory &factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 738b4e4d..392023e7 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -12,6 +12,7 @@ public: void clear() override; void update() override; bool canHandleSeparators() const override; + bool usesOriginFiles() const override; private: void add( diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 172968ab..d296e000 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -102,6 +102,11 @@ void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) mod.data(), &ModInfo::modDetailsUpdated, [&]{ onModChanged(); }); } +bool NexusTab::usesOriginFiles() const +{ + return false; +} + void NexusTab::updateVersionColor() { if (mod()->getVersion() != mod()->getNewestVersion()) { diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index ce1ef426..a09f4316 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -42,6 +42,7 @@ public: void clear() override; void update() override; void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + bool usesOriginFiles() const override; private: QMetaObject::Connection m_modConnection; diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 2f5fbdb8..009fb804 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -53,6 +53,11 @@ bool ModInfoDialogTab::canHandleUnmanaged() const return false; } +bool ModInfoDialogTab::usesOriginFiles() const +{ + return true; +} + void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; @@ -158,3 +163,8 @@ void NotesTab::onNotes() mod()->setNotes(ui->notesEdit->toHtml()); } } + +bool NotesTab::usesOriginFiles() const +{ + return false; +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index fae5bc41..c85d2ded 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -27,10 +27,12 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + virtual bool deleteRequested(); virtual bool canHandleSeparators() const; virtual bool canHandleUnmanaged() const; + virtual bool usesOriginFiles() const; virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); @@ -81,6 +83,7 @@ public: void clear() override; void update() override; bool canHandleSeparators() const override; + bool usesOriginFiles() const override; private: void onComments(); -- cgit v1.3.1 From 4ee9f29cdcb3df2a085e02fa29c8ae3ddbd27f1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 12:49:08 -0400 Subject: performance optimizations: text/ini tabs and advanced conflict list use views and custom models call update() after feedFiles() to allow some tabs to have post-processing after feedFiles() --- src/modinfodialog.cpp | 20 +++- src/modinfodialog.h | 2 + src/modinfodialog.ui | 48 +++------ src/modinfodialogconflicts.cpp | 216 +++++++++++++++++++++++++++++++++++------ src/modinfodialogconflicts.h | 6 +- src/modinfodialogtextfiles.cpp | 133 +++++++++++++++++++------ src/modinfodialogtextfiles.h | 13 ++- 7 files changed, 338 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4c169cc4..a407e9b7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -35,6 +35,19 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; + +int naturalCompare(const QString& a, const QString& b) +{ + static QCollator c = []{ + QCollator c; + c.setNumericMode(true); + c.setCaseSensitivity(Qt::CaseInsensitive); + return c; + }(); + + return c.compare(a, b); +} + bool canPreviewFile( PluginContainer& pluginContainer, bool isArchive, const QString& filename) { @@ -283,10 +296,15 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) tabInfo.tab->setMod(m_mod, origin); tabInfo.tab->clear(); - tabInfo.tab->update(); } + feedFiles(becauseOriginChanged); + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->update(); + } + setTabsColors(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 31ea5536..e814dcf4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -42,6 +42,8 @@ bool canUnhideFile(bool isArchive, const QString& filename); FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); +int naturalCompare(const QString& a, const QString& b); + /** * this is a larger dialog used to visualise information about the mod. diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 40b3c7b4..5defcd57 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -60,13 +60,16 @@ - + A list of text-files in the mod directory. A list of text-files in the mod directory like readmes. + + true + @@ -117,13 +120,16 @@ - + This is a list of .ini files in the mod. This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. + + true + @@ -251,6 +257,9 @@ They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. + + true +
@@ -375,6 +384,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + + true + @@ -477,9 +489,6 @@ text-align: left; true - - true - 2 @@ -553,9 +562,6 @@ text-align: left; true - - true - 2 @@ -629,9 +635,6 @@ text-align: left; true - - true - 1 @@ -696,7 +699,7 @@ text-align: left; 0 - + Qt::CustomContextMenu @@ -709,27 +712,6 @@ text-align: left; true - - 3 - - - 243 - - - - Overwrites - - - - - File - - - - - Overwritten by - - diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 7f297ec3..55fbf2e5 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -13,19 +13,6 @@ using namespace MOBase; const int max_scan_for_context_menu = 50; -int naturalCompare(const QString& a, const QString& b) -{ - static QCollator c = []{ - QCollator c; - c.setNumericMode(true); - c.setCaseSensitivity(Qt::CaseInsensitive); - return c; - }(); - - return c.compare(a, b); -} - - class ElideLeftDelegate : public QStyledItemDelegate { public: @@ -581,6 +568,10 @@ bool GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; + QList overwriteItems; + QList overwrittenItems; + QList noConflictItems; + if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); @@ -594,19 +585,19 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( + overwriteItems.append(createOverwriteItem( file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( + noConflictItems.append(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( + overwrittenItems.append(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -614,6 +605,10 @@ bool GeneralConflictsTab::update() } } + ui->overwriteTree->addTopLevelItems(overwriteItems); + ui->overwrittenTree->addTopLevelItems(overwrittenItems); + ui->noConflictTree->addTopLevelItems(noConflictItems); + ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); ui->noConflictCount->display(numNonConflicting); @@ -691,10 +686,166 @@ void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) } +class AdvancedListModel : public QAbstractItemModel +{ +public: + struct Item + { + QString before, relativeName, after; + FileEntry::Index index; + QString fileName; + bool hasAltOrigins; + QString altOrigin; + bool archive; + }; + + void clear() + { + m_items.clear(); + } + + void reserve(std::size_t s) + { + m_items.reserve(s); + } + + QModelIndex index(int row, int col, const QModelIndex& ={}) const override + { + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& parent={}) const override + { + if (parent.isValid()) { + return 0; + } + + return static_cast(m_items.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 3; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_items.size()) { + return {}; + } + + const auto& item = m_items[i]; + + if (index.column() == 0) { + return item.before; + } else if (index.column() == 1) { + return item.relativeName; + } else if (index.column() == 2) { + return item.after; + } + } + + return {}; + } + + QVariant headerData(int col, Qt::Orientation, int role) const + { + if (role == Qt::DisplayRole) { + if (col == 0) { + return tr("Overwrites"); + } else if (col == 1) { + return tr("File"); + } else if (col == 2) { + return tr("Overwritten by"); + } + } + + return {}; + } + + void sort(int col, Qt::SortOrder order=Qt::AscendingOrder) + { + // avoids branching on column/sort order while sorting + auto sortBeforeAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.before, b.before) < 0); + }; + + auto sortBeforeDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.before, b.before) > 0); + }; + + auto sortFileAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.relativeName, b.relativeName) < 0); + }; + + auto sortFileDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.relativeName, b.relativeName) > 0); + }; + + auto sortAfterAsc = [](const auto& a, const auto& b) { + return (naturalCompare(a.after, b.after) < 0); + }; + + auto sortAfterDesc = [](const auto& a, const auto& b) { + return (naturalCompare(a.after, b.after) > 0); + }; + + if (col == 0) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortBeforeAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortBeforeDesc); + } + } else if (col == 1) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortFileAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortFileDesc); + } + } else if (col == 2) { + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAfterAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortAfterDesc); + } + } + + emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + } + + void add(Item item) + { + m_items.emplace_back(std::move(item)); + } + + void finished() + { + emit dataChanged(index(0, 0), index(0, rowCount())); + } + +private: + std::vector m_items; +}; + + AdvancedConflictsTab::AdvancedConflictsTab( ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc) + : m_tab(tab), ui(pui), m_core(oc), m_model(new AdvancedListModel) { + ui->conflictsAdvancedList->setModel(m_model); + // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); @@ -717,9 +868,9 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); - QObject::connect( - ui->conflictsAdvancedList, &QTreeWidget::customContextMenuRequested, - [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); + //QObject::connect( + // ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, + // [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; @@ -727,7 +878,7 @@ AdvancedConflictsTab::AdvancedConflictsTab( void AdvancedConflictsTab::clear() { - ui->conflictsAdvancedList->clear(); + m_model->clear(); } void AdvancedConflictsTab::saveState(Settings& s) @@ -776,7 +927,10 @@ void AdvancedConflictsTab::update() if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); - for (const auto& file : m_tab->origin()->getFiles()) { + const auto& files = m_tab->origin()->getFiles(); + m_model->reserve(files.size()); + + for (const auto& file : files) { const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); const QString fileName = relativeName.mid(0).prepend(rootPath); @@ -784,18 +938,16 @@ void AdvancedConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - auto* advancedItem = createItem( + addItem( file->getIndex(), fileOrigin, archive, fileName, relativeName, alternatives); - - if (advancedItem) { - ui->conflictsAdvancedList->addTopLevelItem(advancedItem); - } } + + m_model->finished(); } } -QTreeWidgetItem* AdvancedConflictsTab::createItem( +void AdvancedConflictsTab::addItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) @@ -888,7 +1040,7 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { - return nullptr; + return; } } @@ -900,9 +1052,9 @@ QTreeWidgetItem* AdvancedConflictsTab::createItem( }); if (!matched) { - return nullptr; + return; } - return new ConflictItem( - {before, relativeName, after}, index, fileName, hasAlts, "", archive); + m_model->add( + {before, relativeName, after, index, fileName, hasAlts, "", archive}); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 1f82a7c0..f499d1a4 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -9,6 +9,7 @@ class ConflictsTab; class OrganizerCore; +class ConflictItem; class GeneralConflictsTab : public QObject { @@ -60,6 +61,8 @@ private: }; +class AdvancedListModel; + class AdvancedConflictsTab : public QObject { Q_OBJECT; @@ -82,8 +85,9 @@ private: Ui::ModInfoDialog* ui; OrganizerCore& m_core; FilterWidget m_filter; + AdvancedListModel* m_model; - QTreeWidgetItem* createItem( + void addItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 7c8e84c7..08b8c988 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,32 +1,109 @@ #include "modinfodialogtextfiles.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include -class FileListItem : public QListWidgetItem +class FileListModel : public QAbstractItemModel { public: - FileListItem(const QString& rootPath, QString fullPath) - : m_fullPath(std::move(fullPath)) + void clear() { - setText(m_fullPath.mid(rootPath.length() + 1)); + m_files.clear(); } - const QString& fullPath() const + QModelIndex index(int row, int col, const QModelIndex& ={}) const override { - return m_fullPath; + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& ={}) const override + { + return static_cast(m_files.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 1; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_files.size()) { + return {}; + } + + return m_files[i].text; + } + + return {}; + } + + void add(const QString& rootPath, QString fullPath) + { + QString text = fullPath.mid(rootPath.length() + 1); + m_files.emplace_back(std::move(fullPath), std::move(text)); + } + + void finished() + { + std::sort(m_files.begin(), m_files.end(), [](const auto& a, const auto& b) { + return (naturalCompare(a.text, b.text) < 0); + }); + + emit dataChanged(index(0, 0), index(0, rowCount())); + } + + QString fullPath(const QModelIndex& index) const + { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_files.size()) { + return {}; + } + + return m_files[i].fullPath; } private: - QString m_fullPath; + struct File + { + QString fullPath; + QString text; + + File(QString fp, QString t) + : fullPath(std::move(fp)), text(std::move(t)) + { + } + }; + + std::deque m_files; }; GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e) + QListView* list, QSplitter* sp, TextEditor* e) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_list(list), m_editor(e), m_model(new FileListModel) { + m_list->setModel(m_model); m_editor->setupToolbar(); sp->setSizes({200, 1}); @@ -34,14 +111,14 @@ GenericFilesTab::GenericFilesTab( sp->setStretchFactor(1, 1); QObject::connect( - m_list, &QListWidget::currentItemChanged, - [&](auto* current, auto* previous){ onSelection(current, previous); }); + m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, + [&](auto current, auto previous){ onSelection(current, previous); }); } void GenericFilesTab::clear() { - m_list->clear(); - select(nullptr); + m_model->clear(); + select({}); setHasData(false); } @@ -76,7 +153,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { - m_list->addItem(new FileListItem(rootPath, fullPath)); + m_model->add(rootPath, fullPath); setHasData(true); return true; } @@ -85,31 +162,31 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } -void GenericFilesTab::onSelection( - QListWidgetItem* current, QListWidgetItem* previous) +void GenericFilesTab::update() { - auto* item = dynamic_cast(current); - if (!item) { - qCritical("TextFilesTab: item is not a FileListItem"); - return; - } + m_model->finished(); +} +void GenericFilesTab::onSelection( + const QModelIndex& current, const QModelIndex& previous) +{ if (!canClose()) { - m_list->setCurrentItem(previous, QItemSelectionModel::Current); + m_list->selectionModel()->select(previous, QItemSelectionModel::Current); return; } - select(item); + select(current); } -void GenericFilesTab::select(FileListItem* item) +void GenericFilesTab::select(const QModelIndex& index) { - if (item) { - m_editor->setEnabled(true); - m_editor->load(item->fullPath()); - } else { + if (!index.isValid()) { m_editor->setEnabled(false); + return; } + + m_editor->setEnabled(true); + m_editor->load(m_model->fullPath(index)); } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 75f31d88..d879c2bd 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -3,9 +3,10 @@ #include "modinfodialogtab.h" #include -#include +#include class FileListItem; +class FileListModel; class TextEditor; class GenericFilesTab : public ModInfoDialogTab @@ -16,21 +17,23 @@ public: void clear() override; bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update() override; protected: - QListWidget* m_list; + QListView* m_list; TextEditor* m_editor; + FileListModel* m_model; GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListWidget* list, QSplitter* splitter, TextEditor* editor); + QListView* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; private: - void onSelection(QListWidgetItem* current, QListWidgetItem* previous); - void select(FileListItem* item); + void onSelection(const QModelIndex& current, const QModelIndex& previous); + void select(const QModelIndex& index); }; -- cgit v1.3.1 From 1e54b4b57682b544bca0bb9d563fdb3d747073f2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 14:50:55 -0400 Subject: switched all conflict lists to QTreeView with custom model --- src/modinfodialog.ui | 56 +-- src/modinfodialogconflicts.cpp | 766 ++++++++++++++++++++++++----------------- src/modinfodialogconflicts.h | 37 +- 3 files changed, 471 insertions(+), 388 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 5defcd57..cdf4c80f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -406,7 +406,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -421,7 +421,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e 100 - + 0 @@ -473,7 +473,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -489,25 +489,6 @@ text-align: left; true - - 2 - - - 200 - - - 365 - - - - File - - - - - Overwritten Mods - - @@ -546,7 +527,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -562,25 +543,6 @@ text-align: left; true - - 2 - - - 200 - - - 365 - - - - File - - - - - Providing Mod - - @@ -619,7 +581,7 @@ text-align: left; - + Qt::CustomContextMenu @@ -635,14 +597,6 @@ text-align: left; true - - 1 - - - - File - - diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 55fbf2e5..d1384d7a 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -10,7 +10,7 @@ using namespace MOBase; // if there are more than 50 selected items in the conflict tree, don't bother // checking whether menu items apply to them, just show all of them -const int max_scan_for_context_menu = 50; +const std::size_t max_small_selection = 50; class ElideLeftDelegate : public QStyledItemDelegate @@ -27,60 +27,62 @@ protected: }; -class ConflictItem : public QTreeWidgetItem +class ConflictItem { public: - static const int FILENAME_USERROLE = Qt::UserRole + 1; - static const int ALT_ORIGIN_USERROLE = Qt::UserRole + 2; - static const int ARCHIVE_USERROLE = Qt::UserRole + 3; - static const int INDEX_USERROLE = Qt::UserRole + 4; - static const int HAS_ALTS_USERROLE = Qt::UserRole + 5; - ConflictItem( - QStringList columns, FileEntry::Index index, const QString& fileName, - bool hasAltOrigins, const QString& altOrigin, bool archive) - : QTreeWidgetItem(columns) + QString before, QString relativeName, QString after, + FileEntry::Index index, QString fileName, + bool hasAltOrigins, QString altOrigin, bool archive) : + m_before(std::move(before)), + m_relativeName(std::move(relativeName)), + m_after(std::move(after)), + m_index(index), + m_fileName(std::move(fileName)), + m_hasAltOrigins(hasAltOrigins), + m_altOrigin(std::move(altOrigin)), + m_isArchive(archive) { - setData(0, FILENAME_USERROLE, fileName); - setData(0, ALT_ORIGIN_USERROLE, altOrigin); - setData(0, ARCHIVE_USERROLE, archive); - setData(0, INDEX_USERROLE, index); - setData(0, HAS_ALTS_USERROLE, hasAltOrigins); - - if (archive) { - QFont f = font(0); - f.setItalic(true); - - for (int i=0; i); - return data(0, INDEX_USERROLE).toUInt(); + return m_index; } bool canHide() const @@ -103,21 +105,276 @@ public: return canPreviewFile(pluginContainer, isArchive(), fileName()); } - bool operator<(const QTreeWidgetItem& other) const +private: + QString m_before; + QString m_relativeName; + QString m_after; + FileEntry::Index m_index; + QString m_fileName; + bool m_hasAltOrigins; + QString m_altOrigin; + bool m_isArchive; +}; + + +class ConflictListModel : public QAbstractItemModel +{ +public: + struct Column { - const int column = treeWidget()->sortColumn(); + QString caption; + const QString& (ConflictItem::*getText)() const; + }; - if (column >= columnCount() || column >= other.columnCount()) { - // shouldn't happen - qWarning().nospace() << "ConflictItem::operator<() mistmatch in column count"; - return false; + ConflictListModel(QTreeView* tree, std::vector columns) + : m_tree(tree), m_columns(std::move(columns)) + { + m_tree->setModel(this); + } + + void clear() + { + m_items.clear(); + } + + void reserve(std::size_t s) + { + m_items.reserve(s); + } + + QModelIndex index(int row, int col, const QModelIndex& ={}) const override + { + return createIndex(row, col); + } + + QModelIndex parent(const QModelIndex&) const override + { + return {}; + } + + int rowCount(const QModelIndex& parent={}) const override + { + if (parent.isValid()) { + return 0; + } + + return static_cast(m_items.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return static_cast(m_columns.size()); + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole || role == Qt::FontRole) { + const auto row = index.row(); + if (row < 0) { + return {}; + } + + const auto i = static_cast(row); + if (i >= m_items.size()) { + return {}; + } + + const auto col = index.column(); + if (col < 0) { + return {}; + } + + const auto c = static_cast(col); + if (c >= m_columns.size()) { + return {}; + } + + const auto& item = m_items[i]; + + if (role == Qt::DisplayRole) { + return (item.*m_columns[c].getText)(); + } else if (role == Qt::FontRole) { + if (item.isArchive()) { + QFont f = m_tree->font(); + f.setItalic(true); + return f; + } + } + } + + return {}; + } + + QVariant headerData(int col, Qt::Orientation, int role) const + { + if (role == Qt::DisplayRole) { + if (col < 0) { + return {}; + } + + const auto i = static_cast(col); + if (i >= m_columns.size()) { + return {}; + } + + return m_columns[i].caption; } - return (naturalCompare(text(column), other.text(column)) < 0); + return {}; + } + + void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder) + { + if (colIndex < 0) { + return; + } + + const auto c = static_cast(colIndex); + if (c >= m_columns.size()) { + return; + } + + const auto& col = m_columns[c]; + + // avoids branching on sort order while sorting + auto sortAsc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); + }; + + auto sortDesc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); + }; + + if (order == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortDesc); + } + + emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + } + + void add(ConflictItem item) + { + m_items.emplace_back(std::move(item)); + } + + void finished() + { + beginResetModel(); + endResetModel(); + } + + const ConflictItem* getItem(std::size_t row) const + { + if (row >= m_items.size()) { + return nullptr; + } + + return &m_items[row]; + } + +private: + QTreeView* m_tree; + std::vector m_columns; + std::vector m_items; +}; + + +class OverwriteConflictListModel : public ConflictListModel +{ +public: + OverwriteConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten Mods"), &ConflictItem::before} + }) + { + } +}; + + +class OverwrittenConflictListModel : public ConflictListModel +{ +public: + OverwrittenConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName}, + {tr("Providing Mod"), &ConflictItem::after} + }) + { + } +}; + + +class NoConflictListModel : public ConflictListModel +{ +public: + NoConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("File"), &ConflictItem::relativeName} + }) + { + } +}; + + +class AdvancedConflictListModel : public ConflictListModel +{ +public: + AdvancedConflictListModel(QTreeView* tree) + : ConflictListModel(tree, { + {tr("Overwrites"), &ConflictItem::before}, + {tr("File"), &ConflictItem::relativeName}, + {tr("Overwritten By"), &ConflictItem::after} + }) + { } }; +std::size_t smallSelectionSize(const QTreeView* tree) +{ + const std::size_t too_many = std::numeric_limits::max(); + + std::size_t n = 0; + const auto* sel = tree->selectionModel(); + + for (const auto& range : sel->selection()) { + n += range.height(); + + if (n >= max_small_selection) { + return too_many; + } + } + + return n; +} + +template +void for_each_in_selection(QTreeView* tree, F&& f) +{ + const auto* sel = tree->selectionModel(); + const auto* model = dynamic_cast(tree->model()); + + if (!model) { + qCritical() << "tree doesn't have a ConflictListModel"; + return; + } + + for (const auto& range : sel->selection()) { + // ranges are inclusive + for (int row=range.top(); row<=range.bottom(); ++row) { + if (auto* item=model->getItem(static_cast(row))) { + if (!f(item)) { + return; + } + } + } + } +} + + ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : @@ -169,52 +426,55 @@ bool ConflictsTab::canHandleUnmanaged() const return true; } -void ConflictsTab::changeItemsVisibility( - const QList& items, bool visible) +void ConflictsTab::changeItemsVisibility(QTreeView* tree, bool visible) { bool changed = false; bool stop = false; - qDebug().nospace() + const auto n = smallSelectionSize(tree); + + qDebug().nospace().noquote() << (visible ? "unhiding" : "hiding") << " " - << items.size() << " conflict files"; + << (n > max_small_selection ? "a lot of" : QString("%1").arg(n)) + << " conflict files"; QFlags flags = (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - if (items.size() > 1) { + if (n > 1) { flags |= FileRenamer::MULTIPLE; } FileRenamer renamer(parentWidget(), flags); - for (const auto* item : items) { - if (stop) { - break; - } + auto* model = dynamic_cast(tree->model()); + if (!model) { + qCritical() << "list doesn't have a ConflictListModel"; + return; + } - const auto* ci = dynamic_cast(item); - if (!ci) { - continue; + for_each_in_selection(tree, [&](const ConflictItem* item) { + if (stop) { + return false; } auto result = FileRenamer::RESULT_CANCEL; if (visible) { - if (!ci->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; - continue; + if (!item->canUnhide()) { + qDebug().nospace() << "cannot unhide " << item->relativeName() << ", skipping"; + return true; } - result = unhideFile(renamer, ci->fileName()); + result = unhideFile(renamer, item->fileName()); } else { - if (!ci->canHide()) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; - continue; + if (!item->canHide()) { + qDebug().nospace() << "cannot hide " << item->relativeName() << ", skipping"; + return true; } - result = hideFile(renamer, ci->fileName()); + result = hideFile(renamer, item->fileName()); } switch (result) { @@ -235,7 +495,9 @@ void ConflictsTab::changeItemsVisibility( break; } } - } + + return true; + }); qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; @@ -250,38 +512,36 @@ void ConflictsTab::changeItemsVisibility( } } -void ConflictsTab::openItems(const QList& items) +void ConflictsTab::openItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - core().executeFileVirtualized(parentWidget(), ci->fileName()); - } - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().executeFileVirtualized(parentWidget(), item->fileName()); + return true; + }); } -void ConflictsTab::previewItems(const QList& items) +void ConflictsTab::previewItems(QTreeView* tree) { // the menu item is only shown for a single selection, but handle all of them // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - core().previewFileWithAlternatives(parentWidget(), ci->fileName()); - } - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + core().previewFileWithAlternatives(parentWidget(), item->fileName()); + return true; + }); } -void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) +void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) { - auto actions = createMenuActions(tree->selectedItems()); + auto actions = createMenuActions(tree); QMenu menu; // open if (actions.open) { connect(actions.open, &QAction::triggered, [&]{ - openItems(tree->selectedItems()); + openItems(tree); }); menu.addAction(actions.open); @@ -290,7 +550,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // preview if (actions.preview) { connect(actions.preview, &QAction::triggered, [&]{ - previewItems(tree->selectedItems()); + previewItems(tree); }); menu.addAction(actions.preview); @@ -299,7 +559,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ - changeItemsVisibility(tree->selectedItems(), false); + changeItemsVisibility(tree, false); }); menu.addAction(actions.hide); @@ -308,7 +568,7 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) // unhide if (actions.unhide) { connect(actions.unhide, &QAction::triggered, [&]{ - changeItemsVisibility(tree->selectedItems(), true); + changeItemsVisibility(tree, true); }); menu.addAction(actions.unhide); @@ -332,10 +592,9 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) } } -ConflictsTab::Actions ConflictsTab::createMenuActions( - const QList& selection) +ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) { - if (selection.empty()) { + if (tree->selectionModel()->selection().isEmpty()) { return {}; } @@ -345,18 +604,28 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( bool enablePreview = true; bool enableGoto = true; - if (selection.size() == 1) { + const auto n = smallSelectionSize(tree); + + const auto* model = dynamic_cast(tree->model()); + if (!model) { + qCritical() << "tree doesn't have a ConflictListModel"; + return {}; + } + + if (n == 1) { // this is a single selection - const auto* ci = dynamic_cast(selection[0]); - if (!ci) { + const auto* item = model->getItem(static_cast( + tree->selectionModel()->selectedRows()[0].row())); + + if (!item) { return {}; } - enableHide = ci->canHide(); - enableUnhide = ci->canUnhide(); - enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(plugin()); - enableGoto = ci->hasAlts(); + enableHide = item->canHide(); + enableUnhide = item->canUnhide(); + enableOpen = item->canOpen(); + enablePreview = item->canPreview(plugin()); + enableGoto = item->hasAlts(); } else { // this is a multiple selection, don't show open/preview so users don't open @@ -367,28 +636,28 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( // don't bother with this on multiple selection, at least for now enableGoto = false; - if (selection.size() < max_scan_for_context_menu) { + if (n <= max_small_selection) { // 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 (const auto* ci=dynamic_cast(item)) { - if (ci->canHide()) { - enableHide = true; - } + for_each_in_selection(tree, [&](const ConflictItem* item) { + if (item->canHide()) { + enableHide = true; + } - if (ci->canUnhide()) { - enableUnhide = true; - } + if (item->canUnhide()) { + enableUnhide = true; + } - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; - } + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + return false; } - } + + return true; + }); } } @@ -411,22 +680,19 @@ ConflictsTab::Actions ConflictsTab::createMenuActions( actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); - if (enableGoto) { - actions.gotoActions = createGotoActions(selection); + if (enableGoto && n == 1) { + const auto* item = model->getItem(static_cast( + tree->selectionModel()->selectedRows()[0].row())); + + actions.gotoActions = createGotoActions(item); } return actions; } -std::vector ConflictsTab::createGotoActions( - const QList& selection) +std::vector ConflictsTab::createGotoActions(const ConflictItem* item) { - if (!origin() || selection.size() != 1) { - return {}; - } - - const auto* item = dynamic_cast(selection[0]); - if (!item) { + if (!origin()) { return {}; } @@ -468,39 +734,42 @@ std::vector ConflictsTab::createGotoActions( GeneralConflictsTab::GeneralConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : + m_tab(tab), ui(pui), m_core(oc), + m_overwriteModel(new OverwriteConflictListModel(ui->overwriteTree)), + m_overwrittenModel(new OverwrittenConflictListModel(ui->overwrittenTree)), + m_noConflictModel(new NoConflictListModel(ui->noConflictTree)) { m_expanders.overwrite.set(ui->overwriteExpander, ui->overwriteTree, true); m_expanders.overwritten.set(ui->overwrittenExpander, ui->overwrittenTree, true); m_expanders.nonconflict.set(ui->noConflictExpander, ui->noConflictTree); QObject::connect( - ui->overwriteTree, &QTreeWidget::itemDoubleClicked, - [&](auto* item, int col){ onOverwriteActivated(item, col); }); + ui->overwriteTree, &QTreeView::doubleClicked, + [&](auto&& item){ onOverwriteActivated(item); }); QObject::connect( - ui->overwrittenTree, &QTreeWidget::itemDoubleClicked, - [&](auto* item, int col){ onOverwrittenActivated(item, col); }); + ui->overwrittenTree, &QTreeView::doubleClicked, + [&](auto&& item){ onOverwrittenActivated(item); }); QObject::connect( - ui->overwriteTree, &QTreeWidget::customContextMenuRequested, + ui->overwriteTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree); }); QObject::connect( - ui->overwrittenTree, &QTreeWidget::customContextMenuRequested, + ui->overwrittenTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree); }); QObject::connect( - ui->noConflictTree, &QTreeWidget::customContextMenuRequested, + ui->noConflictTree, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree); }); } void GeneralConflictsTab::clear() { - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); + m_overwriteModel->clear(); + m_overwrittenModel->clear(); + m_noConflictModel->clear(); ui->overwriteCount->display(0); ui->overwrittenCount->display(0); @@ -568,10 +837,6 @@ bool GeneralConflictsTab::update() int numOverwrite = 0; int numOverwritten = 0; - QList overwriteItems; - QList overwrittenItems; - QList noConflictItems; - if (m_tab->origin() != nullptr) { const auto rootPath = m_tab->mod()->absolutePath(); @@ -585,19 +850,19 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { - overwriteItems.append(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); + m_overwriteModel->add(createOverwriteItem( + file->getIndex(), archive, fileName, relativeName, alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree - noConflictItems.append(createNoConflictItem( + m_noConflictModel->add(createNoConflictItem( file->getIndex(), archive, fileName, relativeName)); ++numNonConflicting; } } else { - overwrittenItems.append(createOverwrittenItem( + m_overwrittenModel->add(createOverwrittenItem( file->getIndex(), fileOrigin, archive, fileName, relativeName)); ++numOverwritten; @@ -605,9 +870,9 @@ bool GeneralConflictsTab::update() } } - ui->overwriteTree->addTopLevelItems(overwriteItems); - ui->overwrittenTree->addTopLevelItems(overwrittenItems); - ui->noConflictTree->addTopLevelItems(noConflictItems); + m_overwriteModel->finished(); + m_overwrittenModel->finished(); + m_noConflictModel->finished(); ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); @@ -616,7 +881,7 @@ bool GeneralConflictsTab::update() return (numOverwrite > 0 || numOverwritten > 0); } -QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( +ConflictItem GeneralConflictsTab::createOverwriteItem( FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName, const FileEntry::AlternativesVector& alternatives) @@ -638,17 +903,17 @@ QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); - return new ConflictItem(fields, index, fileName, true, origin, archive); + return {altString, relativeName, "", index, fileName, true, origin, archive}; } -QTreeWidgetItem* GeneralConflictsTab::createNoConflictItem( +ConflictItem GeneralConflictsTab::createNoConflictItem( FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName) { - return new ConflictItem({relativeName}, index, fileName, false, "", archive); + return {"", relativeName, "", index, fileName, false, "", archive}; } -QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( +ConflictItem GeneralConflictsTab::createOverwrittenItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName) { @@ -659,193 +924,53 @@ QTreeWidgetItem* GeneralConflictsTab::createOverwrittenItem( QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); - return new ConflictItem( - fields, index, fileName, true, ToQString(realOrigin.getName()), archive); + return { + "", relativeName, ToQString(realOrigin.getName()), + index, fileName, true, ToQString(realOrigin.getName()), archive}; } -void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) +void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index) { - if (auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - emit modOpen(origin); - } + auto* model = dynamic_cast(ui->overwriteTree->model()); + if (!model) { + return; } -} -void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) -{ - if (const auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); + auto* item = model->getItem(static_cast(index.row())); + if (!item) { + return; + } - if (!origin.isEmpty()) { - emit modOpen(origin); - } + const auto origin = item->altOrigin(); + if (!origin.isEmpty()) { + emit modOpen(origin); } } - -class AdvancedListModel : public QAbstractItemModel +void GeneralConflictsTab::onOverwrittenActivated(const QModelIndex& index) { -public: - struct Item - { - QString before, relativeName, after; - FileEntry::Index index; - QString fileName; - bool hasAltOrigins; - QString altOrigin; - bool archive; - }; - - void clear() - { - m_items.clear(); - } - - void reserve(std::size_t s) - { - m_items.reserve(s); - } - - QModelIndex index(int row, int col, const QModelIndex& ={}) const override - { - return createIndex(row, col); - } - - QModelIndex parent(const QModelIndex&) const override - { - return {}; - } - - int rowCount(const QModelIndex& parent={}) const override - { - if (parent.isValid()) { - return 0; - } - - return static_cast(m_items.size()); - } - - int columnCount(const QModelIndex& ={}) const override - { - return 3; - } - - QVariant data(const QModelIndex& index, int role) const override - { - if (role == Qt::DisplayRole) { - const auto row = index.row(); - if (row < 0) { - return {}; - } - - const auto i = static_cast(row); - if (i >= m_items.size()) { - return {}; - } - - const auto& item = m_items[i]; - - if (index.column() == 0) { - return item.before; - } else if (index.column() == 1) { - return item.relativeName; - } else if (index.column() == 2) { - return item.after; - } - } - - return {}; - } - - QVariant headerData(int col, Qt::Orientation, int role) const - { - if (role == Qt::DisplayRole) { - if (col == 0) { - return tr("Overwrites"); - } else if (col == 1) { - return tr("File"); - } else if (col == 2) { - return tr("Overwritten by"); - } - } - - return {}; - } - - void sort(int col, Qt::SortOrder order=Qt::AscendingOrder) - { - // avoids branching on column/sort order while sorting - auto sortBeforeAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.before, b.before) < 0); - }; - - auto sortBeforeDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.before, b.before) > 0); - }; - - auto sortFileAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.relativeName, b.relativeName) < 0); - }; - - auto sortFileDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.relativeName, b.relativeName) > 0); - }; - - auto sortAfterAsc = [](const auto& a, const auto& b) { - return (naturalCompare(a.after, b.after) < 0); - }; - - auto sortAfterDesc = [](const auto& a, const auto& b) { - return (naturalCompare(a.after, b.after) > 0); - }; - - if (col == 0) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortBeforeAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortBeforeDesc); - } - } else if (col == 1) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortFileAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortFileDesc); - } - } else if (col == 2) { - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortAfterAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortAfterDesc); - } - } - - emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); + auto* model = dynamic_cast(ui->overwrittenTree->model()); + if (!model) { + return; } - void add(Item item) - { - m_items.emplace_back(std::move(item)); + auto* item = model->getItem(static_cast(index.row())); + if (!item) { + return; } - void finished() - { - emit dataChanged(index(0, 0), index(0, rowCount())); + const auto origin = item->altOrigin(); + if (!origin.isEmpty()) { + emit modOpen(origin); } - -private: - std::vector m_items; -}; +} AdvancedConflictsTab::AdvancedConflictsTab( - ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) - : m_tab(tab), ui(pui), m_core(oc), m_model(new AdvancedListModel) + ConflictsTab* tab, Ui::ModInfoDialog* pui, OrganizerCore& oc) : + m_tab(tab), ui(pui), m_core(oc), + m_model(new AdvancedConflictListModel(ui->conflictsAdvancedList)) { - ui->conflictsAdvancedList->setModel(m_model); - // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); @@ -868,9 +993,9 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&]{ update(); }); - //QObject::connect( - // ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, - // [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); + QObject::connect( + ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); m_filter.set(ui->conflictsAdvancedFilter); m_filter.changed = [&]{ update(); }; @@ -938,16 +1063,20 @@ void AdvancedConflictsTab::update() const int fileOrigin = file->getOrigin(archive); const auto& alternatives = file->getAlternatives(); - addItem( + auto item = createItem( file->getIndex(), fileOrigin, archive, fileName, relativeName, alternatives); + + if (item) { + m_model->add(std::move(*item)); + } } m_model->finished(); } } -void AdvancedConflictsTab::addItem( +std::optional AdvancedConflictsTab::createItem( FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) @@ -1040,7 +1169,7 @@ void AdvancedConflictsTab::addItem( // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it if (!hasAlts) { - return; + return {}; } } @@ -1052,9 +1181,8 @@ void AdvancedConflictsTab::addItem( }); if (!matched) { - return; + return {}; } - m_model->add( - {before, relativeName, after, index, fileName, hasAlts, "", archive}); + return ConflictItem{before, relativeName, after, index, fileName, hasAlts, "", archive}; } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index f499d1a4..f0c0c589 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -6,10 +6,12 @@ #include "filterwidget.h" #include "directoryentry.h" #include +#include class ConflictsTab; class OrganizerCore; class ConflictItem; +class ConflictListModel; class GeneralConflictsTab : public QObject { @@ -39,21 +41,25 @@ private: OrganizerCore& m_core; Expanders m_expanders; - QTreeWidgetItem* createOverwriteItem( + ConflictListModel* m_overwriteModel; + ConflictListModel* m_overwrittenModel; + ConflictListModel* m_noConflictModel; + + ConflictItem createOverwriteItem( MOShared::FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); - QTreeWidgetItem* createNoConflictItem( + ConflictItem createNoConflictItem( MOShared::FileEntry::Index index, bool archive, const QString& fileName, const QString& relativeName); - QTreeWidgetItem* createOverwrittenItem( + ConflictItem createOverwrittenItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName); - void onOverwriteActivated(QTreeWidgetItem* item, int); - void onOverwrittenActivated(QTreeWidgetItem *item, int); + void onOverwriteActivated(const QModelIndex& index); + void onOverwrittenActivated(const QModelIndex& index); void onOverwriteTreeContext(const QPoint &pos); void onOverwrittenTreeContext(const QPoint &pos); @@ -61,8 +67,6 @@ private: }; -class AdvancedListModel; - class AdvancedConflictsTab : public QObject { Q_OBJECT; @@ -85,9 +89,9 @@ private: Ui::ModInfoDialog* ui; OrganizerCore& m_core; FilterWidget m_filter; - AdvancedListModel* m_model; + ConflictListModel* m_model; - void addItem( + std::optional createItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); @@ -109,13 +113,12 @@ public: void restoreState(const Settings& s) override; bool canHandleUnmanaged() const override; - void openItems(const QList& items); - void previewItems(const QList& items); + void openItems(QTreeView* tree); + void previewItems(QTreeView* tree); - void changeItemsVisibility( - const QList& items, bool visible); + void changeItemsVisibility(QTreeView* tree, bool visible); - void showContextMenu(const QPoint &pos, QTreeWidget* tree); + void showContextMenu(const QPoint &pos, QTreeView* tree); private: struct Actions @@ -131,10 +134,8 @@ private: GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; - Actions createMenuActions(const QList& selection); - - std::vector createGotoActions( - const QList& selection); + Actions createMenuActions(QTreeView* tree); + std::vector createGotoActions(const ConflictItem* item); }; #endif // MODINFODIALOGCONFLICTS_H -- cgit v1.3.1 From 1ef0620f6e2d8a4e7d48b3b2b8eb96c3a8d3cd0d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 08:10:17 -0400 Subject: rework of the images tab to avoid using widgets at all, painting is done manually --- src/modinfodialog.ui | 19 +++- src/modinfodialogconflicts.cpp | 8 -- src/modinfodialogimages.cpp | 237 +++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 71 ++++++++++-- 4 files changed, 268 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index cdf4c80f..bcdf1007 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -177,11 +177,8 @@ 0 - - - true - - + + 0 @@ -1163,6 +1160,18 @@ p, li { white-space: pre-wrap; } QTextEdit
texteditor.h
+ + ImagesScrollArea + QScrollArea +
modinfodialogimages.h
+ 1 +
+ + ImagesThumbnails + QWidget +
modinfodialogimages.h
+ 1 +
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d1384d7a..05b69f05 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -887,7 +887,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; for (const auto& alt : alternatives) { @@ -898,9 +897,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( altString += ToQString(ds.getOriginByID(alt.first).getName()); } - QStringList fields(relativeName); - fields.append(altString); - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); return {altString, relativeName, "", index, fileName, true, origin, archive}; @@ -918,12 +914,8 @@ ConflictItem GeneralConflictsTab::createOverwrittenItem( const QString& fileName, const QString& relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - return { "", relativeName, ToQString(realOrigin.getName()), index, fileName, true, ToQString(realOrigin.getName()), archive}; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index f4cdade8..1f5b258c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,25 +1,68 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" -ScalableImage::ScalableImage(QImage original) - : m_original(std::move(original)), m_border(1) +void ImagesScrollArea::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollArea::resizeEvent(QResizeEvent* e) +{ + if (m_tab) { + m_tab->scrollAreaResized(e->size()); + } +} + + +void ImagesThumbnails::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesThumbnails::paintEvent(QPaintEvent* e) +{ + if (m_tab) { + m_tab->paintThumbnails(e); + } +} + +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +{ + if (m_tab) { + m_tab->thumbnailsMouseEvent(e); + } +} + + +ScalableImage::ScalableImage(QString path) + : m_path(std::move(path)), m_border(1) { auto sp = sizePolicy(); sp.setHeightForWidth(true); setSizePolicy(sp); } +void ScalableImage::setImage(const QString& path) +{ + m_path = path; + m_original = {}; + m_scaled = {}; + + update(); +} + void ScalableImage::setImage(QImage image) { + m_path.clear(); m_original = std::move(image); m_scaled = {}; update(); } -const QImage& ScalableImage::image() const +void ScalableImage::clear() { - return m_original; + setImage(QImage()); } bool ScalableImage::hasHeightForWidth() const @@ -35,7 +78,15 @@ int ScalableImage::heightForWidth(int w) const void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { - return; + if (m_path.isNull()) { + return; + } + + m_original.load(m_path); + + if (m_original.isNull()) { + return; + } } const QRect widgetRect = rect(); @@ -72,83 +123,177 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawImage(drawImageRect, m_scaled); } -void ScalableImage::mousePressEvent(QMouseEvent* e) -{ - if (e->button() == Qt::LeftButton) { - emit clicked(m_original); - } -} - ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) { ui->imagesImage->layout()->addWidget(m_image); - ui->imagesThumbnails->setLayout(new QVBoxLayout); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); } void ImagesTab::clear() { - m_image->setImage({}); + m_image->clear(); + m_files.clear(); + setHasData(false); +} - while (ui->imagesThumbnails->layout()->count() > 0) { - auto* item = ui->imagesThumbnails->layout()->takeAt(0); - delete item->widget(); - delete item; +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + QImageReader reader(fullPath); + if (reader.canRead()) { + m_files.push_back({fullPath}); + return true; } - static_cast(ui->imagesThumbnails->layout())->addStretch(1); - setHasData(false); + return false; } -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +void ImagesTab::update() +{ + setHasData(!m_files.empty()); +} + +void ImagesTab::scrollAreaResized(const QSize& s) { - static constexpr const char* extensions[] = { - ".png", ".jpg" - }; + const int availableWidth = s.width(); - for (const auto* e : extensions) { - if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (add(fullPath)) { - setHasData(true); + const int thumbSize = availableWidth - (m_margins * 2); + + int height = 0; + if (!m_files.empty()) { + height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); + } + + height += (m_margins * 2); + + qDebug() << "new size: " << availableWidth << "x" << height; + + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t begin = e->rect().top() / (thumbSize + m_padding); + const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; + + QPainter painter(ui->imagesThumbnails); + + for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, thumbSize); + + painter.setPen(QColor(Qt::black)); + painter.drawRect(borderRect); + + auto& file = m_files[i]; + if (file.failed) { + continue; + } + + const QRect thumbRect = borderRect.adjusted( + m_border, m_border, -m_border, -m_border); + + if (needsReload(file, thumbRect.size())) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + continue; } - return true; + file.thumbnail = file.original.scaled( + scaledImageSize(file.original.size(), thumbRect.size()), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - } - return false; + if (file.thumbnail.isNull()) { + continue; + } + + const QRect scaledThumbRect( + (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, + (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, + file.thumbnail.width(), file.thumbnail.height()); + + painter.drawImage(scaledThumbRect, file.thumbnail); + } } -bool ImagesTab::add(const QString& fullPath) +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) { - QImage image = QImage(fullPath); + if (e->button() != Qt::LeftButton) { + return; + } - if (image.isNull()) { - qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return false; + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t i = e->y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return; } - auto* thumbnail = new ScalableImage(std::move(image)); + if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { + return; + } - QObject::connect( - thumbnail, &ScalableImage::clicked, - [&](const QImage& image){ onClicked(image); }); + m_image->setImage(m_files[i].original); +} - static_cast(ui->imagesThumbnails->layout())->insertWidget( - ui->imagesThumbnails->layout()->count() - 1, thumbnail); +bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const +{ + if (file.failed) { + return false; + } - return true; + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { + return true; + } + + return false; } -void ImagesTab::onClicked(const QImage& original) +QSize ImagesTab::scaledImageSize( + const QSize& originalSize, const QSize& thumbSize) const { - m_image->setImage(original); + const auto ratio = std::min({ + 1.0, + static_cast(thumbSize.width()) / originalSize.width(), + static_cast(thumbSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 6603660a..1c65d8bd 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -2,28 +2,62 @@ #define MODINFODIALOGIMAGES_H #include "modinfodialogtab.h" +#include + +class ImagesTab; + +class ImagesScrollArea : public QScrollArea +{ + Q_OBJECT; + +public: + using QScrollArea::QScrollArea; + void setTab(ImagesTab* tab); + +protected: + void resizeEvent(QResizeEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + + +class ImagesThumbnails : public QWidget +{ + Q_OBJECT; + +public: + using QWidget::QWidget; + void setTab(ImagesTab* tab); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + class ScalableImage : public QWidget { Q_OBJECT; public: - ScalableImage(QImage image={}); + ScalableImage(QString path={}); + void setImage(const QString& path); void setImage(QImage image); - const QImage& image() const; + void clear(); bool hasHeightForWidth() const; int heightForWidth(int w) const; -signals: - void clicked(const QImage& image); - protected: void paintEvent(QPaintEvent* e) override; - void mousePressEvent(QMouseEvent* e) override; private: + QString m_path; QImage m_original, m_scaled; int m_border; }; @@ -32,6 +66,8 @@ private: class ImagesTab : public ModInfoDialogTab { Q_OBJECT; + friend class ImagesScrollArea; + friend class ImagesThumbnails; public: ImagesTab( @@ -40,12 +76,31 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update() override; private: + struct File + { + QString path; + QImage original, thumbnail; + bool failed = false; + + File(QString path) + : path(std::move(path)) + { + } + }; + ScalableImage* m_image; + std::vector m_files; + int m_margins, m_padding, m_border; + + void scrollAreaResized(const QSize& s); + void paintThumbnails(QPaintEvent* e); + void thumbnailsMouseEvent(QMouseEvent* e); - bool add(const QString& fullPath); - void onClicked(const QImage& image); + bool needsReload(const File& file, const QSize& thumbSize) const; + QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From 24b9f050c10d039be13a266d41088ae0fcc068f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 08:30:11 -0400 Subject: check supported extensions instead of using QImageReader, less accurate but much faster --- src/modinfodialogimages.cpp | 21 +++++++++++++++++---- src/modinfodialogimages.h | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1f5b258c..4acc7838 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -140,6 +140,18 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setWidgetResizable(false); ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + + const auto list = QImageReader::supportedImageFormats(); + for (const auto& entry : list) { + QString s(entry); + if (!s.isEmpty()) { + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } + } } void ImagesTab::clear() @@ -151,10 +163,11 @@ void ImagesTab::clear() bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { - QImageReader reader(fullPath); - if (reader.canRead()) { - m_files.push_back({fullPath}); - return true; + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } } return false; diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 1c65d8bd..88e22a6a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -93,6 +93,7 @@ private: ScalableImage* m_image; std::vector m_files; + std::vector m_supportedFormats; int m_margins, m_padding, m_border; void scrollAreaResized(const QSize& s); -- cgit v1.3.1 From 9e3214a73a5cc38a9b4f0313d8a48c0374cb57a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 09:13:38 -0400 Subject: refactored calculations --- src/modinfodialogimages.cpp | 467 ++++++++++++++++++++++++++------------------ src/modinfodialogimages.h | 36 +++- 2 files changed, 313 insertions(+), 190 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4acc7838..5b35a9b1 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,6 +1,285 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +ImagesTab::ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) +{ + ui->imagesImage->layout()->addWidget(m_image); + delete ui->imagesThumbnails->layout(); + + ui->tabImagesSplitter->setSizes({128, 1}); + ui->tabImagesSplitter->setStretchFactor(0, 0); + ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); + + getSupportedFormats(); +} + +void ImagesTab::clear() +{ + m_image->clear(); + m_files.clear(); + setHasData(false); +} + +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } + } + + return false; +} + +void ImagesTab::update() +{ + setHasData(!m_files.empty()); + resizeWidget(); + ui->imagesThumbnails->update(); +} + +void ImagesTab::getSupportedFormats() +{ + for (const auto& entry : QImageReader::supportedImageFormats()) { + QString s(entry); + if (s.isNull() || s.isEmpty()) { + continue; + } + + // make sure it starts with a dot + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } +} + +int ImagesTab::calcThumbSize(int availableWidth) const +{ + return availableWidth - (m_margins * 2); +} + +int ImagesTab::calcWidgetHeight(int availableWidth) const +{ + if (m_files.empty()) { + return 0; + } + + const auto thumbSize = calcThumbSize(availableWidth); + + int h = 0; + + // first thumb + h = thumbSize; + + // subsequent thumbs with padding before each + const auto thumbWithPadding = m_padding + thumbSize; + h += static_cast(thumbWithPadding * (m_files.size() - 1)); + + // margin top and bottom + h += m_margins * 2; + + return h; +} + +QRect ImagesTab::calcTopThumbRect(int thumbSize) const +{ + return {m_margins, m_margins, thumbSize, thumbSize}; +} + +std::pair ImagesTab::calcVisibleRange( + int top, int bottom, int thumbSize) const +{ + const std::size_t begin = top / (thumbSize + m_padding); + const std::size_t end = bottom / (thumbSize + m_padding) + 1; + + return {begin, end}; +} + +QRect ImagesTab::calcBorderRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return { + topRect.left(), + static_cast(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, + thumbSize + }; +} + +QRect ImagesTab::calcImageRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return calcBorderRect(topRect, thumbSize, i).adjusted( + m_border, m_border, -m_border, -m_border); +} + +QSize ImagesTab::calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const +{ + const auto ratio = std::min({ + 1.0, + static_cast(imageSize.width()) / originalSize.width(), + static_cast(imageSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + PaintContext cx(ui->imagesThumbnails); + cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + cx.topRect = calcTopThumbRect(cx.thumbSize); + + const auto [begin, end] = calcVisibleRange( + e->rect().top(), e->rect().bottom(), cx.thumbSize); + + for (std::size_t i=begin; iimagesThumbnails->width()); + + // calculate index purely based on y position + const std::size_t i = p.y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return nullptr; + } + + // get actual rect + const auto topRect = calcTopThumbRect(thumbSize); + const auto rect = calcBorderRect(topRect, thumbSize, i); + + if (!rect.contains(p)) { + return nullptr; + } + + return &m_files[i]; +} + +void ImagesTab::scrollAreaResized(const QSize&) +{ + resizeWidget(); +} + +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +{ + if (e->button() != Qt::LeftButton) { + return; + } + + if (const auto* file=fileAtPos(e->pos())) { + m_image->setImage(file->original); + } +} + +bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +{ + if (file.failed) { + return false; + } + + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); + if (file.thumbnail.size() != scaledSize) { + return true; + } + + return false; +} + +void ImagesTab::reload(File& file, const QSize& scaledSize) +{ + file.original = {}; + file.thumbnail = {}; + file.failed = false; + + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } + + file.thumbnail = file.original.scaled( + calcScaledImageSize(file.original.size(), scaledSize), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +void ImagesTab::resizeWidget() +{ + if (m_files.empty()) { + ui->imagesThumbnails->setGeometry(QRect()); + return; + } + + const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + + const int widgetHeight = calcWidgetHeight(availableWidth); + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); +} + + void ImagesScrollArea::setTab(ImagesTab* tab) { m_tab = tab; @@ -122,191 +401,3 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } - - -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) -{ - ui->imagesImage->layout()->addWidget(m_image); - delete ui->imagesThumbnails->layout(); - - ui->tabImagesSplitter->setSizes({128, 1}); - ui->tabImagesSplitter->setStretchFactor(0, 0); - ui->tabImagesSplitter->setStretchFactor(1, 1); - - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); - ui->imagesThumbnails->setTab(this); - - const auto list = QImageReader::supportedImageFormats(); - for (const auto& entry : list) { - QString s(entry); - if (!s.isEmpty()) { - if (s[0] != ".") { - s = "." + s; - } - - m_supportedFormats.emplace_back(std::move(s)); - } - } -} - -void ImagesTab::clear() -{ - m_image->clear(); - m_files.clear(); - setHasData(false); -} - -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) -{ - for (const auto& ext : m_supportedFormats) { - if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); - return true; - } - } - - return false; -} - -void ImagesTab::update() -{ - setHasData(!m_files.empty()); -} - -void ImagesTab::scrollAreaResized(const QSize& s) -{ - const int availableWidth = s.width(); - - const int thumbSize = availableWidth - (m_margins * 2); - - int height = 0; - if (!m_files.empty()) { - height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); - } - - height += (m_margins * 2); - - qDebug() << "new size: " << availableWidth << "x" << height; - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t begin = e->rect().top() / (thumbSize + m_padding); - const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; - - QPainter painter(ui->imagesThumbnails); - - for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, thumbSize); - - painter.setPen(QColor(Qt::black)); - painter.drawRect(borderRect); - - auto& file = m_files[i]; - if (file.failed) { - continue; - } - - const QRect thumbRect = borderRect.adjusted( - m_border, m_border, -m_border, -m_border); - - if (needsReload(file, thumbRect.size())) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - continue; - } - - file.thumbnail = file.original.scaled( - scaledImageSize(file.original.size(), thumbRect.size()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - if (file.thumbnail.isNull()) { - continue; - } - - const QRect scaledThumbRect( - (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, - (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, - file.thumbnail.width(), file.thumbnail.height()); - - painter.drawImage(scaledThumbRect, file.thumbnail); - } -} - -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) -{ - if (e->button() != Qt::LeftButton) { - return; - } - - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t i = e->y() / (thumbSize + m_padding); - if (i >= m_files.size()) { - return; - } - - if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { - return; - } - - m_image->setImage(m_files[i].original); -} - -bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const -{ - if (file.failed) { - return false; - } - - if (file.original.isNull() || file.thumbnail.isNull()) { - return true; - } - - if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { - return true; - } - - return false; -} - -QSize ImagesTab::scaledImageSize( - const QSize& originalSize, const QSize& thumbSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(thumbSize.width()) / originalSize.width(), - static_cast(thumbSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 88e22a6a..9c3e5746 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -91,17 +91,49 @@ private: } }; + struct PaintContext + { + QPainter painter; + int thumbSize; + QRect topRect; + + PaintContext(QWidget* w) + : painter(w), thumbSize(0) + { + } + }; + ScalableImage* m_image; std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + void getSupportedFormats(); + void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); - bool needsReload(const File& file, const QSize& thumbSize) const; - QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; + int calcThumbSize(int availableWidth) const; + int calcWidgetHeight(int availableWidth) const; + QRect calcTopThumbRect(int thumbSize) const; + std::pair calcVisibleRange( + int top, int bottom, int thumbSize) const; + + QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QSize calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const; + + void paintThumbnail(PaintContext& cx, std::size_t i); + void paintThumbnailBorder(PaintContext& cx, std::size_t i); + void paintThumbnailImage(PaintContext& cx, std::size_t i); + + const File* fileAtPos(const QPoint& p) const; + + bool needsReload(const File& file, const QSize& imageSize) const; + void reload(File& file, const QSize& imageSize); + void resizeWidget(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From b6d8f4e479e42e401be60f522a0496c1f5624089 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:16:12 -0400 Subject: custom models for esp lists --- src/modinfodialog.ui | 4 +- src/modinfodialogconflicts.cpp | 2 +- src/modinfodialogesps.cpp | 245 +++++++++++++++++++++++++++++------------ src/modinfodialogesps.h | 9 +- src/modinfodialogtextfiles.cpp | 3 +- 5 files changed, 184 insertions(+), 79 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index bcdf1007..a152c292 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -244,7 +244,7 @@
- + List of esps, esms, and esls that can not be loaded by the game. @@ -374,7 +374,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + ESPs in the data directory and thus visible to the game. diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 05b69f05..03589030 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -135,6 +135,7 @@ public: void clear() { m_items.clear(); + endResetModel(); } void reserve(std::size_t s) @@ -261,7 +262,6 @@ public: void finished() { - beginResetModel(); endResetModel(); } diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 6c4fc4dd..3f742bae 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,13 +1,14 @@ #include "modinfodialogesps.h" #include "ui_modinfodialog.h" +#include "modinfodialog.h" #include using MOBase::reportError; -class ESP +class ESPItem { public: - ESP(QString rootPath, QString relativePath) + ESPItem(QString rootPath, QString relativePath) : m_rootPath(std::move(rootPath)), m_active(false) { if (relativePath.contains('/')) { @@ -16,6 +17,8 @@ public: m_activePath = relativePath; m_active = true; } + + updateFilename(); } const QString& rootPath() const @@ -32,6 +35,11 @@ public: } } + const QString& filename() const + { + return m_filename; + } + const QString& activePath() const { return m_activePath; @@ -65,6 +73,8 @@ public: m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; } + updateFilename(); + return true; } @@ -78,6 +88,7 @@ public: if (root.rename(m_activePath, newName)) { m_active = false; m_inactivePath = newName; + updateFilename(); return true; } @@ -88,45 +99,137 @@ private: QString m_rootPath; QString m_activePath; QString m_inactivePath; + QString m_filename; bool m_active; + + void updateFilename() + { + m_filename = fileInfo().fileName(); + } }; -class ESPItem : public QListWidgetItem +class ESPListModel : public QAbstractItemModel { public: - ESPItem(ESP esp) - : m_esp(std::move(esp)) + void clear() { - updateText(); + m_esps.clear(); + endResetModel(); } - const ESP& esp() const + QModelIndex index(int row, int col, const QModelIndex& ={}) const override { - return m_esp; + return createIndex(row, col); } - void setESP(ESP esp) + QModelIndex parent(const QModelIndex&) const override { - m_esp = esp; - updateText(); + return {}; } -private: - ESP m_esp; + int rowCount(const QModelIndex& ={}) const override + { + return static_cast(m_esps.size()); + } + + int columnCount(const QModelIndex& ={}) const override + { + return 1; + } + + QVariant data(const QModelIndex& index, int role) const override + { + if (role == Qt::DisplayRole) { + if (auto* esp=getESP(index)) { + return esp->filename(); + } + } + + return {}; + } + + + void add(ESPItem esp) + { + m_esps.emplace_back(std::move(esp)); + } + + void addOne(ESPItem esp) + { + const auto i = m_esps.size(); + + beginInsertRows({}, static_cast(i), static_cast(i)); + add(std::move(esp)); + endInsertRows(); + } + + bool removeRows(int row, int count, const QModelIndex& = {}) override + { + if (row < 0) { + return false; + } + + const auto start = static_cast(row); + if (start >= m_esps.size()) { + return false; + } + + const auto end = std::min( + start + static_cast(count), + m_esps.size()); + + beginRemoveRows({}, static_cast(start), static_cast(end)); + m_esps.erase(m_esps.begin() + start, m_esps.begin() + end); + endRemoveRows(); + + return true; + } - void updateText() + void finished() { - setText(m_esp.fileInfo().fileName()); + std::sort(m_esps.begin(), m_esps.end(), [](const auto& a, const auto& b) { + return (naturalCompare(a.filename(), b.filename()) < 0); + }); + + endResetModel(); + } + + const ESPItem* getESP(const QModelIndex& index) const + { + const auto row = index.row(); + if (row < 0) { + return nullptr; + } + + const auto i = static_cast(row); + if (i >= m_esps.size()) { + return nullptr; + } + + return &m_esps[i]; } + + ESPItem* getESP(const QModelIndex& index) + { + return const_cast(std::as_const(*this).getESP(index)); + } + +private: + std::deque m_esps; }; + ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) { + ui->inactiveESPList->setModel(m_inactiveModel); + ui->activeESPList->setModel(m_activeModel); + QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); @@ -136,8 +239,8 @@ ESPsTab::ESPsTab( void ESPsTab::clear() { - ui->inactiveESPList->clear(); - ui->activeESPList->clear(); + m_inactiveModel->clear(); + m_activeModel->clear(); setHasData(false); } @@ -151,12 +254,12 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) if (fullPath.endsWith(e, Qt::CaseInsensitive)) { QString relativePath = fullPath.mid(rootPath.length() + 1); - auto* item = new ESPItem({rootPath, relativePath}); + ESPItem esp(rootPath, relativePath); - if (item->esp().isActive()) { - ui->activeESPList->addItem(item); + if (esp.isActive()) { + m_activeModel->add(std::move(esp)); } else { - ui->inactiveESPList->addItem(item); + m_inactiveModel->add(std::move(esp)); } setHasData(true); @@ -167,23 +270,31 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } +void ESPsTab::update() +{ + m_inactiveModel->finished(); + m_activeModel->finished(); +} + void ESPsTab::onActivate() { - auto* item = selectedInactive(); - if (!item) { - qWarning("ESPsTab::onActivate(): no selection"); + const auto index = ui->inactiveESPList->currentIndex(); + if (!index.isValid()) { return; } - ESP esp = item->esp(); + auto* esp = m_inactiveModel->getESP(index); + if (!esp) { + return; + } - if (esp.isActive()) { + if (esp->isActive()) { qWarning("ESPsTab::onActive(): item is already active"); return; } - QDir root(esp.rootPath()); - const QFileInfo file(esp.fileInfo()); + QDir root(esp->rootPath()); + const QFileInfo file(esp->fileInfo()); QString newName = file.fileName(); @@ -205,10 +316,12 @@ void ESPsTab::onActivate() } } - if (esp.activate(newName)) { - ui->inactiveESPList->takeItem(ui->inactiveESPList->row(item)); - ui->activeESPList->addItem(item); - item->setESP(esp); + if (esp->activate(newName)) { + // copy esp, original will be destroyed + auto copy = *esp; + m_inactiveModel->removeRow(index.row()); + m_activeModel->addOne(std::move(copy)); + selectRow(ui->inactiveESPList, index.row()); } else { reportError(QObject::tr("Failed to move file")); } @@ -216,26 +329,28 @@ void ESPsTab::onActivate() void ESPsTab::onDeactivate() { - auto* item = selectedActive(); - if (!item) { - qWarning("ESPsTab::onDeactivate(): no selection"); + const auto index = ui->activeESPList->currentIndex(); + if (!index.isValid()) { return; } - ESP esp = item->esp(); + auto* esp = m_activeModel->getESP(index); + if (!esp) { + return; + } - if (!esp.isActive()) { + if (!esp->isActive()) { qWarning("ESPsTab::onDeactivate(): item is already inactive"); return; } - QDir root(esp.rootPath()); + QDir root(esp->rootPath()); // if we moved the file from optional to active in this session, we move the // file back to where it came from. Otherwise, it is moved to the new folder // "optional" - QString newName = esp.inactivePath(); + QString newName = esp->inactivePath(); if (newName.isEmpty()) { if (!root.exists("optional")) { @@ -245,46 +360,32 @@ void ESPsTab::onDeactivate() } } - newName = QString("optional") + QDir::separator() + esp.fileInfo().fileName(); + newName = QString("optional") + QDir::separator() + esp->fileInfo().fileName(); } - if (esp.deactivate(newName)) { - ui->activeESPList->takeItem(ui->activeESPList->row(item)); - ui->inactiveESPList->addItem(item); - item->setESP(esp); + if (esp->deactivate(newName)) { + // copy esp, original will be destroyed + auto copy = *esp; + + m_activeModel->removeRow(index.row()); + m_inactiveModel->addOne(std::move(copy)); + selectRow(ui->activeESPList, index.row()); } else { reportError(QObject::tr("Failed to move file")); } } -ESPItem* ESPsTab::selectedInactive() -{ - auto* item = ui->inactiveESPList->currentItem(); - if (!item) { - return nullptr; - } - - auto* esp = dynamic_cast(item); - if (!esp) { - qCritical("ESPsTab: inactive item is not an ESPItem"); - return nullptr; - } - - return esp; -} - -ESPItem* ESPsTab::selectedActive() +void ESPsTab::selectRow(QListView* list, int row) { - auto* item = ui->activeESPList->currentItem(); - if (!item) { - return nullptr; + const auto* model = list->model(); + const auto count = model->rowCount(); + if (count == 0) { + return; } - auto* esp = dynamic_cast(item); - if (!esp) { - qCritical("ESPsTab: active item is not an ESPItem"); - return nullptr; + if (row >= count) { + list->setCurrentIndex(model->index(count - 1, 0)); + } else { + list->setCurrentIndex(model->index(row, 0)); } - - return esp; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index e82ed368..c972074a 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -4,6 +4,7 @@ #include "modinfodialogtab.h" class ESPItem; +class ESPListModel; class ESPsTab : public ModInfoDialogTab { @@ -16,13 +17,15 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update(); private: + ESPListModel* m_inactiveModel; + ESPListModel* m_activeModel; + void onActivate(); void onDeactivate(); - - ESPItem* selectedInactive(); - ESPItem* selectedActive(); + void selectRow(QListView* list, int row); }; #endif // MODINFODIALOGESPS_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 08b8c988..25d799b1 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -9,6 +9,7 @@ public: void clear() { m_files.clear(); + endResetModel(); } QModelIndex index(int row, int col, const QModelIndex& ={}) const override @@ -62,7 +63,7 @@ public: return (naturalCompare(a.text, b.text) < 0); }); - emit dataChanged(index(0, 0), index(0, rowCount())); + endResetModel(); } QString fullPath(const QModelIndex& index) const -- cgit v1.3.1 From 86e4f65eb6a2e52d0513a8c2fe1e94cd1af9967d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 10:16:38 -0400 Subject: changed vsdark styles to remove left and right padding from list items --- src/stylesheets/vs15 Dark-Green.qss | 4 +++- src/stylesheets/vs15 Dark-Orange.qss | 4 +++- src/stylesheets/vs15 Dark-Purple.qss | 4 +++- src/stylesheets/vs15 Dark-Red.qss | 4 +++- src/stylesheets/vs15 Dark-Yellow.qss | 4 +++- src/stylesheets/vs15 Dark.qss | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/stylesheets/vs15 Dark-Green.qss b/src/stylesheets/vs15 Dark-Green.qss index 9c57d96e..9cf26a31 100644 --- a/src/stylesheets/vs15 Dark-Green.qss +++ b/src/stylesheets/vs15 Dark-Green.qss @@ -49,7 +49,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Orange.qss b/src/stylesheets/vs15 Dark-Orange.qss index bf7647b2..041f1b00 100644 --- a/src/stylesheets/vs15 Dark-Orange.qss +++ b/src/stylesheets/vs15 Dark-Orange.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Purple.qss b/src/stylesheets/vs15 Dark-Purple.qss index 1c9cb2b8..bc8bbcde 100644 --- a/src/stylesheets/vs15 Dark-Purple.qss +++ b/src/stylesheets/vs15 Dark-Purple.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Red.qss b/src/stylesheets/vs15 Dark-Red.qss index 73a90b15..0c9143cd 100644 --- a/src/stylesheets/vs15 Dark-Red.qss +++ b/src/stylesheets/vs15 Dark-Red.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark-Yellow.qss b/src/stylesheets/vs15 Dark-Yellow.qss index 1d43091d..2eb42534 100644 --- a/src/stylesheets/vs15 Dark-Yellow.qss +++ b/src/stylesheets/vs15 Dark-Yellow.qss @@ -50,7 +50,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, diff --git a/src/stylesheets/vs15 Dark.qss b/src/stylesheets/vs15 Dark.qss index dbeaaf28..d67cce35 100644 --- a/src/stylesheets/vs15 Dark.qss +++ b/src/stylesheets/vs15 Dark.qss @@ -49,7 +49,9 @@ QTreeWidget#categoriesTree::item, QListView::item, QTreeView#espList::item { - padding: 0.3em; } + padding-top: 0.3em; + padding-bottom: 0.3em; +} /* to enable border color */ QTreeView, -- cgit v1.3.1 From 139c33ccc4f529083b0288907caad2946a3f5a8f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 11:41:00 -0400 Subject: various optimizations and caching fixed conflict list not sorting when changing parameters switched from QDirIterator to std::filesystem, much faster FilterWidget precompiles the list --- src/filterwidget.cpp | 19 +++-- src/filterwidget.h | 3 + src/modinfodialog.cpp | 12 ++- src/modinfodialogconflicts.cpp | 186 ++++++++++++++++++++++++----------------- src/modinfodialogconflicts.h | 8 +- src/modinfodialogesps.cpp | 26 +++--- src/modinfodialogtextfiles.cpp | 15 ++-- 7 files changed, 155 insertions(+), 114 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 16a46b0e..82644658 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -30,21 +30,29 @@ void FilterWidget::clear() m_edit->clear(); } -bool FilterWidget::matches(std::function pred) const +void FilterWidget::compile() { + m_compiled.clear(); + const QStringList ORList = [&] { QString filterCopy = QString(m_text); filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); return filterCopy.split(";", QString::SkipEmptyParts); }(); - if (ORList.isEmpty() || !pred) { + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + m_compiled.push_back(ORSegment.split(" ", QString::SkipEmptyParts)); + } +} + +bool FilterWidget::matches(std::function pred) const +{ + if (m_compiled.isEmpty() || !pred) { return true; } - // split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts); + for (auto& ANDKeywords : m_compiled) { bool segmentGood = true; // check each word in the segment for match, each word needs to be matched @@ -115,6 +123,7 @@ void FilterWidget::onTextChanged() if (text != m_text) { m_text = text; + compile(); if (changed) { changed(); diff --git a/src/filterwidget.h b/src/filterwidget.h index 4fee5983..762d9b15 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -20,6 +20,7 @@ private: EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; + QList> m_compiled; void unhook(); void createClear(); @@ -28,6 +29,8 @@ private: void onTextChanged(); void onResized(); + + void compile(); }; #endif // FILTERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a407e9b7..c6cdb961 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogcategories.h" #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" +#include using namespace MOBase; using namespace MOShared; @@ -310,12 +311,17 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(bool becauseOriginChanged) { + namespace fs = std::filesystem; const auto rootPath = m_mod->absolutePath(); if (rootPath.length() > 0) { - QDirIterator dirIterator(rootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); + + for (const auto& entry : fs::recursive_directory_iterator(rootPath.toStdString())) { + if (!entry.is_regular_file()) { + continue; + } + + const auto fileName = QString::fromWCharArray(entry.path().c_str()); for (auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 03589030..52d25954 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -126,8 +126,9 @@ public: const QString& (ConflictItem::*getText)() const; }; - ConflictListModel(QTreeView* tree, std::vector columns) - : m_tree(tree), m_columns(std::move(columns)) + ConflictListModel(QTreeView* tree, std::vector columns) : + m_tree(tree), m_columns(std::move(columns)), + m_sortColumn(-1), m_sortOrder(Qt::AscendingOrder) { m_tree->setModel(this); } @@ -226,32 +227,10 @@ public: void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder) { - if (colIndex < 0) { - return; - } - - const auto c = static_cast(colIndex); - if (c >= m_columns.size()) { - return; - } - - const auto& col = m_columns[c]; - - // avoids branching on sort order while sorting - auto sortAsc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); - }; - - auto sortDesc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); - }; - - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortDesc); - } + m_sortColumn = colIndex; + m_sortOrder = order; + doSort(); emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); } @@ -263,6 +242,7 @@ public: void finished() { endResetModel(); + doSort(); } const ConflictItem* getItem(std::size_t row) const @@ -278,6 +258,37 @@ private: QTreeView* m_tree; std::vector m_columns; std::vector m_items; + int m_sortColumn; + Qt::SortOrder m_sortOrder; + + void doSort() + { + if (m_sortColumn < 0) { + return; + } + + const auto c = static_cast(m_sortColumn); + if (c >= m_columns.size()) { + return; + } + + const auto& col = m_columns[c]; + + // avoids branching on sort order while sorting + auto sortAsc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); + }; + + auto sortDesc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); + }; + + if (m_sortOrder == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortDesc); + } + } }; @@ -841,8 +852,9 @@ bool GeneralConflictsTab::update() const auto rootPath = m_tab->mod()->absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createXItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -851,28 +863,31 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { m_overwriteModel->add(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName), alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree m_noConflictModel->add(createNoConflictItem( - file->getIndex(), archive, fileName, relativeName)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName))); ++numNonConflicting; } } else { m_overwrittenModel->add(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, fileName, relativeName)); + file->getIndex(), fileOrigin, archive, + std::move(fileName), std::move(relativeName))); ++numOverwritten; } } - } - m_overwriteModel->finished(); - m_overwrittenModel->finished(); - m_noConflictModel->finished(); + m_overwriteModel->finished(); + m_overwrittenModel->finished(); + m_noConflictModel->finished(); + } ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); @@ -882,43 +897,48 @@ bool GeneralConflictsTab::update() } ConflictItem GeneralConflictsTab::createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, QString fileName, QString relativeName, const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; + std::wstring altString; for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; + if (!altString.empty()) { + altString += L", "; } - altString += ToQString(ds.getOriginByID(alt.first).getName()); + altString += ds.getOriginByID(alt.first).getName(); } - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); + auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); - return {altString, relativeName, "", index, fileName, true, origin, archive}; + return ConflictItem( + ToQString(altString), std::move(relativeName), QString::null, index, + std::move(fileName), true, std::move(origin), archive); } ConflictItem GeneralConflictsTab::createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName) + FileEntry::Index index, bool archive, QString fileName, QString relativeName) { - return {"", relativeName, "", index, fileName, false, "", archive}; + return ConflictItem( + QString::null, std::move(relativeName), QString::null, index, + std::move(fileName), false, QString::null, archive); } ConflictItem GeneralConflictsTab::createOverwrittenItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName) + QString fileName, QString relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); - return { - "", relativeName, ToQString(realOrigin.getName()), - index, fileName, true, ToQString(realOrigin.getName()), archive}; + QString after = ToQString(realOrigin.getName()); + QString altOrigin = after; + + return ConflictItem( + QString::null, std::move(relativeName), std::move(after), + index, std::move(fileName), true, std::move(altOrigin), archive); } void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index) @@ -1048,8 +1068,9 @@ void AdvancedConflictsTab::update() m_model->reserve(files.size()); for (const auto& file : files) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -1057,7 +1078,7 @@ void AdvancedConflictsTab::update() auto item = createItem( file->getIndex(), fileOrigin, archive, - fileName, relativeName, alternatives); + std::move(fileName), std::move(relativeName), alternatives); if (item) { m_model->add(std::move(*item)); @@ -1070,39 +1091,41 @@ void AdvancedConflictsTab::update() std::optional AdvancedConflictsTab::createItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString before, after; + std::wstring before, after; if (!alternatives.empty()) { + const bool showAllAlts = ui->conflictsAdvancedShowAll->isChecked(); + int beforePrio = 0; int afterPrio = std::numeric_limits::max(); for (const auto& alt : alternatives) { - const auto altOrigin = ds.getOriginByID(alt.first); + const auto& altOrigin = ds.getOriginByID(alt.first); - if (ui->conflictsAdvancedShowAll->isChecked()) { + if (showAllAlts) { // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // add all the mods having a lower priority than this one - if (!before.isEmpty()) { - before += ", "; + if (!before.empty()) { + before += L", "; } - before += ToQString(altOrigin.getName()); + before += altOrigin.getName(); } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // add all the mods having a higher priority than this one - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(altOrigin.getName()); + after += altOrigin.getName(); } } else { // keep track of the nearest mods that come before and after this one @@ -1114,7 +1137,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() > beforePrio) { // the alternative has a higher priority and therefore is closer // to this mod, use it - before = ToQString(altOrigin.getName()); + before = altOrigin.getName(); beforePrio = altOrigin.getPriority(); } } @@ -1125,7 +1148,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() < afterPrio) { // the alternative has a lower priority and there is closer // to this mod, use it - after = ToQString(altOrigin.getName()); + after = altOrigin.getName(); afterPrio = altOrigin.getPriority(); } } @@ -1140,41 +1163,46 @@ std::optional AdvancedConflictsTab::createItem( // nearest mods, it's not worth checking for the primary origin because it // will always have a higher priority than the alternatives (or it wouldn't // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { - FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + if (after.empty() || showAllAlts) { + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); // if no mods overwrite this file, the primary origin is the same as this // mod, so ignore that if (realOrigin.getID() != m_tab->origin()->getID()) { - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(realOrigin.getName()); + after += realOrigin.getName(); } } } - bool hasAlts = !before.isEmpty() || !after.isEmpty(); + const bool hasAlts = !before.empty() || !after.empty(); - if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + if (!hasAlts) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it - if (!hasAlts) { + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { return {}; } } - bool matched = m_filter.matches([&](auto&& what) { + auto beforeQS = QString::fromStdWString(before); + auto afterQS = QString::fromStdWString(after); + + const bool matched = m_filter.matches([&](auto&& what) { return - before.contains(what, Qt::CaseInsensitive) || + beforeQS.contains(what, Qt::CaseInsensitive) || relativeName.contains(what, Qt::CaseInsensitive) || - after.contains(what, Qt::CaseInsensitive); + afterQS.contains(what, Qt::CaseInsensitive); }); if (!matched) { return {}; } - return ConflictItem{before, relativeName, after, index, fileName, hasAlts, "", archive}; + return ConflictItem( + std::move(beforeQS), std::move(relativeName), std::move(afterQS), + index, std::move(fileName), hasAlts, QString::null, archive); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index f0c0c589..fc7dd32a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -47,16 +47,16 @@ private: ConflictItem createOverwriteItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); ConflictItem createNoConflictItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); ConflictItem createOverwrittenItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); void onOverwriteActivated(const QModelIndex& index); void onOverwrittenActivated(const QModelIndex& index); @@ -93,7 +93,7 @@ private: std::optional createItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); }; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 3f742bae..ecb341e8 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -18,7 +18,7 @@ public: m_active = true; } - updateFilename(); + pathChanged(); } const QString& rootPath() const @@ -50,9 +50,9 @@ public: return m_inactivePath; } - QFileInfo fileInfo() const + const QFileInfo& fileInfo() const { - return m_rootPath + QDir::separator() + relativePath(); + return m_fileInfo; } bool isActive() const @@ -73,7 +73,7 @@ public: m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; } - updateFilename(); + pathChanged(); return true; } @@ -88,7 +88,7 @@ public: if (root.rename(m_activePath, newName)) { m_active = false; m_inactivePath = newName; - updateFilename(); + pathChanged(); return true; } @@ -100,11 +100,13 @@ private: QString m_activePath; QString m_inactivePath; QString m_filename; + QFileInfo m_fileInfo; bool m_active; - void updateFilename() + void pathChanged() { - m_filename = fileInfo().fileName(); + m_fileInfo.setFile(m_rootPath + QDir::separator() + relativePath()); + m_filename = m_fileInfo.fileName(); } }; @@ -246,15 +248,11 @@ void ESPsTab::clear() bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) { - static constexpr const char* extensions[] = { - ".esp", ".esm", ".esl" - }; + static const QString extensions[] = {".esp", ".esm", ".esl"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - QString relativePath = fullPath.mid(rootPath.length() + 1); - - ESPItem esp(rootPath, relativePath); + ESPItem esp(rootPath, fullPath.mid(rootPath.length() + 1)); if (esp.isActive()) { m_activeModel->add(std::move(esp)); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 25d799b1..675a4c79 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -202,11 +202,9 @@ TextFilesTab::TextFilesTab( bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".txt" - }; + static const QString extensions[] = {".txt"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { return true; } @@ -226,13 +224,12 @@ IniFilesTab::IniFilesTab( bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".ini", ".cfg" - }; + static const QString extensions[] = {".ini", ".cfg"}; + static const QString meta("meta.ini"); - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (!fullPath.endsWith("meta.ini")) { + if (!fullPath.endsWith(meta)) { return true; } } -- cgit v1.3.1 From 2b57172eb8ab23f2fab580be73c570f44500ed8d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 12:26:25 -0400 Subject: added path and explore button to images tab --- src/modinfodialog.h | 14 ++++++++++++++ src/modinfodialog.ui | 44 +++++++++++++++++++++++++++++++++++++++--- src/modinfodialogconflicts.cpp | 18 ++++------------- src/modinfodialogimages.cpp | 39 +++++++++++++++++++++++++++++++++---- src/modinfodialogimages.h | 3 +++ 5 files changed, 97 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.h b/src/modinfodialog.h index e814dcf4..8ddaf86c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -45,6 +45,20 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa int naturalCompare(const QString& a, const QString& b); +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + + /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a152c292..9f4ae873 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -192,8 +192,11 @@ - - + + + + 3 + 0 @@ -206,6 +209,41 @@ 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Open in Explorer + + + + + + + true + + + + + + + + + @@ -403,7 +441,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 0 + 1 diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 52d25954..c053e64c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -13,20 +13,6 @@ using namespace MOBase; const std::size_t max_small_selection = 50; -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const - { - QStyledItemDelegate::initStyleOption(o, i); - o->textElideMode = Qt::ElideLeft; - } -}; - - class ConflictItem { public: @@ -263,6 +249,10 @@ private: void doSort() { + if (m_items.empty()) { + return; + } + if (m_sortColumn < 0) { return; } diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5b35a9b1..22c004e6 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,13 +1,18 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +#include "utility.h" ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_selection(nullptr) { - ui->imagesImage->layout()->addWidget(m_image); + auto* ly = new QVBoxLayout(ui->imagesImage); + ly->setContentsMargins({0, 0, 0, 0}); + ly->addWidget(m_image); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); @@ -18,13 +23,15 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); + getSupportedFormats(); } void ImagesTab::clear() { - m_image->clear(); m_files.clear(); + select(nullptr); setHasData(false); } @@ -64,6 +71,21 @@ void ImagesTab::getSupportedFormats() } } +void ImagesTab::select(const File* f) +{ + if (f) { + ui->imagesPath->setText(f->path); + ui->imagesExplore->setEnabled(true); + m_image->setImage(f->original); + } else { + ui->imagesPath->clear(); + ui->imagesExplore->setEnabled(false); + m_image->clear(); + } + + m_selection = f; +} + int ImagesTab::calcThumbSize(int availableWidth) const { return availableWidth - (m_margins * 2); @@ -227,10 +249,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) } if (const auto* file=fileAtPos(e->pos())) { - m_image->setImage(file->original); + select(file); } } +void ImagesTab::onExplore() +{ + if (!m_selection) { + return; + } + + MOBase::shell::ExploreFile(m_selection->path); +} + bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const { if (file.failed) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 9c3e5746..cb9a2d67 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -107,12 +107,15 @@ private: std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + const File* m_selection; void getSupportedFormats(); + void select(const File* file); void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void onExplore(); int calcThumbSize(int availableWidth) const; int calcWidgetHeight(int availableWidth) const; -- cgit v1.3.1 From fb93d9ff2d1c158fb546471f6e18e4dc9b965c2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 13:02:11 -0400 Subject: nexus tab: fixed to only make one request, changed css to match nexus more closely bbcode now supports [img width=x,height=x] images tab: don't reload the original image --- src/bbcode.cpp | 2 +- src/modinfodialogimages.cpp | 12 +++++------ src/modinfodialognexus.cpp | 52 +++++++++++++++++++++++++++++++++------------ src/modinfodialognexus.h | 1 + 4 files changed, 46 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 3475f1b2..9f064106 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -174,7 +174,7 @@ private: "\\1"); m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + m_TagMap["img"] = std::make_pair(QRegExp("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), ""); m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "\"\\1\""); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 22c004e6..9b0e98c5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -282,14 +282,14 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const void ImagesTab::reload(File& file, const QSize& scaledSize) { - file.original = {}; - file.thumbnail = {}; file.failed = false; - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; + if (file.original.isNull()) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } } file.thumbnail = file.original.scaled( diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index d296e000..797f7923 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -7,10 +7,16 @@ #include #include +bool isValidModID(int id) +{ + return (id > 0); +} + NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), + m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -53,6 +59,8 @@ void NexusTab::clear() void NexusTab::update() { + QScopedValueRollback loading(m_loading, true); + clear(); ui->modID->setText(QString("%1").arg(mod()->getNexusID())); @@ -123,7 +131,7 @@ void NexusTab::updateWebpage() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -147,16 +155,19 @@ void NexusTab::onModChanged() QString descriptionAsHTML = R"( - @@ -181,6 +192,10 @@ void NexusTab::onModChanged() void NexusTab::onModIDChanged() { + if (m_loading) { + return; + } + const int oldID = mod()->getNexusID(); const int newID = ui->modID->text().toInt(); @@ -190,7 +205,7 @@ void NexusTab::onModIDChanged() ui->browser->page()->setHtml(""); - if (newID != 0) { + if (isValidModID(newID)) { refreshData(newID); } } @@ -198,6 +213,10 @@ void NexusTab::onModIDChanged() void NexusTab::onSourceGameChanged() { + if (m_loading) { + return; + } + for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { mod()->setGameName(game->gameShortName()); @@ -210,6 +229,10 @@ void NexusTab::onSourceGameChanged() void NexusTab::onVersionChanged() { + if (m_loading) { + return; + } + MOBase::VersionInfo version(ui->version->text()); mod()->setVersion(version); updateVersionColor(); @@ -217,6 +240,10 @@ void NexusTab::onVersionChanged() void NexusTab::onUrlChanged() { + if (m_loading) { + return; + } + mod()->setURL(ui->url->text()); mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); } @@ -225,7 +252,7 @@ void NexusTab::onOpenLink() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -237,7 +264,7 @@ void NexusTab::onRefreshBrowser() { const auto modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { refreshData(modID); } else qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -259,18 +286,15 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { - if (modID <= 0) { - qDebug() << "NexusTab: can't refresh, no mod id"; + if (!isValidModID(modID)) { return false; } if (m_requestStarted) { - qDebug() << "NexusTab: a refresh request is already running"; return false; } if (!mod()->updateNXMInfo()) { - qDebug() << "NexusTab: nexus description does not need an update"; return false; } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index a09f4316..86d87c30 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -47,6 +47,7 @@ public: private: QMetaObject::Connection m_modConnection; bool m_requestStarted; + bool m_loading; void cleanup(); void updateVersionColor(); -- cgit v1.3.1 From 00d4921e47e5eef08b10dac127795ecc8ccf84ae Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 15:36:46 -0400 Subject: only load nexus website on activation fixed refresh not working, will now always clear the browser so the refresh is obvious --- src/modinfodialog.cpp | 34 +++++++++++++++++++++++++++++++++- src/modinfodialog.h | 3 ++- src/modinfodialognexus.cpp | 31 ++++++++++++++++--------------- src/modinfodialognexus.h | 1 + src/modinfodialogtab.cpp | 20 +++++++++++++++++++- src/modinfodialogtab.h | 5 +++++ 6 files changed, 76 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c6cdb961..f11fa6f3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -157,6 +157,8 @@ ModInfoDialog::ModInfoDialog( update(); }); } + + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -196,6 +198,10 @@ int ModInfoDialog::exec() void ModInfoDialog::setMod(ModInfo::Ptr mod) { m_mod = mod; + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->resetFirstActivation(); + } } void ModInfoDialog::setMod(const QString& name) @@ -225,8 +231,25 @@ void ModInfoDialog::setTab(ETabs id) switchToTab(id); } +ModInfoDialog::TabInfo* ModInfoDialog::currentTab() +{ + const auto index = ui->tabWidget->currentIndex(); + if (index < 0) { + return nullptr; + } + + const auto i = static_cast(index); + if (i >= m_tabs.size()) { + return nullptr; + } + + return &m_tabs[i]; +} + void ModInfoDialog::update(bool firstTime) { + const int oldTab = ui->tabWidget->currentIndex(); + setWindowTitle(m_mod->name()); setTabsVisibility(firstTime); @@ -236,6 +259,12 @@ void ModInfoDialog::update(bool firstTime) switchToTab(m_initialTab); m_initialTab = ETabs(-1); } + + if (ui->tabWidget->currentIndex() == oldTab) { + if (auto* tabInfo=currentTab()) { + tabInfo->tab->activated(); + } + } } void ModInfoDialog::setTabsVisibility(bool firstTime) @@ -533,8 +562,11 @@ void ModInfoDialog::on_closeButton_clicked() close(); } -void ModInfoDialog::on_tabWidget_currentChanged(int index) +void ModInfoDialog::onTabChanged() { + if (auto* tabInfo=currentTab()) { + tabInfo->tab->activated(); + } } void ModInfoDialog::on_nextButton_clicked() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 8ddaf86c..36363c34 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -118,7 +118,6 @@ signals: private slots: void on_closeButton_clicked(); - void on_tabWidget_currentChanged(int index); void on_nextButton_clicked(); void on_prevButton_clicked(); @@ -144,6 +143,7 @@ private: ETabs m_initialTab; std::vector createTabs(); + TabInfo* currentTab(); void restoreTabState(const QString& state); QString saveTabState() const; void update(bool firstTime=false); @@ -157,6 +157,7 @@ private: void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; void onOriginModified(std::size_t tabIndex, int originID); + void onTabChanged(); template std::unique_ptr createTab(int index) diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 797f7923..adf79060 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -26,7 +26,7 @@ NexusTab::NexusTab( connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); - connect(ui->refresh, &QToolButton::clicked, [&]{ updateWebpage(); }); + connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); connect( ui->sourceGame, @@ -96,10 +96,14 @@ void NexusTab::update() (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); - updateWebpage(); setHasData(mod()->getNexusID() >= 0); } +void NexusTab::firstActivation() +{ + updateWebpage(); +} + void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { cleanup(); @@ -265,9 +269,11 @@ void NexusTab::onRefreshBrowser() const auto modID = mod()->getNexusID(); if (isValidModID(modID)) { - refreshData(modID); - } else + mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + updateWebpage(); + } else { qInfo("Mod has no valid Nexus ID, info can't be updated."); + } } void NexusTab::onEndorse() @@ -286,17 +292,12 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { - if (!isValidModID(modID)) { - return false; - } - - if (m_requestStarted) { - return false; - } - - if (!mod()->updateNXMInfo()) { - return false; + if (isValidModID(modID) && !m_requestStarted) { + if (mod()->updateNXMInfo()) { + ui->browser->setHtml(""); + return true; + } } - return true; + return false; } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 86d87c30..92330704 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -41,6 +41,7 @@ public: void clear() override; void update() override; + void firstActivation() override; void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; bool usesOriginFiles() const override; diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 009fb804..d99e8727 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -7,10 +7,23 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabID(id), m_hasData(false) + m_origin(nullptr), m_tabID(id), m_hasData(false), m_firstActivation(true) { } +void ModInfoDialogTab::activated() +{ + if (m_firstActivation) { + m_firstActivation = false; + firstActivation(); + } +} + +void ModInfoDialogTab::resetFirstActivation() +{ + m_firstActivation = true; +} + void ModInfoDialogTab::update() { // no-op @@ -22,6 +35,11 @@ bool ModInfoDialogTab::feedFile(const QString&, const QString&) return false; } +void ModInfoDialogTab::firstActivation() +{ + // no-op +} + bool ModInfoDialogTab::canClose() { return true; diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index c85d2ded..41d913f8 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -21,9 +21,13 @@ public: ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; virtual ~ModInfoDialogTab() = default; + void activated(); + void resetFirstActivation(); + virtual void clear() = 0; virtual void update(); virtual bool feedFile(const QString& rootPath, const QString& filename); + virtual void firstActivation(); virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); @@ -70,6 +74,7 @@ private: MOShared::FilesOrigin* m_origin; int m_tabID; bool m_hasData; + bool m_firstActivation; }; -- cgit v1.3.1 From 19a19ead4059c600680d6e7ac193490e32e03245 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 16:41:32 -0400 Subject: filterwidget now support automatically installing a proxy on lists filters on txt and ini tabs --- src/filterwidget.cpp | 70 +++++++++++++++++++++++++++++++++++++++--- src/filterwidget.h | 41 ++++++++++++++++++++++--- src/modinfodialog.ui | 8 ++++- src/modinfodialogconflicts.cpp | 4 +-- src/modinfodialogtextfiles.cpp | 13 +++++--- src/modinfodialogtextfiles.h | 4 ++- 6 files changed, 123 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 82644658..1b9efd7c 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,12 +1,41 @@ #include "filterwidget.h" #include "eventfilter.h" -FilterWidget::FilterWidget() - : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr) +FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) + : QSortFilterProxyModel(parent), m_filter(fw) { + connect(&fw, &FilterWidget::changed, [&]{ invalidateFilter(); }); } -void FilterWidget::set(QLineEdit* edit) +bool FilterWidgetProxyModel::filterAcceptsRow( + int sourceRow, const QModelIndex& sourceParent) const +{ + const auto cols = sourceModel()->columnCount(); + + const auto m = m_filter.matches([&](auto&& what) { + for (int c=0; cindex(sourceRow, c, sourceParent); + const auto text = sourceModel()->data(index, Qt::DisplayRole).toString(); + + if (text.contains(what, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }); + + return m; +} + + +FilterWidget::FilterWidget() : + m_edit(nullptr), m_list(nullptr), m_proxy(nullptr), + m_eventFilter(nullptr), m_clear(nullptr) +{ +} + +void FilterWidget::setEdit(QLineEdit* edit) { unhook(); @@ -16,11 +45,22 @@ void FilterWidget::set(QLineEdit* edit) return; } + m_edit->setPlaceholderText(QObject::tr("Filter")); + createClear(); hookEvents(); clear(); } +void FilterWidget::setList(QAbstractItemView* list) +{ + m_list = list; + + m_proxy = new FilterWidgetProxyModel(*this); + m_proxy->setSourceModel(m_list->model()); + m_list->setModel(m_proxy); +} + void FilterWidget::clear() { if (!m_edit) { @@ -30,6 +70,16 @@ void FilterWidget::clear() m_edit->clear(); } +QModelIndex FilterWidget::map(const QModelIndex& index) +{ + if (m_proxy) { + return m_proxy->mapToSource(index); + } else { + qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + return index; + } +} + void FilterWidget::compile() { m_compiled.clear(); @@ -83,6 +133,14 @@ void FilterWidget::unhook() if (m_edit) { m_edit->removeEventFilter(m_eventFilter); } + + if (m_proxy && m_list) { + auto* model = m_proxy->sourceModel(); + m_proxy->setSourceModel(nullptr); + delete m_proxy; + + m_list->setModel(model); + } } void FilterWidget::createClear() @@ -125,9 +183,11 @@ void FilterWidget::onTextChanged() m_text = text; compile(); - if (changed) { - changed(); + if (m_proxy) { + m_proxy->invalidateFilter(); } + + emit changed(); } } diff --git a/src/filterwidget.h b/src/filterwidget.h index 762d9b15..5b08c3ae 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -1,22 +1,55 @@ #ifndef FILTERWIDGET_H #define FILTERWIDGET_H +#include +#include +#include +#include +#include + class EventFilter; +class FilterWidget; -class FilterWidget +class FilterWidgetProxyModel : public QSortFilterProxyModel { + Q_OBJECT; + public: - std::function changed; + FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent=nullptr); + using QSortFilterProxyModel::invalidateFilter; + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const override; + +private: + FilterWidget& m_filter; +}; + + +class FilterWidget : public QObject +{ + Q_OBJECT; + +public: + using predFun = std::function; FilterWidget(); - void set(QLineEdit* edit); + void setEdit(QLineEdit* edit); + void setList(QAbstractItemView* list); void clear(); - bool matches(std::function pred) const; + QModelIndex map(const QModelIndex& index); + + bool matches(predFun pred) const; + +signals: + void changed(); private: QLineEdit* m_edit; + QAbstractItemView* m_list; + FilterWidgetProxyModel* m_proxy; EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 9f4ae873..69e73da4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -72,6 +72,9 @@ + + + @@ -108,7 +111,7 @@ Qt::Horizontal - + 6 @@ -132,6 +135,9 @@
+ + + diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index c053e64c..e631db30 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -999,8 +999,8 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); - m_filter.set(ui->conflictsAdvancedFilter); - m_filter.changed = [&]{ update(); }; + m_filter.setEdit(ui->conflictsAdvancedFilter); + QObject::connect(&m_filter, &FilterWidget::changed, [&]{ update(); }); } void AdvancedConflictsTab::clear() diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 675a4c79..2b5ab489 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -100,7 +100,7 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e) : + QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e), m_model(new FileListModel) { @@ -111,6 +111,9 @@ GenericFilesTab::GenericFilesTab( sp->setStretchFactor(0, 0); sp->setStretchFactor(1, 1); + m_filter.setEdit(filter); + m_filter.setList(m_list); + QObject::connect( m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, [&](auto current, auto previous){ onSelection(current, previous); }); @@ -187,7 +190,7 @@ void GenericFilesTab::select(const QModelIndex& index) } m_editor->setEnabled(true); - m_editor->load(m_model->fullPath(index)); + m_editor->load(m_model->fullPath(m_filter.map(index))); } @@ -196,7 +199,8 @@ TextFilesTab::TextFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) + ui->textFileList, ui->tabTextSplitter, + ui->textFileEditor, ui->textFileFilter) { } @@ -218,7 +222,8 @@ IniFilesTab::IniFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) + ui->iniFileList, ui->tabIniSplitter, + ui->iniFileEditor, ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d879c2bd..037c6bb3 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -2,6 +2,7 @@ #define MODINFODIALOGTEXTFILES_H #include "modinfodialogtab.h" +#include "filterwidget.h" #include #include @@ -23,11 +24,12 @@ protected: QListView* m_list; TextEditor* m_editor; FileListModel* m_model; + FilterWidget m_filter; GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor); + QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; -- cgit v1.3.1 From 65b3eb24fc5e3e1033d583a24cb51a19b0cfac2c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:20:19 -0400 Subject: filterable images --- src/filterwidget.cpp | 5 ++ src/filterwidget.h | 1 + src/modinfodialog.ui | 3 ++ src/modinfodialogimages.cpp | 111 +++++++++++++++++++++++++++++++++++--------- src/modinfodialogimages.h | 9 ++++ 5 files changed, 108 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 1b9efd7c..44cbb274 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -70,6 +70,11 @@ void FilterWidget::clear() m_edit->clear(); } +bool FilterWidget::empty() const +{ + return m_text.isEmpty(); +} + QModelIndex FilterWidget::map(const QModelIndex& index) { if (m_proxy) { diff --git a/src/filterwidget.h b/src/filterwidget.h index 5b08c3ae..4fb9831f 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -38,6 +38,7 @@ public: void setEdit(QLineEdit* edit); void setList(QAbstractItemView* list); void clear(); + bool empty() const; QModelIndex map(const QModelIndex& index); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 69e73da4..fd367e74 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -196,6 +196,9 @@
+ + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 9b0e98c5..deaacd14 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -23,6 +23,9 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + m_filter.setEdit(ui->imagesFilter); + connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); }); + connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); getSupportedFormats(); @@ -31,6 +34,8 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { m_files.clear(); + m_filteredFiles.clear(); + select(nullptr); setHasData(false); } @@ -50,10 +55,62 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { setHasData(!m_files.empty()); + + filterImages(); resizeWidget(); ui->imagesThumbnails->update(); } +void ImagesTab::filterImages() +{ + m_filteredFiles.clear(); + + if (m_filter.empty()) { + return; + } + + for (auto& f : m_files) { + const auto m = m_filter.matches([&](auto&& what) { + return f.path.contains(what, Qt::CaseInsensitive); + }); + + if (m) { + m_filteredFiles.push_back(&f); + } + } +} + +std::size_t ImagesTab::fileCount() const +{ + if (m_filter.empty()) { + return m_files.size(); + } else { + return m_filteredFiles.size(); + } +} + +const ImagesTab::File* ImagesTab::getFile(std::size_t i) const +{ + if (m_filter.empty()) { + if (i >= m_files.size()) { + return nullptr; + } + + return &m_files[i]; + } else { + if (i >= m_filteredFiles.size()) { + return nullptr; + } + + return m_filteredFiles[i]; + } +} + +ImagesTab::File* ImagesTab::getFile(std::size_t i) +{ + return const_cast(std::as_const(*this).getFile(i)); +} + void ImagesTab::getSupportedFormats() { for (const auto& entry : QImageReader::supportedImageFormats()) { @@ -74,7 +131,7 @@ void ImagesTab::getSupportedFormats() void ImagesTab::select(const File* f) { if (f) { - ui->imagesPath->setText(f->path); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); } else { @@ -93,7 +150,7 @@ int ImagesTab::calcThumbSize(int availableWidth) const int ImagesTab::calcWidgetHeight(int availableWidth) const { - if (m_files.empty()) { + if (fileCount() == 0) { return 0; } @@ -106,7 +163,7 @@ int ImagesTab::calcWidgetHeight(int availableWidth) const // subsequent thumbs with padding before each const auto thumbWithPadding = m_padding + thumbSize; - h += static_cast(thumbWithPadding * (m_files.size() - 1)); + h += static_cast(thumbWithPadding * (fileCount() - 1)); // margin top and bottom h += m_margins * 2; @@ -170,7 +227,8 @@ void ImagesTab::paintThumbnails(QPaintEvent* e) const auto [begin, end] = calcVisibleRange( e->rect().top(), e->rect().bottom(), cx.thumbSize); - for (std::size_t i=begin; ifailed) { return; } const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i); - if (needsReload(file, imageRect.size())) { - reload(file, imageRect.size()); + if (needsReload(*file, imageRect.size())) { + reload(*file, imageRect.size()); } - if (file.thumbnail.isNull()) { + if (file->thumbnail.isNull()) { return; } // center scaled image in rect const QRect scaledThumbRect( - (imageRect.left()+imageRect.width()/2) - file.thumbnail.width()/2, - (imageRect.top()+imageRect.height()/2) - file.thumbnail.height()/2, - file.thumbnail.width(), - file.thumbnail.height()); + (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2, + (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2, + file->thumbnail.width(), + file->thumbnail.height()); - cx.painter.drawImage(scaledThumbRect, file.thumbnail); + cx.painter.drawImage(scaledThumbRect, file->thumbnail); } const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const @@ -222,7 +285,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const // calculate index purely based on y position const std::size_t i = p.y() / (thumbSize + m_padding); - if (i >= m_files.size()) { + if (i >= fileCount()) { return nullptr; } @@ -234,7 +297,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const return nullptr; } - return &m_files[i]; + return getFile(i); } void ImagesTab::scrollAreaResized(const QSize&) @@ -248,9 +311,7 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) return; } - if (const auto* file=fileAtPos(e->pos())) { - select(file); - } + select(fileAtPos(e->pos())); } void ImagesTab::onExplore() @@ -262,6 +323,11 @@ void ImagesTab::onExplore() MOBase::shell::ExploreFile(m_selection->path); } +void ImagesTab::onFilterChanged() +{ + update(); +} + bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const { if (file.failed) { @@ -299,14 +365,17 @@ void ImagesTab::reload(File& file, const QSize& scaledSize) void ImagesTab::resizeWidget() { - if (m_files.empty()) { + if (fileCount() == 0) { ui->imagesThumbnails->setGeometry(QRect()); return; } const auto availableWidth = ui->imagesScrollArea->viewport()->width(); - const int widgetHeight = calcWidgetHeight(availableWidth); + const int widgetHeight = std::max( + calcWidgetHeight(availableWidth), + ui->imagesScrollArea->viewport()->height()); + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index cb9a2d67..d90f2bfd 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -2,6 +2,7 @@ #define MODINFODIALOGIMAGES_H #include "modinfodialogtab.h" +#include "filterwidget.h" #include class ImagesTab; @@ -105,9 +106,11 @@ private: ScalableImage* m_image; std::vector m_files; + std::vector m_filteredFiles; std::vector m_supportedFormats; int m_margins, m_padding, m_border; const File* m_selection; + FilterWidget m_filter; void getSupportedFormats(); void select(const File* file); @@ -116,6 +119,7 @@ private: void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); void onExplore(); + void onFilterChanged(); int calcThumbSize(int availableWidth) const; int calcWidgetHeight(int availableWidth) const; @@ -134,6 +138,11 @@ private: const File* fileAtPos(const QPoint& p) const; + std::size_t fileCount() const; + const File* getFile(std::size_t i) const; + File* getFile(std::size_t i); + + void filterImages(); bool needsReload(const File& file, const QSize& imageSize) const; void reload(File& file, const QSize& imageSize); void resizeWidget(); -- cgit v1.3.1 From fa20f81b46c3f8c89fcbbdff6e5da0225c707358 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:44:41 -0400 Subject: replaced reportError() calls by qCritical() because they can come from a thread, which hangs the ui categories and notes will update the tab color when changing fixed rare crash when reordering tabs while loading a mod --- src/modinfodialog.cpp | 18 ++++++++++++++++-- src/modinfodialog.h | 1 + src/modinfodialog.ui | 7 ++----- src/modinfodialogcategories.cpp | 3 ++- src/modinfodialogesps.cpp | 3 ++- src/modinfodialogtab.cpp | 36 ++++++++++++++++++++++++------------ src/modinfodialogtab.h | 2 ++ src/modinfodialogtextfiles.cpp | 2 +- src/modinforegular.cpp | 8 ++++++-- 9 files changed, 56 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f11fa6f3..7696023f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -124,7 +124,8 @@ ModInfoDialog::ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), - m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)) + m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), + m_arrangingTabs(false) { ui->setupUi(this); @@ -156,6 +157,10 @@ ModInfoDialog::ModInfoDialog( setMod(name); update(); }); + + connect( + tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, + [&]{ setTabsColors(); }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -269,6 +274,8 @@ void ModInfoDialog::update(bool firstTime) void ModInfoDialog::setTabsVisibility(bool firstTime) { + QScopedValueRollback arrangingTabs(m_arrangingTabs, true); + std::vector visibility(m_tabs.size()); bool changed = false; @@ -374,7 +381,7 @@ void ModInfoDialog::setTabsColors() for (const auto& tabInfo : m_tabs) { const auto c = tabInfo.tab->hasData() ? QColor::Invalid : - ui->tabWidget->palette().color(QPalette::Disabled, QPalette::WindowText); + m_mainWindow->palette().color(QPalette::Disabled, QPalette::WindowText); ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); } @@ -564,6 +571,13 @@ void ModInfoDialog::on_closeButton_clicked() void ModInfoDialog::onTabChanged() { + if (m_arrangingTabs) { + // this can be fired while re-arranging tabs, which happens before mods + // are given to tabs, and might trigger first activation, which breaks all + // sorts of things + return; + } + if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 36363c34..6dc66003 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -141,6 +141,7 @@ private: PluginContainer* m_plugin; std::vector m_tabs; ETabs m_initialTab; + bool m_arrangingTabs; std::vector createTabs(); TabInfo* currentTab(); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index fd367e74..30ea9217 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -810,9 +810,6 @@ text-align: left; - - true - false @@ -1038,7 +1035,7 @@ p, li { white-space: pre-wrap; } - + Enter comments about the mod here. These are displayed in the notes column of the mod list. @@ -1051,7 +1048,7 @@ p, li { white-space: pre-wrap; } - + Enter notes about the mod here. These can be viewed in the mod list by hovering over the notes column or the flags column. diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 0d739d1f..8ffded59 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -34,7 +34,6 @@ void CategoriesTab::update() ui->categories->invisibleRootItem(), 0); updatePrimary(); - setHasData(ui->primaryCategories->count() > 0); } bool CategoriesTab::canHandleSeparators() const @@ -92,6 +91,8 @@ void CategoriesTab::updatePrimary() break; } } + + setHasData(ui->primaryCategories->count() > 0); } void CategoriesTab::addChecked(QTreeWidgetItem* tree) diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index ecb341e8..7961096d 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -260,7 +260,6 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) m_inactiveModel->add(std::move(esp)); } - setHasData(true); return true; } } @@ -272,6 +271,8 @@ void ESPsTab::update() { m_inactiveModel->finished(); m_activeModel->finished(); + + setHasData(m_inactiveModel->rowCount() > 0 || m_activeModel->rowCount() > 0); } void ESPsTab::onActivate() diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index d99e8727..f5eeeed1 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -131,7 +131,10 @@ void ModInfoDialogTab::emitModOpen(QString name) void ModInfoDialogTab::setHasData(bool b) { - m_hasData = b; + if (m_hasData != b) { + m_hasData = b; + emit hasDataChanged(); + } } @@ -140,14 +143,14 @@ NotesTab::NotesTab( QWidget* parent, Ui::ModInfoDialog* ui, int index) : ModInfoDialogTab(oc, plugin, parent, ui, index) { - connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); - connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); + connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); + connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); } void NotesTab::clear() { - ui->commentsEdit->clear(); - ui->notesEdit->clear(); + ui->comments->clear(); + ui->notes->clear(); setHasData(false); } @@ -156,10 +159,9 @@ void NotesTab::update() const auto comments = mod()->comments(); const auto notes = mod()->notes(); - ui->commentsEdit->setText(comments); - ui->notesEdit->setText(notes); - - setHasData(!comments.isEmpty() || !notes.isEmpty()); + ui->comments->setText(comments); + ui->notes->setText(notes); + checkHasData(); } bool NotesTab::canHandleSeparators() const @@ -169,20 +171,30 @@ bool NotesTab::canHandleSeparators() const void NotesTab::onComments() { - mod()->setComments(ui->commentsEdit->text()); + mod()->setComments(ui->comments->text()); + checkHasData(); } void NotesTab::onNotes() { // Avoid saving html stub if notes field is empty. - if (ui->notesEdit->toPlainText().isEmpty()) { + if (ui->notes->toPlainText().isEmpty()) { mod()->setNotes({}); } else { - mod()->setNotes(ui->notesEdit->toHtml()); + mod()->setNotes(ui->notes->toHtml()); } + + checkHasData(); } bool NotesTab::usesOriginFiles() const { return false; } + +void NotesTab::checkHasData() +{ + setHasData( + !ui->comments->text().isEmpty() || + !ui->notes->toPlainText().isEmpty()); +} diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 41d913f8..058035ab 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -49,6 +49,7 @@ public: signals: void originModified(int originID); void modOpen(QString name); + void hasDataChanged(); protected: Ui::ModInfoDialog* ui; @@ -93,6 +94,7 @@ public: private: void onComments(); void onNotes(); + void checkHasData(); }; #endif // MODINFODIALOGTAB_H diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 2b5ab489..e3d00049 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -158,7 +158,6 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { m_model->add(rootPath, fullPath); - setHasData(true); return true; } } @@ -169,6 +168,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) void GenericFilesTab::update() { m_model->finished(); + setHasData(m_model->rowCount() > 0); } void GenericFilesTab::onSelection( diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index babbd665..4333e351 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -194,10 +194,14 @@ void ModInfoRegular::saveMeta() if (metaFile.status() == QSettings::NoError) { m_MetaInfoChanged = false; } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + qCritical() + << QString("failed to write %1/meta.ini: error %2") + .arg(absolutePath()).arg(metaFile.status()); } } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + qCritical() + << QString("failed to write %1/meta.ini: error %2") + .arg(absolutePath()).arg(metaFile.status()); } } } -- cgit v1.3.1 From fc3ed80d0de220a1aa9d2b459785d47beea5861b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:58:34 -0400 Subject: tooltip on images --- src/modinfodialogimages.cpp | 24 ++++++++++++++++++++++++ src/modinfodialogimages.h | 2 ++ 2 files changed, 26 insertions(+) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index deaacd14..08fd5c5b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -314,6 +314,20 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::showTooltip(QHelpEvent* e) +{ + const auto* f = fileAtPos(e->pos()); + if (!f) { + QToolTip::hideText(); + e->ignore(); + return; + } + + QToolTip::showText( + e->globalPos(), QDir::toNativeSeparators(f->path), + ui->imagesThumbnails); +} + void ImagesTab::onExplore() { if (!m_selection) { @@ -412,6 +426,16 @@ void ImagesThumbnails::mousePressEvent(QMouseEvent* e) } } +bool ImagesThumbnails::event(QEvent* e) +{ + if (e->type() == QEvent::ToolTip) { + m_tab->showTooltip(static_cast(e)); + return true; + } + + return QWidget::event(e); +} + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d90f2bfd..bd404801 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -34,6 +34,7 @@ public: protected: void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; + bool event(QEvent* e) override; private: ImagesTab* m_tab = nullptr; @@ -118,6 +119,7 @@ private: void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void showTooltip(QHelpEvent* e); void onExplore(); void onFilterChanged(); -- cgit v1.3.1 From cb56bf76fd8036895bda468a5ad9ef707dbefce9 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 18:10:00 -0400 Subject: fixed text editors not clearing when switching mods ported typo fixes that applied to the mod info dialog --- src/modinfodialog.ui | 2 +- src/modinfodialogfiletree.cpp | 4 ++-- src/modinfodialogtextfiles.cpp | 1 + src/texteditor.cpp | 8 ++++++++ src/texteditor.h | 1 + 5 files changed, 13 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 30ea9217..7a7d82da 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -426,7 +426,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e ESPs in the data directory and thus visible to the game. - These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window. + <html><head/><body><p>These are the mod files that are in the (virtual) data directory of your game and will thus be selectable in the esp list in the main window.</p></body></html> true diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index b57f6b5d..24faec5e 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -166,9 +166,9 @@ void FileTreeTab::onDelete() if (rows.count() == 1) { QString fileName = m_fs->fileName(rows[0]); - message = tr("Are sure you want to delete \"%1\"?").arg(fileName); + message = tr("Are you sure you want to delete \"%1\"?").arg(fileName); } else { - message = tr("Are sure you want to delete the selected files?"); + message = tr("Are you sure you want to delete the selected files?"); } if (QMessageBox::question(parentWidget(), tr("Confirm"), message) != QMessageBox::Yes) { diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index e3d00049..5b035c53 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -185,6 +185,7 @@ void GenericFilesTab::onSelection( void GenericFilesTab::select(const QModelIndex& index) { if (!index.isValid()) { + m_editor->clear(); m_editor->setEnabled(false); return; } diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 6c1685da..ee36c104 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -57,6 +57,14 @@ void TextEditor::setDefaultStyle() setHighlightBackgroundColor(backgroundColor); } +void TextEditor::clear() +{ + m_filename.clear(); + m_encoding.clear(); + setPlainText(""); + dirty(false); +} + bool TextEditor::load(const QString& filename) { m_filename = filename; diff --git a/src/texteditor.h b/src/texteditor.h index f3031731..775565f2 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -91,6 +91,7 @@ public: void setupToolbar(); + void clear(); bool load(const QString& filename); bool save(); -- cgit v1.3.1 From cac0fb710c48fa55c6e7303485adf9f4f3364682 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 18:54:47 -0400 Subject: added explore button to editors fixed save button on editor being enabled with empty documents added ctrl+s always show vertical scrollbar in thumbnails, fixes nonstop relayouts when the height is just right and the scrollbar flickers on and off --- src/modinfodialog.ui | 6 ++++++ src/texteditor.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++------ src/texteditor.h | 6 ++++++ 3 files changed, 66 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 7a7d82da..56c97c34 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -168,6 +168,9 @@ Qt::Horizontal + + false + @@ -184,6 +187,9 @@ + + Qt::ScrollBarAlwaysOn + diff --git a/src/texteditor.cpp b/src/texteditor.cpp index ee36c104..9bbe3ddd 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -5,7 +5,7 @@ TextEditor::TextEditor(QWidget* parent) : QPlainTextEdit(parent), m_toolbar(nullptr), m_lineNumbers(nullptr), m_highlighter(nullptr), - m_dirty(false) + m_dirty(false), m_loading(false) { m_toolbar = new TextEditorToolbar(*this); m_lineNumbers = new TextEditorLineNumbers(*this); @@ -59,17 +59,25 @@ void TextEditor::setDefaultStyle() void TextEditor::clear() { + QScopedValueRollback loading(m_loading, true); + m_filename.clear(); m_encoding.clear(); setPlainText(""); dirty(false); + document()->setModified(false); + + emit loaded(""); } bool TextEditor::load(const QString& filename) { - m_filename = filename; clear(); + QScopedValueRollback loading(m_loading, true); + + m_filename = filename; + const QString s = MOBase::readFileText(filename, &m_encoding); setPlainText(s); @@ -81,6 +89,8 @@ bool TextEditor::load(const QString& filename) onModified(false); } + emit loaded(m_filename); + return true; } @@ -180,8 +190,21 @@ void TextEditor::setHighlightBackgroundColor(const QColor& c) update(); } +void TextEditor::explore() +{ + if (m_filename.isEmpty()) { + return; + } + + MOBase::shell::ExploreFile(m_filename); +} + void TextEditor::onModified(bool b) { + if (m_loading) { + return; + } + dirty(b); emit modified(b); } @@ -431,15 +454,27 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } -TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : - m_editor(editor), - m_save(new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"))), - m_wordWrap(new QAction(QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"))) +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) + : m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr) { - QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); + m_save = new QAction( + QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); + + m_save->setShortcutContext(Qt::WidgetWithChildrenShortcut); + m_save->setShortcut(Qt::CTRL + Qt::Key_S); + m_editor.addAction(m_save); + + m_wordWrap = new QAction( + QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); + + m_explore = new QAction( + QObject::tr("&Open in Explorer"), &editor); m_wordWrap->setCheckable(true); + + QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); + QObject::connect(m_explore, &QAction::triggered, [&]{ m_editor.explore(); }); auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -453,8 +488,13 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : b->setDefaultAction(m_wordWrap); layout->addWidget(b); + b = new QToolButton; + b->setDefaultAction(m_explore); + layout->addWidget(b); + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); + QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f){ onLoaded(f); }); } void TextEditorToolbar::onTextModified(bool b) @@ -467,6 +507,13 @@ void TextEditorToolbar::onWordWrap(bool b) m_wordWrap->setChecked(b); } +void TextEditorToolbar::onLoaded(const QString& s) +{ + const auto hasDoc = !s.isEmpty(); + + m_explore->setEnabled(hasDoc); + m_wordWrap->setEnabled(hasDoc); +} void HTMLEditor::focusOutEvent(QFocusEvent* e) { diff --git a/src/texteditor.h b/src/texteditor.h index 775565f2..798222a3 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -16,9 +16,11 @@ private: TextEditor& m_editor; QAction* m_save; QAction* m_wordWrap; + QAction* m_explore; void onTextModified(bool b); void onWordWrap(bool b); + void onLoaded(const QString& s); }; @@ -112,7 +114,10 @@ public: QColor highlightBackgroundColor() const; void setHighlightBackgroundColor(const QColor& c); + void explore(); + signals: + void loaded(QString filename); void modified(bool b); void wordWrapChanged(bool b); @@ -127,6 +132,7 @@ private: QString m_filename; QString m_encoding; bool m_dirty; + bool m_loading; void setDefaultStyle(); void onModified(bool b); -- cgit v1.3.1 From 9cce32f19f435570b94c6f65c2bc6eb99bd542ac Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 01:58:13 -0400 Subject: dds toggle for images --- src/modinfodialog.ui | 10 ++++ src/modinfodialogimages.cpp | 108 ++++++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 6 +++ 3 files changed, 104 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 56c97c34..3ea0a27b 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -173,6 +173,9 @@ + + 3 + 0 @@ -202,6 +205,13 @@ + + + + Show .dds files + + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 08fd5c5b..e25fd838 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,5 +1,6 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +#include "settings.h" #include "utility.h" ImagesTab::ImagesTab( @@ -7,8 +8,10 @@ ImagesTab::ImagesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), - m_selection(nullptr) + m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) { + getSupportedFormats(); + auto* ly = new QVBoxLayout(ui->imagesImage); ly->setContentsMargins({0, 0, 0, 0}); ly->addWidget(m_image); @@ -23,12 +26,15 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); + m_filter.setEdit(ui->imagesFilter); connect(&m_filter, &FilterWidget::changed, [&]{ onFilterChanged(); }); connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); + connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); - getSupportedFormats(); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); } void ImagesTab::clear() @@ -54,55 +60,88 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { - setHasData(!m_files.empty()); - filterImages(); resizeWidget(); ui->imagesThumbnails->update(); + + setHasData(fileCount() > 0); } -void ImagesTab::filterImages() +void ImagesTab::saveState(Settings& s) { - m_filteredFiles.clear(); + s.directInterface().setValue( + "mod_info_dialog_images_show_dds", m_ddsEnabled); +} + +void ImagesTab::restoreState(const Settings& s) +{ + ui->imagesShowDDS->setChecked(s.directInterface() + .value("mod_info_dialog_images_show_dds", false).toBool()); +} - if (m_filter.empty()) { +void ImagesTab::filterImages() +{ + if (!needsFiltering()) { return; } + m_filteredFiles.clear(); + + bool hasTextFilter = !m_filter.empty(); + bool sawSelection = false; + for (auto& f : m_files) { - const auto m = m_filter.matches([&](auto&& what) { - return f.path.contains(what, Qt::CaseInsensitive); - }); + if (hasTextFilter) { + const auto m = m_filter.matches([&](auto&& what) { + return f.path.contains(what, Qt::CaseInsensitive); + }); + + if (!m) { + continue; + } + } + + if (!m_ddsEnabled) { + if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { + continue; + } + } - if (m) { - m_filteredFiles.push_back(&f); + if (&f == m_selection) { + sawSelection = true; } + + m_filteredFiles.push_back(&f); + } + + if (!sawSelection) { + select(nullptr); } } std::size_t ImagesTab::fileCount() const { - if (m_filter.empty()) { - return m_files.size(); - } else { + if (needsFiltering()) { return m_filteredFiles.size(); + } else { + return m_files.size(); } } const ImagesTab::File* ImagesTab::getFile(std::size_t i) const { - if (m_filter.empty()) { - if (i >= m_files.size()) { + if (needsFiltering()) { + if (i >= m_filteredFiles.size()) { return nullptr; } - return &m_files[i]; + return m_filteredFiles[i]; } else { - if (i >= m_filteredFiles.size()) { + if (i >= m_files.size()) { return nullptr; } - return m_filteredFiles[i]; + return &m_files[i]; } } @@ -111,14 +150,34 @@ ImagesTab::File* ImagesTab::getFile(std::size_t i) return const_cast(std::as_const(*this).getFile(i)); } +bool ImagesTab::needsFiltering() const +{ + if (!m_filter.empty()) { + return true; + } + + if (!m_ddsEnabled) { + return true; + } + + return false; +} + void ImagesTab::getSupportedFormats() { + m_ddsAvailable = false; + for (const auto& entry : QImageReader::supportedImageFormats()) { QString s(entry); if (s.isNull() || s.isEmpty()) { continue; } + // used to enable the checkbox + if (s.compare("dds", Qt::CaseInsensitive) == 0) { + m_ddsAvailable = true; + } + // make sure it starts with a dot if (s[0] != ".") { s = "." + s; @@ -337,6 +396,15 @@ void ImagesTab::onExplore() MOBase::shell::ExploreFile(m_selection->path); } +void ImagesTab::onShowDDS() +{ + const auto b = ui->imagesShowDDS->isChecked(); + if (b != m_ddsEnabled) { + m_ddsEnabled = b; + update(); + } +} + void ImagesTab::onFilterChanged() { update(); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index bd404801..fa7115b0 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -79,6 +79,8 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update() override; + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; private: struct File @@ -112,15 +114,19 @@ private: int m_margins, m_padding, m_border; const File* m_selection; FilterWidget m_filter; + bool m_ddsAvailable, m_ddsEnabled; void getSupportedFormats(); + void enableDDS(bool b); void select(const File* file); + bool needsFiltering() const; void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); void showTooltip(QHelpEvent* e); void onExplore(); + void onShowDDS(); void onFilterChanged(); int calcThumbSize(int availableWidth) const; -- cgit v1.3.1 From 0fc928fd815f27eef77491224bb8922b4e070f2d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 07:03:40 -0400 Subject: rework of the images tab: - uses a custom widget with a scrollbar instead of a QScrollArea - scrolling by image instead of pixel - refactored all the geometry stuff in ImagesGeometry --- src/modinfodialog.ui | 51 +++--- src/modinfodialogimages.cpp | 393 ++++++++++++++++++++++++++------------------ src/modinfodialogimages.h | 189 ++++++++++++++++----- 3 files changed, 418 insertions(+), 215 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 3ea0a27b..a9af2593 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -172,7 +172,7 @@ false - + 3 @@ -189,20 +189,34 @@ 0 - - - Qt::ScrollBarAlwaysOn - - - - - 0 - 0 - 686 - 436 - + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 - + + + + + + + Qt::Vertical + + + + @@ -1221,16 +1235,15 @@ p, li { white-space: pre-wrap; }
texteditor.h
- ImagesScrollArea - QScrollArea + ImagesThumbnails + QWidget
modinfodialogimages.h
1
- ImagesThumbnails - QWidget + ImagesScrollbar + QScrollBar
modinfodialogimages.h
- 1
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index e25fd838..5c93e3d4 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -3,11 +3,36 @@ #include "settings.h" #include "utility.h" +QSize resizeWithAspectRatio(const QSize& original, const QSize& available) +{ + const auto ratio = std::min({ + 1.0, + static_cast(available.width()) / original.width(), + static_cast(available.height()) / original.height()}); + + const QSize scaledSize( + static_cast(std::round(original.width() * ratio)), + static_cast(std::round(original.height() * ratio))); + + return scaledSize; +} + +QRect centeredRect(const QRect& rect, const QSize& size) +{ + return QRect( + (rect.left()+rect.width()/2) - size.width()/2, + (rect.top()+rect.height()/2) - size.height()/2, + size.width(), + size.height()); +} + + ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_image(new ScalableImage), + m_margins(3), m_border(1), m_padding(0), m_spacing(5), m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -22,10 +47,12 @@ ImagesTab::ImagesTab( ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + ui->imagesScrollerVBar->setTab(this); + ui->imagesScrollerVBar->setSingleStep(1); + connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); m_filter.setEdit(ui->imagesFilter); @@ -35,12 +62,16 @@ ImagesTab::ImagesTab( connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); + + ui->imagesThumbnails->setAutoFillBackground(false); + ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); } void ImagesTab::clear() { m_files.clear(); m_filteredFiles.clear(); + ui->imagesScrollerVBar->setValue(0); select(nullptr); setHasData(false); @@ -202,157 +233,98 @@ void ImagesTab::select(const File* f) m_selection = f; } -int ImagesTab::calcThumbSize(int availableWidth) const -{ - return availableWidth - (m_margins * 2); -} - -int ImagesTab::calcWidgetHeight(int availableWidth) const +ImagesGeometry ImagesTab::makeGeometry() const { - if (fileCount() == 0) { - return 0; - } - - const auto thumbSize = calcThumbSize(availableWidth); - - int h = 0; - - // first thumb - h = thumbSize; - - // subsequent thumbs with padding before each - const auto thumbWithPadding = m_padding + thumbSize; - h += static_cast(thumbWithPadding * (fileCount() - 1)); - - // margin top and bottom - h += m_margins * 2; - - return h; -} - -QRect ImagesTab::calcTopThumbRect(int thumbSize) const -{ - return {m_margins, m_margins, thumbSize, thumbSize}; + return ImagesGeometry( + ui->imagesThumbnails->size(), + m_margins, m_border, m_padding, m_spacing); } -std::pair ImagesTab::calcVisibleRange( - int top, int bottom, int thumbSize) const +void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const std::size_t begin = top / (thumbSize + m_padding); - const std::size_t end = bottom / (thumbSize + m_padding) + 1; + const auto geo = makeGeometry(); - return {begin, end}; -} + QPainter painter(ui->imagesThumbnails); -QRect ImagesTab::calcBorderRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return { - topRect.left(), - static_cast(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, - thumbSize - }; -} + painter.fillRect( + ui->imagesThumbnails->rect(), + ui->imagesThumbnails->palette().color(QPalette::Window)); -QRect ImagesTab::calcImageRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return calcBorderRect(topRect, thumbSize, i).adjusted( - m_border, m_border, -m_border, -m_border); -} + const auto visible = geo.fullyVisibleCount() + 1; + const auto first = ui->imagesScrollerVBar->value(); -QSize ImagesTab::calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(imageSize.width()) / originalSize.width(), - static_cast(imageSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - PaintContext cx(ui->imagesThumbnails); - cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); - cx.topRect = calcTopThumbRect(cx.thumbSize); - - const auto [begin, end] = calcVisibleRange( - e->rect().top(), e->rect().bottom(), cx.thumbSize); + for (std::size_t i=0; ifailed) { + if (file.failed) { return; } - const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i); - - if (needsReload(*file, imageRect.size())) { - reload(*file, imageRect.size()); + if (needsReload(geo, file)) { + reload(geo, file); } - if (file->thumbnail.isNull()) { + if (file.thumbnail.isNull()) { return; } - // center scaled image in rect - const QRect scaledThumbRect( - (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2, - (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2, - file->thumbnail.width(), - file->thumbnail.height()); + const auto imageRect = geo.imageRect(i); + const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - cx.painter.drawImage(scaledThumbRect, file->thumbnail); + painter.drawImage(scaledThumbRect, file.thumbnail); } const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const { - const auto thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + const auto geo = makeGeometry(); - // calculate index purely based on y position - const std::size_t i = p.y() / (thumbSize + m_padding); - if (i >= fileCount()) { + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { return nullptr; } - // get actual rect - const auto topRect = calcTopThumbRect(thumbSize); - const auto rect = calcBorderRect(topRect, thumbSize, i); + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return nullptr; + } - if (!rect.contains(p)) { + const auto i = static_cast(first) + offset; + if (i >= fileCount()) { return nullptr; } @@ -364,7 +336,7 @@ void ImagesTab::scrollAreaResized(const QSize&) resizeWidget(); } -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) { if (e->button() != Qt::LeftButton) { return; @@ -373,6 +345,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) +{ + const auto d = (e->angleDelta() / 8).y(); + + ui->imagesScrollerVBar->setValue( + ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); +} + +void ImagesTab::onScrolled() +{ + ui->imagesThumbnails->update(); +} + void ImagesTab::showTooltip(QHelpEvent* e) { const auto* f = fileAtPos(e->pos()); @@ -410,7 +395,7 @@ void ImagesTab::onFilterChanged() update(); } -bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const { if (file.failed) { return false; @@ -420,15 +405,11 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const return true; } - const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); - if (file.thumbnail.size() != scaledSize) { - return true; - } - - return false; + const auto scaledSize = geo.scaledImageSize(file.original.size()); + return (file.thumbnail.size() != scaledSize); } -void ImagesTab::reload(File& file, const QSize& scaledSize) +void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; @@ -441,56 +422,63 @@ void ImagesTab::reload(File& file, const QSize& scaledSize) } file.thumbnail = file.original.scaled( - calcScaledImageSize(file.original.size(), scaledSize), + geo.scaledImageSize(file.original.size()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } void ImagesTab::resizeWidget() { if (fileCount() == 0) { - ui->imagesThumbnails->setGeometry(QRect()); + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); return; } - const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + const auto geo = makeGeometry(); + const auto availableSize = ui->imagesThumbnails->size(); + const auto fullyVisible = geo.fullyVisibleCount(); - const int widgetHeight = std::max( - calcWidgetHeight(availableWidth), - ui->imagesScrollArea->viewport()->height()); - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); + if (fullyVisible >= fileCount()) { + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); + } else { + const auto d = fileCount() - fullyVisible; + ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setEnabled(true); + } } -void ImagesScrollArea::setTab(ImagesTab* tab) +void ImagesThumbnails::setTab(ImagesTab* tab) { m_tab = tab; } -void ImagesScrollArea::resizeEvent(QResizeEvent* e) +void ImagesThumbnails::paintEvent(QPaintEvent* e) { if (m_tab) { - m_tab->scrollAreaResized(e->size()); + m_tab->paintThumbnailsArea(e); } } - -void ImagesThumbnails::setTab(ImagesTab* tab) +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) { - m_tab = tab; + if (m_tab) { + m_tab->thumbnailAreaMouseEvent(e); + } } -void ImagesThumbnails::paintEvent(QPaintEvent* e) +void ImagesThumbnails::wheelEvent(QWheelEvent* e) { if (m_tab) { - m_tab->paintThumbnails(e); + m_tab->thumbnailAreaWheelEvent(e); } } -void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +void ImagesThumbnails::resizeEvent(QResizeEvent* e) { if (m_tab) { - m_tab->thumbnailsMouseEvent(e); + m_tab->scrollAreaResized(e->size()); } } @@ -505,6 +493,19 @@ bool ImagesThumbnails::event(QEvent* e) } +void ImagesScrollbar::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollbar::wheelEvent(QWheelEvent* e) +{ + if (m_tab) { + m_tab->thumbnailAreaWheelEvent(e); + } +} + + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { @@ -564,14 +565,8 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect imageRect = widgetRect.adjusted( m_border, m_border, -m_border, -m_border); - const auto ratio = std::min({ - 1.0, - static_cast(imageRect.width()) / m_original.width(), - static_cast(imageRect.height()) / m_original.height()}); - - const QSize scaledSize( - static_cast(std::round(m_original.width() * ratio)), - static_cast(std::round(m_original.height() * ratio))); + const QSize scaledSize = resizeWithAspectRatio( + m_original.size(), imageRect.size()); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { m_scaled = m_original.scaled( @@ -580,16 +575,98 @@ void ScalableImage::paintEvent(QPaintEvent* e) } const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); - - const QRect drawImageRect( - (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2, - (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2, - m_scaled.width(), m_scaled.height()); - + const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } + + +ImagesGeometry::ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing) : + m_widgetSize(widgetSize), + m_margins(margins), m_padding(padding), m_border(border), + m_spacing(spacing), m_topRect(calcTopRect()) +{ +} + +QRect ImagesGeometry::calcTopRect() const +{ + const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); + const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + + return {m_margins, m_margins, thumbWidth, thumbHeight}; +} + +std::size_t ImagesGeometry::fullyVisibleCount() const +{ + const auto r = thumbRect(0); + const auto visible = (m_widgetSize.height() / (r.height() + m_spacing)); + return static_cast(visible); +} + +QRect ImagesGeometry::thumbRect(std::size_t i) const +{ + // rect for the top thumbnail + QRect r = m_topRect; + + // move down + const auto thumbWithSpacing = m_spacing + r.height(); + r.translate(0, static_cast(i * thumbWithSpacing)); + + return r; +} + +QRect ImagesGeometry::borderRect(std::size_t i) const +{ + auto r = thumbRect(i); + + // border rect is currently the same as thumb rect, but may change if + // captions are added + + return r; +} + +QRect ImagesGeometry::imageRect(std::size_t i) const +{ + auto r = borderRect(i); + + // remove border and padding + const auto m = m_border + m_padding; + r.adjust(m, m, -m, -m); + + return r; +} + +std::size_t ImagesGeometry::indexAt(const QPoint& p) const +{ + // calculate index purely based on y position + const std::size_t offset = p.y() / (m_topRect.height() + m_spacing); + + if (!borderRect(offset).contains(p)) { + return BadIndex; + } + + return offset; +} + +QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const +{ + const auto availableSize = imageRect(0).size(); + return resizeWithAspectRatio(originalSize, availableSize); +} + +void ImagesGeometry::dump() const +{ + qDebug() + << "ImagesTab geometry:\n" + << " . widget size: " << m_widgetSize << "\n" + << " . margins: " << m_margins << "\n" + << " . border: " << m_border << "\n" + << " . padding: " << m_padding << "\n" + << " . spacing: " << m_spacing << "\n" + << " . top rect: " << m_topRect; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index fa7115b0..60ce9def 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -3,26 +3,32 @@ #include "modinfodialogtab.h" #include "filterwidget.h" -#include +#include class ImagesTab; -class ImagesScrollArea : public QScrollArea +// vertical scrollbar, this is only to handle wheel events to scroll by one +// instead of the system's scroll setting +// +class ImagesScrollbar : public QScrollBar { - Q_OBJECT; - public: - using QScrollArea::QScrollArea; + using QScrollBar::QScrollBar; void setTab(ImagesTab* tab); protected: - void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* event) override; private: ImagesTab* m_tab = nullptr; }; +// widget inside the scroller, calls ImagesTab::paintThumbnailArea() when +// needed and also forwards mouse clicks and tooltip events +// class ImagesThumbnails : public QWidget { Q_OBJECT; @@ -32,8 +38,24 @@ public: void setTab(ImagesTab* tab); protected: + // forwards to ImagesTab::paintThumbnailArea() + // void paintEvent(QPaintEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaMouseEvent() + // void mousePressEvent(QMouseEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* e); + + // forwards to ImagesTab::scrollAreaResized() + // + void resizeEvent(QResizeEvent* e) override; + + // forwards to ImagesTab::showTooltip for tooltip events + // bool event(QEvent* e) override; private: @@ -41,6 +63,8 @@ private: }; +// a widget that draws an image scaled to fit while keeping the aspect ratio +// class ScalableImage : public QWidget { Q_OBJECT; @@ -48,12 +72,16 @@ class ScalableImage : public QWidget public: ScalableImage(QString path={}); + // sets the image to draw void setImage(const QString& path); void setImage(QImage image); + + // removes the image, won't draw the border nor the image void clear(); - bool hasHeightForWidth() const; - int heightForWidth(int w) const; + // tells the QWidget's layout manager this widget is always square + bool hasHeightForWidth() const override; + int heightForWidth(int w) const override; protected: void paintEvent(QPaintEvent* e) override; @@ -65,10 +93,105 @@ private: }; +// handles all the geometry calculations by ImagesTab for painting or handling +// mouse clicks +// +// a thumbnail looks like this: +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// | +-border--------+ <------ border rect +// | | padding | | +// | | | | +// | | +-------+ <----------- image rect +// | | | | | | +// | | | image | | | +// | | | | | | +// | | +-------+ | | +// | | | | +// | +---------------+ | +// | | +// +-----------------------+ +// +// spacing +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// .... +// +// +class ImagesGeometry +{ +public: + // returned by indexAt() if the point is outside any possible thumbnail + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + + ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing); + + // returns the number of images fully visible in the widget + // + std::size_t fullyVisibleCount() const; + + // rectangle around the whole thumbnail + // + QRect thumbRect(std::size_t i) const; + + // rectangle of the border for the given thumbnail + // + QRect borderRect(std::size_t i) const; + + // rectangle of the image for the given thumbnail + // + QRect imageRect(std::size_t i) const; + + // returns the index of the image at the given point; this does not take into + // account any scrolling, the image at the top of widget is always 0 + // + std::size_t indexAt(const QPoint& p) const; + + // returns the size of the image that fits in imageRect() while keeping the + // same aspect ratio as the given one + // + QSize scaledImageSize(const QSize& originalSize) const; + + // dumps stuff to qDebug() + // + void dump() const; + +private: + // size of the widget containing all the thumbnails + const QSize m_widgetSize; + + // space outside the thumbnail border + const int m_margins; + + // size of the border + const int m_border; + + // space between the border and the image + const int m_padding; + + // spacing between thumbnails + const int m_spacing; + + // rectangle of the first thumbnail on top + const QRect m_topRect; + + + // calculates the top rectangle + // + QRect calcTopRect() const; +}; + + class ImagesTab : public ModInfoDialogTab { Q_OBJECT; - friend class ImagesScrollArea; + friend class ImagesScrollbar; friend class ImagesThumbnails; public: @@ -95,23 +218,11 @@ private: } }; - struct PaintContext - { - QPainter painter; - int thumbSize; - QRect topRect; - - PaintContext(QWidget* w) - : painter(w), thumbSize(0) - { - } - }; - ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; std::vector m_supportedFormats; - int m_margins, m_padding, m_border; + int m_margins, m_border, m_padding, m_spacing; const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; @@ -122,27 +233,29 @@ private: bool needsFiltering() const; void scrollAreaResized(const QSize& s); - void paintThumbnails(QPaintEvent* e); - void thumbnailsMouseEvent(QMouseEvent* e); + void paintThumbnailsArea(QPaintEvent* e); + void thumbnailAreaMouseEvent(QMouseEvent* e); + void thumbnailAreaWheelEvent(QWheelEvent* e); + void onScrolled(); + void showTooltip(QHelpEvent* e); void onExplore(); void onShowDDS(); void onFilterChanged(); - int calcThumbSize(int availableWidth) const; - int calcWidgetHeight(int availableWidth) const; - QRect calcTopThumbRect(int thumbSize) const; - std::pair calcVisibleRange( - int top, int bottom, int thumbSize) const; + ImagesGeometry makeGeometry() const; + + void paintThumbnail( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); - QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QSize calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const; + void paintThumbnailBorder( + QPainter& painter, const ImagesGeometry& geo, + std::size_t i); - void paintThumbnail(PaintContext& cx, std::size_t i); - void paintThumbnailBorder(PaintContext& cx, std::size_t i); - void paintThumbnailImage(PaintContext& cx, std::size_t i); + void paintThumbnailImage( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); const File* fileAtPos(const QPoint& p) const; @@ -151,8 +264,8 @@ private: File* getFile(std::size_t i); void filterImages(); - bool needsReload(const File& file, const QSize& imageSize) const; - void reload(File& file, const QSize& imageSize); + bool needsReload(const ImagesGeometry& geo, const File& file) const; + void reload(const ImagesGeometry& geo, File& file); void resizeWidget(); }; -- cgit v1.3.1 From 48607afc65e2f06bbc8b4b05386c6de89a33b7bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 07:27:41 -0400 Subject: changed thumbnails to use margin around border selected thumbnail now has a background color --- src/modinfodialogimages.cpp | 63 +++++++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 16 +++++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5c93e3d4..7e021f97 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -65,6 +65,17 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setAutoFillBackground(false); ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); + + { + auto list = std::make_unique(); + parentWidget()->style()->polish(list.get()); + + m_colors.border = QColor(Qt::black); + m_colors.background = QColor(Qt::black); + m_colors.selection = list->palette().color(QPalette::Highlight); + + m_image->setColors(m_colors.border, m_colors.background); + } } void ImagesTab::clear() @@ -92,7 +103,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { filterImages(); - resizeWidget(); + updateScrollbar(); ui->imagesThumbnails->update(); setHasData(fileCount() > 0); @@ -231,6 +242,7 @@ void ImagesTab::select(const File* f) } m_selection = f; + ui->imagesThumbnails->update(); } ImagesGeometry ImagesTab::makeGeometry() const @@ -268,10 +280,21 @@ void ImagesTab::paintThumbnail( QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i) { + paintThumbnailBackground(painter, geo, file, i); paintThumbnailBorder(painter, geo, i); paintThumbnailImage(painter, geo, file, i); } +void ImagesTab::paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i) +{ + if (&file == m_selection) { + const auto rect = geo.thumbRect(i); + painter.fillRect(rect, m_colors.selection); + } +} + void ImagesTab::paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i) { @@ -282,7 +305,7 @@ void ImagesTab::paintThumbnailBorder( borderRect.setRight(borderRect.right() - 1); borderRect.setBottom(borderRect.bottom() - 1); - painter.setPen(QColor(Qt::black)); + painter.setPen(m_colors.border); painter.drawRect(borderRect); } @@ -305,6 +328,7 @@ void ImagesTab::paintThumbnailImage( const auto imageRect = geo.imageRect(i); const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); + painter.fillRect(scaledThumbRect, m_colors.background); painter.drawImage(scaledThumbRect, file.thumbnail); } @@ -333,7 +357,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const void ImagesTab::scrollAreaResized(const QSize&) { - resizeWidget(); + updateScrollbar(); } void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) @@ -426,7 +450,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } -void ImagesTab::resizeWidget() +void ImagesTab::updateScrollbar() { if (fileCount() == 0) { ui->imagesScrollerVBar->setRange(0, 0); @@ -547,6 +571,12 @@ int ScalableImage::heightForWidth(int w) const return w; } +void ScalableImage::setColors(const QColor& border, const QColor& background) +{ + m_borderColor = border; + m_backgroundColor = background; +} + void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { @@ -578,8 +608,15 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); + + // background + painter.fillRect(drawBorderRect, m_backgroundColor); + + // border + painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + + // image painter.drawImage(drawImageRect, m_scaled); } @@ -594,11 +631,14 @@ ImagesGeometry::ImagesGeometry( QRect ImagesGeometry::calcTopRect() const { - const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); - const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); - const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + const auto thumbWidth = m_widgetSize.width(); + const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = + m_margins + m_border + m_padding + + imageSize + + m_border + m_padding + m_margins; - return {m_margins, m_margins, thumbWidth, thumbHeight}; + return {0, 0, thumbWidth, thumbHeight}; } std::size_t ImagesGeometry::fullyVisibleCount() const @@ -624,8 +664,9 @@ QRect ImagesGeometry::borderRect(std::size_t i) const { auto r = thumbRect(i); - // border rect is currently the same as thumb rect, but may change if - // captions are added + // remove margins + const auto m = m_margins; + r.adjust(m, m, -m, -m); return r; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 60ce9def..db541ed8 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -83,6 +83,9 @@ public: bool hasHeightForWidth() const override; int heightForWidth(int w) const override; + // sets the colors + void setColors(const QColor& border, const QColor& background); + protected: void paintEvent(QPaintEvent* e) override; @@ -90,6 +93,7 @@ private: QString m_path; QImage m_original, m_scaled; int m_border; + QColor m_borderColor, m_backgroundColor; }; @@ -218,6 +222,11 @@ private: } }; + struct Colors + { + QColor border, background, selection; + }; + ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; @@ -226,6 +235,7 @@ private: const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; + Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); @@ -249,6 +259,10 @@ private: QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i); + void paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); + void paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i); @@ -266,7 +280,7 @@ private: void filterImages(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); - void resizeWidget(); + void updateScrollbar(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From c2df11298b3be4919a660206e5100ee6dbdecd1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 08:43:18 -0400 Subject: refactored stuff into PaintContext and Files keep selection when switching back to no filtering up/down keyboard navigation --- src/modinfodialog.ui | 6 +- src/modinfodialogimages.cpp | 445 ++++++++++++++++++++++++++++++-------------- src/modinfodialogimages.h | 98 +++++++--- 3 files changed, 387 insertions(+), 162 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a9af2593..e745967d 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -207,7 +207,11 @@ 0 - + + + Qt::StrongFocus + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7e021f97..4179c62a 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -33,7 +33,7 @@ ImagesTab::ImagesTab( ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), m_margins(3), m_border(1), m_padding(0), m_spacing(5), - m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -80,11 +80,8 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { - m_files.clear(); - m_filteredFiles.clear(); ui->imagesScrollerVBar->setValue(0); - - select(nullptr); + select(BadIndex); setHasData(false); } @@ -92,7 +89,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { for (const auto& ext : m_supportedFormats) { if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); + m_files.add({fullPath}); return true; } } @@ -102,11 +99,11 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { - filterImages(); + checkFiltering(); updateScrollbar(); ui->imagesThumbnails->update(); - setHasData(fileCount() > 0); + setHasData(m_files.size() > 0); } void ImagesTab::saveState(Settings& s) @@ -121,88 +118,79 @@ void ImagesTab::restoreState(const Settings& s) .value("mod_info_dialog_images_show_dds", false).toBool()); } -void ImagesTab::filterImages() +void ImagesTab::checkFiltering() { - if (!needsFiltering()) { - return; + if (m_filter.empty() && m_ddsEnabled) { + // no filtering needed + + if (m_files.isFiltered()) { + // was filtered, needs switch + switchToAll(); + } + } else { + // filtering is needed + switchToFiltered(); } +} - m_filteredFiles.clear(); +void ImagesTab::switchToAll() +{ + // remember selection + const auto* oldSelection = m_files.selectedFile(); + + // switch + m_files.switchToAll(); + + // reselect old + if (oldSelection) { + m_files.select(m_files.indexOf(oldSelection)); + } else { + m_files.select(BadIndex); + } +} + +void ImagesTab::switchToFiltered() +{ + // remember old selection, will be checked when building the filtered list + // below + const auto* oldSelection = m_files.selectedFile(); + std::size_t newSelection = BadIndex; + + // switch, also clears list + m_files.switchToFiltered(); - bool hasTextFilter = !m_filter.empty(); - bool sawSelection = false; + const bool hasTextFilter = !m_filter.empty(); - for (auto& f : m_files) { + for (auto& f : m_files.allFiles()) { if (hasTextFilter) { + // check filter widget const auto m = m_filter.matches([&](auto&& what) { return f.path.contains(what, Qt::CaseInsensitive); - }); + }); if (!m) { + // no match, skip continue; } } if (!m_ddsEnabled) { + // skip .dds files if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { continue; } } - if (&f == m_selection) { - sawSelection = true; - } - - m_filteredFiles.push_back(&f); - } - - if (!sawSelection) { - select(nullptr); - } -} - -std::size_t ImagesTab::fileCount() const -{ - if (needsFiltering()) { - return m_filteredFiles.size(); - } else { - return m_files.size(); - } -} - -const ImagesTab::File* ImagesTab::getFile(std::size_t i) const -{ - if (needsFiltering()) { - if (i >= m_filteredFiles.size()) { - return nullptr; - } - - return m_filteredFiles[i]; - } else { - if (i >= m_files.size()) { - return nullptr; + if (&f == oldSelection) { + // found the old selection, remember its index + newSelection = m_files.size(); } - return &m_files[i]; - } -} - -ImagesTab::File* ImagesTab::getFile(std::size_t i) -{ - return const_cast(std::as_const(*this).getFile(i)); -} - -bool ImagesTab::needsFiltering() const -{ - if (!m_filter.empty()) { - return true; + m_files.addFiltered(&f); } - if (!m_ddsEnabled) { - return true; - } - - return false; + // reselect old, or clear if it wasn't found + select(newSelection); } void ImagesTab::getSupportedFormats() @@ -229,22 +217,101 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(const File* f) +void ImagesTab::select(std::size_t i) { - if (f) { + m_files.select(i); + + if (const auto* f=m_files.selectedFile()) { ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); + ensureVisible(i); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); m_image->clear(); } - m_selection = f; ui->imagesThumbnails->update(); } +void ImagesTab::moveSelection(int direction) +{ + const auto i = m_files.selectedIndex(); + + if (i == BadIndex) { + if (m_files.size() > 0) { + select(0); + } + + return; + } + + if (direction > 0) { + // moving down + if (i < (m_files.size() - 1)) { + select(i + 1); + } + } else { + // moving up + if (i > 0) { + select(i - 1); + } + } +} + +void ImagesTab::ensureVisible(std::size_t i) +{ + const auto geo = makeGeometry(); + + const auto fullyVisible = geo.fullyVisibleCount(); + const auto first = ui->imagesScrollerVBar->value(); + + if (i < first) { + // go up + ui->imagesScrollerVBar->setValue(static_cast(i)); + } else if (i >= first + fullyVisible) { + // go down + + if (i >= fullyVisible) { + ui->imagesScrollerVBar->setValue(static_cast(i - fullyVisible + 1)); + } + } +} + +std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const +{ + const auto geo = makeGeometry(); + + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { + return BadIndex; + } + + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return BadIndex; + } + + const auto i = static_cast(first) + offset; + if (i >= m_files.size()) { + return BadIndex; + } + + return i; +} + +const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const +{ + const auto i = fileIndexAtPos(p); + if (i >= m_files.size()) { + return nullptr; + } + + return m_files.get(i); +} + ImagesGeometry ImagesTab::makeGeometry() const { return ImagesGeometry( @@ -254,105 +321,78 @@ ImagesGeometry ImagesTab::makeGeometry() const void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const auto geo = makeGeometry(); - - QPainter painter(ui->imagesThumbnails); + PaintContext cx(ui->imagesThumbnails, makeGeometry()); - painter.fillRect( + cx.painter.fillRect( ui->imagesThumbnails->rect(), ui->imagesThumbnails->palette().color(QPalette::Window)); - const auto visible = geo.fullyVisibleCount() + 1; + const auto visible = cx.geo.fullyVisibleCount() + 1; const auto first = ui->imagesScrollerVBar->value(); for (std::size_t i=0; ifailed) { return; } - if (needsReload(geo, file)) { - reload(geo, file); + if (needsReload(cx.geo, *cx.file)) { + reload(cx.geo, *cx.file); } - if (file.thumbnail.isNull()) { + if (cx.file->thumbnail.isNull()) { return; } - const auto imageRect = geo.imageRect(i); - const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - - painter.fillRect(scaledThumbRect, m_colors.background); - painter.drawImage(scaledThumbRect, file.thumbnail); -} - -const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const -{ - const auto geo = makeGeometry(); - - // this is the index relative to the top - const auto offset = geo.indexAt(p); - if (offset == ImagesGeometry::BadIndex) { - return nullptr; - } - - const auto first = ui->imagesScrollerVBar->value(); - if (first < 0) { - return nullptr; - } - - const auto i = static_cast(first) + offset; - if (i >= fileCount()) { - return nullptr; - } + const auto imageRect = cx.geo.imageRect(cx.thumbIndex); + const auto scaledThumbRect = centeredRect( + imageRect, cx.file->thumbnail.size()); - return getFile(i); + cx.painter.fillRect(scaledThumbRect, m_colors.background); + cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail); } void ImagesTab::scrollAreaResized(const QSize&) @@ -366,7 +406,7 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileAtPos(e->pos())); + select(fileIndexAtPos(e->pos())); } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -377,6 +417,19 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); } +bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Down) { + moveSelection(+1); + return true; + } else if (e->key() == Qt::Key_Up) { + moveSelection(-1); + return true; + } + + return false; +} + void ImagesTab::onScrolled() { ui->imagesThumbnails->update(); @@ -398,11 +451,9 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { - if (!m_selection) { - return; + if (auto* f=m_files.selectedFile()) { + MOBase::shell::ExploreFile(f->path); } - - MOBase::shell::ExploreFile(m_selection->path); } void ImagesTab::onShowDDS() @@ -452,7 +503,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) void ImagesTab::updateScrollbar() { - if (fileCount() == 0) { + if (m_files.size() == 0) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); return; @@ -462,11 +513,11 @@ void ImagesTab::updateScrollbar() const auto availableSize = ui->imagesThumbnails->size(); const auto fullyVisible = geo.fullyVisibleCount(); - if (fullyVisible >= fileCount()) { + if (fullyVisible >= m_files.size()) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); } else { - const auto d = fileCount() - fullyVisible; + const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setEnabled(true); } @@ -506,6 +557,17 @@ void ImagesThumbnails::resizeEvent(QResizeEvent* e) } } +void ImagesThumbnails::keyPressEvent(QKeyEvent* e) +{ + if (m_tab) { + if (m_tab->thumbnailAreaKeyPressEvent(e)) { + return; + } + } + + QWidget::keyPressEvent(e); +} + bool ImagesThumbnails::event(QEvent* e) { if (e->type() == QEvent::ToolTip) { @@ -711,3 +773,116 @@ void ImagesGeometry::dump() const << " . spacing: " << m_spacing << "\n" << " . top rect: " << m_topRect; } + + +ImagesTab::Files::Files() + : m_selection(BadIndex), m_filtered(false) +{ +} + +void ImagesTab::Files::clear() +{ + m_allFiles.clear(); + m_filteredFiles.clear(); + m_filtered = false; +} + +void ImagesTab::Files::add(File f) +{ + m_allFiles.emplace_back(std::move(f)); +} + +void ImagesTab::Files::addFiltered(File* f) +{ + m_filteredFiles.push_back(f); +} + +std::size_t ImagesTab::Files::size() const +{ + if (m_filtered) { + return m_filteredFiles.size(); + } else { + return m_allFiles.size(); + } +} + +void ImagesTab::Files::switchToAll() +{ + m_filtered = false; + m_filteredFiles.clear(); +} + +void ImagesTab::Files::switchToFiltered() +{ + m_filtered = true; + m_filteredFiles.clear(); +} + +const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const +{ + if (m_filtered) { + if (i < m_filteredFiles.size()) { + return m_filteredFiles[i]; + } + } else { + if (i < m_allFiles.size()) { + return &m_allFiles[i]; + } + } + + return nullptr; +} + +ImagesTab::File* ImagesTab::Files::get(std::size_t i) +{ + return const_cast(std::as_const(*this).get(i)); +} + +std::size_t ImagesTab::Files::indexOf(const File* f) const +{ + if (m_filtered) { + for (std::size_t i=0; i& ImagesTab::Files::allFiles() +{ + return m_allFiles; +} + +bool ImagesTab::Files::isFiltered() const +{ + return m_filtered; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index db541ed8..c7f46975 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -54,6 +54,10 @@ protected: // void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaKeyPressEvent() + // + void keyPressEvent(QKeyEvent* e) override; + // forwards to ImagesTab::showTooltip for tooltip events // bool event(QEvent* e) override; @@ -210,6 +214,9 @@ public: void restoreState(const Settings& s) override; private: + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + struct File { QString path; @@ -227,25 +234,72 @@ private: QColor border, background, selection; }; + struct PaintContext + { + mutable QPainter painter; + ImagesGeometry geo; + File* file; + std::size_t thumbIndex; + std::size_t fileIndex; + + PaintContext(QWidget* w, ImagesGeometry geo) + : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) + { + } + }; + + class Files + { + public: + Files(); + + void clear(); + + void add(File f); + void addFiltered(File* f); + + std::size_t size() const; + + void switchToAll(); + void switchToFiltered(); + + const File* get(std::size_t i) const; + File* get(std::size_t i); + std::size_t indexOf(const File* f) const; + + const File* selectedFile() const; + File* selectedFile(); + std::size_t selectedIndex() const; + void select(std::size_t i); + + std::vector& allFiles(); + + bool isFiltered() const; + + private: + std::vector m_allFiles; + std::vector m_filteredFiles; + std::size_t m_selection; + bool m_filtered; + }; + + ScalableImage* m_image; - std::vector m_files; - std::vector m_filteredFiles; std::vector m_supportedFormats; + Files m_files; int m_margins, m_border, m_padding, m_spacing; - const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); - void select(const File* file); - bool needsFiltering() const; void scrollAreaResized(const QSize& s); void paintThumbnailsArea(QPaintEvent* e); void thumbnailAreaMouseEvent(QMouseEvent* e); void thumbnailAreaWheelEvent(QWheelEvent* e); + bool thumbnailAreaKeyPressEvent(QKeyEvent* e); void onScrolled(); void showTooltip(QHelpEvent* e); @@ -253,31 +307,23 @@ private: void onShowDDS(); void onFilterChanged(); - ImagesGeometry makeGeometry() const; - - void paintThumbnail( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBackground( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBorder( - QPainter& painter, const ImagesGeometry& geo, - std::size_t i); - - void paintThumbnailImage( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); + void select(std::size_t i); + void moveSelection(int direction); + void ensureVisible(std::size_t i); + std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; - std::size_t fileCount() const; - const File* getFile(std::size_t i) const; - File* getFile(std::size_t i); + ImagesGeometry makeGeometry() const; + + void paintThumbnail(const PaintContext& cx); + void paintThumbnailBackground(const PaintContext& cx); + void paintThumbnailBorder(const PaintContext& cx); + void paintThumbnailImage(const PaintContext& cx); - void filterImages(); + void checkFiltering(); + void switchToAll(); + void switchToFiltered(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); void updateScrollbar(); -- cgit v1.3.1 From 933a2c7d1086ebc87ee2d214deaa3752ea8c6cf1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 09:06:41 -0400 Subject: added keyboard navigation for page down/up, end/home changed to draw the full image background only behind the image --- src/modinfodialogimages.cpp | 113 ++++++++++++++++++++++++++++++++------------ src/modinfodialogimages.h | 13 ++++- 2 files changed, 94 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4179c62a..7738d55f 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -50,7 +50,6 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setTab(this); ui->imagesScrollerVBar->setTab(this); - ui->imagesScrollerVBar->setSingleStep(1); connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); @@ -80,6 +79,7 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { + m_files.clear(); ui->imagesScrollerVBar->setValue(0); select(BadIndex); setHasData(false); @@ -221,7 +221,12 @@ void ImagesTab::select(std::size_t i) { m_files.select(i); - if (const auto* f=m_files.selectedFile()) { + if (auto* f=m_files.selectedFile()) { + // when jumping elsewhere in the list, such as with page down/up, the file + // might not be visible yet, which means it hasn't been loaded and would + // pass a null image in setImage() below + f->ensureOriginalLoaded(); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); @@ -235,29 +240,36 @@ void ImagesTab::select(std::size_t i) ui->imagesThumbnails->update(); } -void ImagesTab::moveSelection(int direction) +void ImagesTab::moveSelection(int by) { - const auto i = m_files.selectedIndex(); + if (m_files.empty()) { + return; + } + auto i = m_files.selectedIndex(); if (i == BadIndex) { - if (m_files.size() > 0) { - select(0); - } - - return; + i = 0; } - if (direction > 0) { + if (by > 0) { // moving down - if (i < (m_files.size() - 1)) { - select(i + 1); + i += static_cast(by); + + if (i >= m_files.size()) { + i = (m_files.size() - 1); } - } else { + } else if (by < 0) { // moving up - if (i > 0) { - select(i - 1); + const auto abs_by = static_cast(std::abs(by)); + + if (abs_by > i) { + i = 0; + } else { + i -= abs_by; } } + + select(i); } void ImagesTab::ensureVisible(std::size_t i) @@ -406,7 +418,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileIndexAtPos(e->pos())); + const auto i = fileIndexAtPos(e->pos()); + if (i != BadIndex) { + select(i); + } } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -419,12 +434,39 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) { - if (e->key() == Qt::Key_Down) { - moveSelection(+1); - return true; - } else if (e->key() == Qt::Key_Up) { - moveSelection(-1); - return true; + switch (e->key()) { + case Qt::Key_Down: { + moveSelection(ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_Up: { + moveSelection(-ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_PageDown: { + moveSelection(ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_PageUp: { + moveSelection(-ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_Home: { + select(0); + return true; + } + + case Qt::Key_End: { + if (!m_files.empty()) { + select(m_files.size() - 1); + } + + return true; + } } return false; @@ -487,13 +529,10 @@ bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; + file.ensureOriginalLoaded(); - if (file.original.isNull()) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; - } + if (file.failed) { + return; } file.thumbnail = file.original.scaled( @@ -519,6 +558,8 @@ void ImagesTab::updateScrollbar() } else { const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setSingleStep(1); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); ui->imagesScrollerVBar->setEnabled(true); } } @@ -671,13 +712,13 @@ void ScalableImage::paintEvent(QPaintEvent* e) QPainter painter(this); - // background - painter.fillRect(drawBorderRect, m_backgroundColor); - // border painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + // background + painter.fillRect(drawImageRect, m_backgroundColor); + // image painter.drawImage(drawImageRect, m_scaled); } @@ -784,6 +825,7 @@ void ImagesTab::Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); + m_selection = BadIndex; m_filtered = false; } @@ -797,6 +839,15 @@ void ImagesTab::Files::addFiltered(File* f) m_filteredFiles.push_back(f); } +bool ImagesTab::Files::empty() const +{ + if (m_filtered) { + return m_filteredFiles.empty(); + } else { + return m_allFiles.empty(); + } +} + std::size_t ImagesTab::Files::size() const { if (m_filtered) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index c7f46975..5ce66d5d 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -227,6 +227,16 @@ private: : path(std::move(path)) { } + + void ensureOriginalLoaded() + { + if (original.isNull()) { + if (!original.load(path)) { + qCritical() << "failed to load image from " << path; + failed = true; + } + } + } }; struct Colors @@ -258,6 +268,7 @@ private: void add(File f); void addFiltered(File* f); + bool empty() const; std::size_t size() const; void switchToAll(); @@ -308,7 +319,7 @@ private: void onFilterChanged(); void select(std::size_t i); - void moveSelection(int direction); + void moveSelection(int by); void ensureVisible(std::size_t i); std::size_t fileIndexAtPos(const QPoint& p) const; -- cgit v1.3.1 From 1c0024b2feda8b5c152d2e211205601ee807e517 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:27:02 -0400 Subject: moved all images classes into a namespace with shorter names split theme and metrics into their own class added filename to thumbs --- src/modinfodialog.ui | 8 +- src/modinfodialogimages.cpp | 379 +++++++++++++++++++++++++++++--------------- src/modinfodialogimages.h | 263 +++++++++++++++++------------- 3 files changed, 405 insertions(+), 245 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e745967d..3da13261 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -207,14 +207,14 @@ 0 - + Qt::StrongFocus - + Qt::Vertical @@ -1239,13 +1239,13 @@ p, li { white-space: pre-wrap; }
texteditor.h
- ImagesThumbnails + ImagesTabHelpers::ThumbnailsWidget QWidget
modinfodialogimages.h
1
- ImagesScrollbar + ImagesTabHelpers::Scrollbar QScrollBar
modinfodialogimages.h
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7738d55f..8c323ac6 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -3,6 +3,8 @@ #include "settings.h" #include "utility.h" +using namespace ImagesTabHelpers; + QSize resizeWithAspectRatio(const QSize& original, const QSize& available) { const auto ratio = std::min({ @@ -32,7 +34,6 @@ ImagesTab::ImagesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), - m_margins(3), m_border(1), m_padding(0), m_spacing(5), m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -69,11 +70,19 @@ ImagesTab::ImagesTab( auto list = std::make_unique(); parentWidget()->style()->polish(list.get()); - m_colors.border = QColor(Qt::black); - m_colors.background = QColor(Qt::black); - m_colors.selection = list->palette().color(QPalette::Highlight); + m_theme.borderColor = QColor(Qt::black); + m_theme.backgroundColor = QColor(Qt::black); + m_theme.textColor = list->palette().color(QPalette::WindowText); + + m_theme.highlightBackgroundColor = list->palette().color(QPalette::Highlight); + m_theme.highlightTextColor = list->palette().color(QPalette::HighlightedText); + + m_theme.font = list->font(); - m_image->setColors(m_colors.border, m_colors.background); + const QFontMetrics fm(m_theme.font); + m_metrics.textHeight = fm.height(); + + m_image->setColors(m_theme.borderColor, m_theme.backgroundColor); } } @@ -165,8 +174,8 @@ void ImagesTab::switchToFiltered() if (hasTextFilter) { // check filter widget const auto m = m_filter.matches([&](auto&& what) { - return f.path.contains(what, Qt::CaseInsensitive); - }); + return f.path().contains(what, Qt::CaseInsensitive); + }); if (!m) { // no match, skip @@ -176,7 +185,7 @@ void ImagesTab::switchToFiltered() if (!m_ddsEnabled) { // skip .dds files - if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { + if (f.path().endsWith(".dds", Qt::CaseInsensitive)) { continue; } } @@ -227,9 +236,9 @@ void ImagesTab::select(std::size_t i) // pass a null image in setImage() below f->ensureOriginalLoaded(); - ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); - m_image->setImage(f->original); + m_image->setImage(f->original()); ensureVisible(i); } else { ui->imagesPath->clear(); @@ -297,7 +306,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const // this is the index relative to the top const auto offset = geo.indexAt(p); - if (offset == ImagesGeometry::BadIndex) { + if (offset == BadIndex) { return BadIndex; } @@ -314,7 +323,7 @@ std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const return i; } -const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const +const File* ImagesTab::fileAtPos(const QPoint& p) const { const auto i = fileIndexAtPos(p); if (i >= m_files.size()) { @@ -324,11 +333,9 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const return m_files.get(i); } -ImagesGeometry ImagesTab::makeGeometry() const +Geometry ImagesTab::makeGeometry() const { - return ImagesGeometry( - ui->imagesThumbnails->size(), - m_margins, m_border, m_padding, m_spacing); + return Geometry(ui->imagesThumbnails->size(), m_metrics); } void ImagesTab::paintThumbnailsArea(QPaintEvent* e) @@ -362,13 +369,14 @@ void ImagesTab::paintThumbnail(const PaintContext& cx) paintThumbnailBackground(cx); paintThumbnailBorder(cx); paintThumbnailImage(cx); + paintThumbnailText(cx); } void ImagesTab::paintThumbnailBackground(const PaintContext& cx) { if (m_files.selectedIndex() == cx.fileIndex) { const auto rect = cx.geo.thumbRect(cx.thumbIndex); - cx.painter.fillRect(rect, m_colors.selection); + cx.painter.fillRect(rect, m_theme.highlightBackgroundColor); } } @@ -381,30 +389,46 @@ void ImagesTab::paintThumbnailBorder(const PaintContext& cx) borderRect.setRight(borderRect.right() - 1); borderRect.setBottom(borderRect.bottom() - 1); - cx.painter.setPen(m_colors.border); + cx.painter.setPen(m_theme.borderColor); cx.painter.drawRect(borderRect); } void ImagesTab::paintThumbnailImage(const PaintContext& cx) { - if (cx.file->failed) { + if (cx.file->failed()) { return; } - if (needsReload(cx.geo, *cx.file)) { - reload(cx.geo, *cx.file); - } - - if (cx.file->thumbnail.isNull()) { - return; - } + cx.file->loadIfNeeded(cx.geo); const auto imageRect = cx.geo.imageRect(cx.thumbIndex); const auto scaledThumbRect = centeredRect( - imageRect, cx.file->thumbnail.size()); + imageRect, cx.file->thumbnail().size()); - cx.painter.fillRect(scaledThumbRect, m_colors.background); - cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail); + cx.painter.fillRect(scaledThumbRect, m_theme.backgroundColor); + cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail()); +} + +void ImagesTab::paintThumbnailText(const PaintContext& cx) +{ + const auto tr = cx.geo.textRect(cx.thumbIndex); + + if (cx.fileIndex == m_files.selectedIndex()) { + cx.painter.setPen(m_theme.highlightTextColor); + } else { + cx.painter.setPen(m_theme.textColor); + } + + cx.painter.setFont(m_theme.font); + + QFontMetrics fm(m_theme.font); + + const auto text = fm.elidedText( + cx.file->filename(), Qt::ElideRight, tr.width()); + + const auto flags = Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine; + + cx.painter.drawText(tr, flags, text); } void ImagesTab::scrollAreaResized(const QSize&) @@ -487,14 +511,14 @@ void ImagesTab::showTooltip(QHelpEvent* e) } QToolTip::showText( - e->globalPos(), QDir::toNativeSeparators(f->path), + e->globalPos(), QDir::toNativeSeparators(f->path()), ui->imagesThumbnails); } void ImagesTab::onExplore() { if (auto* f=m_files.selectedFile()) { - MOBase::shell::ExploreFile(f->path); + MOBase::shell::ExploreFile(f->path()); } } @@ -512,34 +536,6 @@ void ImagesTab::onFilterChanged() update(); } -bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const -{ - if (file.failed) { - return false; - } - - if (file.original.isNull() || file.thumbnail.isNull()) { - return true; - } - - const auto scaledSize = geo.scaledImageSize(file.original.size()); - return (file.thumbnail.size() != scaledSize); -} - -void ImagesTab::reload(const ImagesGeometry& geo, File& file) -{ - file.failed = false; - file.ensureOriginalLoaded(); - - if (file.failed) { - return; - } - - file.thumbnail = file.original.scaled( - geo.scaledImageSize(file.original.size()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); -} - void ImagesTab::updateScrollbar() { if (m_files.size() == 0) { @@ -565,40 +561,56 @@ void ImagesTab::updateScrollbar() } -void ImagesThumbnails::setTab(ImagesTab* tab) +namespace ImagesTabHelpers +{ + +void Scrollbar::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void Scrollbar::wheelEvent(QWheelEvent* e) +{ + if (m_tab) { + m_tab->thumbnailAreaWheelEvent(e); + } +} + + +void ThumbnailsWidget::setTab(ImagesTab* tab) { m_tab = tab; } -void ImagesThumbnails::paintEvent(QPaintEvent* e) +void ThumbnailsWidget::paintEvent(QPaintEvent* e) { if (m_tab) { m_tab->paintThumbnailsArea(e); } } -void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +void ThumbnailsWidget::mousePressEvent(QMouseEvent* e) { if (m_tab) { m_tab->thumbnailAreaMouseEvent(e); } } -void ImagesThumbnails::wheelEvent(QWheelEvent* e) +void ThumbnailsWidget::wheelEvent(QWheelEvent* e) { if (m_tab) { m_tab->thumbnailAreaWheelEvent(e); } } -void ImagesThumbnails::resizeEvent(QResizeEvent* e) +void ThumbnailsWidget::resizeEvent(QResizeEvent* e) { if (m_tab) { m_tab->scrollAreaResized(e->size()); } } -void ImagesThumbnails::keyPressEvent(QKeyEvent* e) +void ThumbnailsWidget::keyPressEvent(QKeyEvent* e) { if (m_tab) { if (m_tab->thumbnailAreaKeyPressEvent(e)) { @@ -609,7 +621,7 @@ void ImagesThumbnails::keyPressEvent(QKeyEvent* e) QWidget::keyPressEvent(e); } -bool ImagesThumbnails::event(QEvent* e) +bool ThumbnailsWidget::event(QEvent* e) { if (e->type() == QEvent::ToolTip) { m_tab->showTooltip(static_cast(e)); @@ -620,19 +632,6 @@ bool ImagesThumbnails::event(QEvent* e) } -void ImagesScrollbar::setTab(ImagesTab* tab) -{ - m_tab = tab; -} - -void ImagesScrollbar::wheelEvent(QWheelEvent* e) -{ - if (m_tab) { - m_tab->thumbnailAreaWheelEvent(e); - } -} - - ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { @@ -724,104 +723,220 @@ void ScalableImage::paintEvent(QPaintEvent* e) } -ImagesGeometry::ImagesGeometry( - const QSize& widgetSize, int margins, int border, int padding, int spacing) : - m_widgetSize(widgetSize), - m_margins(margins), m_padding(padding), m_border(border), - m_spacing(spacing), m_topRect(calcTopRect()) +Metrics::Metrics() : + margins(3), + border(1), + padding(0), + spacing(5), + textSpacing(2), + textHeight(0) +{ +} + + +Geometry::Geometry(QSize widgetSize, Metrics metrics) + : m_widgetSize(widgetSize), m_metrics(metrics), m_topRect(calcTopRect()) { } -QRect ImagesGeometry::calcTopRect() const +QRect Geometry::calcTopRect() const { const auto thumbWidth = m_widgetSize.width(); - const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2); + const auto& m = m_metrics; + + const auto imageSize = + thumbWidth - + (m.margins * 2) - + (m.border * 2) - + (m.padding * 2); + const auto thumbHeight = - m_margins + m_border + m_padding + + m.margins + + m.border + m.padding + imageSize + - m_border + m_padding + m_margins; + m.padding + m.border + + m.textSpacing + m.textHeight + + m.margins; return {0, 0, thumbWidth, thumbHeight}; } -std::size_t ImagesGeometry::fullyVisibleCount() const +std::size_t Geometry::fullyVisibleCount() const { const auto r = thumbRect(0); - const auto visible = (m_widgetSize.height() / (r.height() + m_spacing)); + + const auto thumbWithSpacing = r.height() + m_metrics.spacing; + const auto visible = (m_widgetSize.height() / thumbWithSpacing); + return static_cast(visible); } -QRect ImagesGeometry::thumbRect(std::size_t i) const +QRect Geometry::thumbRect(std::size_t i) const { // rect for the top thumbnail QRect r = m_topRect; // move down - const auto thumbWithSpacing = m_spacing + r.height(); + const auto thumbWithSpacing = m_metrics.spacing + r.height(); r.translate(0, static_cast(i * thumbWithSpacing)); return r; } -QRect ImagesGeometry::borderRect(std::size_t i) const +QRect Geometry::borderRect(std::size_t i) const { auto r = thumbRect(i); + const auto& m = m_metrics; - // remove margins - const auto m = m_margins; - r.adjust(m, m, -m, -m); + // remove margins and text + r.adjust(m.margins, m.margins, -m.margins, -m.margins); + + // remove text + r.adjust(0, 0, 0, -(m.textSpacing + m.textHeight)); return r; } -QRect ImagesGeometry::imageRect(std::size_t i) const +QRect Geometry::imageRect(std::size_t i) const { auto r = borderRect(i); // remove border and padding - const auto m = m_border + m_padding; + const auto m = m_metrics.border + m_metrics.padding; r.adjust(m, m, -m, -m); return r; } -std::size_t ImagesGeometry::indexAt(const QPoint& p) const +QRect Geometry::textRect(std::size_t i) const +{ + const auto r = borderRect(i); + + return QRect( + r.left(), + r.bottom() + m_metrics.textSpacing, + r.width(), + m_metrics.textHeight); +} + +QRect Geometry::selectionRect(std::size_t i) const +{ + const auto br = borderRect(i); + const auto tr = textRect(i); + + return QRect( + br.left(), + br.top(), + br.width(), + tr.bottom() - br.top()); +} + +std::size_t Geometry::indexAt(const QPoint& p) const { // calculate index purely based on y position - const std::size_t offset = p.y() / (m_topRect.height() + m_spacing); + const std::size_t offset = p.y() / (m_topRect.height() + m_metrics.spacing); - if (!borderRect(offset).contains(p)) { + if (!selectionRect(offset).contains(p)) { return BadIndex; } return offset; } -QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const +QSize Geometry::scaledImageSize(const QSize& originalSize) const { const auto availableSize = imageRect(0).size(); return resizeWithAspectRatio(originalSize, availableSize); } -void ImagesGeometry::dump() const + +File::File(QString path) + : m_path(std::move(path)), m_failed(false) +{ +} + +void File::ensureOriginalLoaded() +{ + if (m_original.isNull()) { + if (!m_original.load(m_path)) { + qCritical() << "failed to load image from " << m_path; + m_failed = true; + } + } +} + +const QString& File::path() const +{ + return m_path; +} + +const QString& File::filename() const +{ + if (m_filename.isEmpty()) { + m_filename = QFileInfo(m_path).fileName(); + } + + return m_filename; +} + +const QImage& File::original() const { - qDebug() - << "ImagesTab geometry:\n" - << " . widget size: " << m_widgetSize << "\n" - << " . margins: " << m_margins << "\n" - << " . border: " << m_border << "\n" - << " . padding: " << m_padding << "\n" - << " . spacing: " << m_spacing << "\n" - << " . top rect: " << m_topRect; + return m_original; } +const QImage& File::thumbnail() const +{ + return m_thumbnail; +} + +bool File::failed() const +{ + return m_failed; +} + +void File::loadIfNeeded(const Geometry& geo) +{ + if (needsLoad(geo)) { + load(geo); + } +} + +bool File::needsLoad(const Geometry& geo) const +{ + if (m_failed) { + return false; + } + + if (m_original.isNull() || m_thumbnail.isNull()) { + return true; + } -ImagesTab::Files::Files() + const auto scaledSize = geo.scaledImageSize(m_original.size()); + return (m_thumbnail.size() != scaledSize); +} + +void File::load(const Geometry& geo) +{ + m_failed = false; + ensureOriginalLoaded(); + + if (m_failed) { + return; + } + + const auto scaledSize = geo.scaledImageSize(m_original.size()); + + m_thumbnail = m_original.scaled( + scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + + +Files::Files() : m_selection(BadIndex), m_filtered(false) { } -void ImagesTab::Files::clear() +void Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); @@ -829,17 +944,17 @@ void ImagesTab::Files::clear() m_filtered = false; } -void ImagesTab::Files::add(File f) +void Files::add(File f) { m_allFiles.emplace_back(std::move(f)); } -void ImagesTab::Files::addFiltered(File* f) +void Files::addFiltered(File* f) { m_filteredFiles.push_back(f); } -bool ImagesTab::Files::empty() const +bool Files::empty() const { if (m_filtered) { return m_filteredFiles.empty(); @@ -848,7 +963,7 @@ bool ImagesTab::Files::empty() const } } -std::size_t ImagesTab::Files::size() const +std::size_t Files::size() const { if (m_filtered) { return m_filteredFiles.size(); @@ -857,19 +972,19 @@ std::size_t ImagesTab::Files::size() const } } -void ImagesTab::Files::switchToAll() +void Files::switchToAll() { m_filtered = false; m_filteredFiles.clear(); } -void ImagesTab::Files::switchToFiltered() +void Files::switchToFiltered() { m_filtered = true; m_filteredFiles.clear(); } -const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const +const File* Files::get(std::size_t i) const { if (m_filtered) { if (i < m_filteredFiles.size()) { @@ -884,12 +999,12 @@ const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const return nullptr; } -ImagesTab::File* ImagesTab::Files::get(std::size_t i) +File* Files::get(std::size_t i) { return const_cast(std::as_const(*this).get(i)); } -std::size_t ImagesTab::Files::indexOf(const File* f) const +std::size_t Files::indexOf(const File* f) const { if (m_filtered) { for (std::size_t i=0; i& ImagesTab::Files::allFiles() +std::vector& Files::allFiles() { return m_allFiles; } -bool ImagesTab::Files::isFiltered() const +bool Files::isFiltered() const { return m_filtered; } + + +PaintContext::PaintContext(QWidget* w, Geometry geo) + : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) +{ +} + +} // namespace diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 5ce66d5d..0d34b46a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -7,10 +7,15 @@ class ImagesTab; +namespace ImagesTabHelpers +{ + +static constexpr std::size_t BadIndex = std::numeric_limits::max(); + // vertical scrollbar, this is only to handle wheel events to scroll by one // instead of the system's scroll setting // -class ImagesScrollbar : public QScrollBar +class Scrollbar : public QScrollBar { public: using QScrollBar::QScrollBar; @@ -29,7 +34,7 @@ private: // widget inside the scroller, calls ImagesTab::paintThumbnailArea() when // needed and also forwards mouse clicks and tooltip events // -class ImagesThumbnails : public QWidget +class ThumbnailsWidget : public QWidget { Q_OBJECT; @@ -101,15 +106,47 @@ private: }; +struct Theme +{ + QColor borderColor, backgroundColor, textColor; + QColor highlightBackgroundColor, highlightTextColor; + QFont font; +}; + + +struct Metrics +{ + // space outside the thumbnail border + int margins; + + // size of the border + int border; + + // space between the border and the image + int padding; + + // spacing between the thumbnail and the text + int textSpacing; + + // height of the text + int textHeight; + + // spacing between thumbnails + int spacing; + + Metrics(); +}; + + // handles all the geometry calculations by ImagesTab for painting or handling // mouse clicks // // a thumbnail looks like this: // -// +-----------------------+ <-- thumb rect +// +-----------------------+ <--- thumb rect // | margins | // | | -// | +-border--------+ <------ border rect +// | +-border--------+ <------- border rect // | | padding | | // | | | | // | | +-------+ <----------- image rect @@ -119,6 +156,10 @@ private: // | | +-------+ | | // | | | | // | +---------------+ | +// | text spacing | +// | +---------------+ <------- text rect +// | | text | | +// | +---------------+ | // | | // +-----------------------+ // @@ -130,15 +171,10 @@ private: // .... // // -class ImagesGeometry +class Geometry { public: - // returned by indexAt() if the point is outside any possible thumbnail - static constexpr std::size_t BadIndex = - std::numeric_limits::max(); - - ImagesGeometry( - const QSize& widgetSize, int margins, int border, int padding, int spacing); + Geometry(QSize widgetSize, Metrics metrics); // returns the number of images fully visible in the widget // @@ -156,9 +192,20 @@ public: // QRect imageRect(std::size_t i) const; + // rectangle of the text for the given thumbnail + // + QRect textRect(std::size_t i) const; + + // rectangle that responds to selection: includes the border and extends down + // to the text + // + QRect selectionRect(std::size_t i) const; + // returns the index of the image at the given point; this does not take into // account any scrolling, the image at the top of widget is always 0 // + // returns BadIndex if there's no thumbnail at this point + // std::size_t indexAt(const QPoint& p) const; // returns the size of the image that fits in imageRect() while keeping the @@ -166,25 +213,12 @@ public: // QSize scaledImageSize(const QSize& originalSize) const; - // dumps stuff to qDebug() - // - void dump() const; - private: // size of the widget containing all the thumbnails const QSize m_widgetSize; - // space outside the thumbnail border - const int m_margins; - - // size of the border - const int m_border; - - // space between the border and the image - const int m_padding; - - // spacing between thumbnails - const int m_spacing; + // metrics + const Metrics m_metrics; // rectangle of the first thumbnail on top const QRect m_topRect; @@ -196,11 +230,88 @@ private: }; +class File +{ +public: + File(QString path); + + void ensureOriginalLoaded(); + + const QString& path() const; + const QString& filename() const; + const QImage& original() const; + const QImage& thumbnail() const; + bool failed() const; + + void loadIfNeeded(const Geometry& geo); + +private: + QString m_path; + mutable QString m_filename; + QImage m_original, m_thumbnail; + bool m_failed; + + bool needsLoad(const Geometry& geo) const; + void load(const Geometry& geo); +}; + + +class Files +{ +public: + Files(); + + void clear(); + + void add(File f); + void addFiltered(File* f); + + bool empty() const; + std::size_t size() const; + + void switchToAll(); + void switchToFiltered(); + + const File* get(std::size_t i) const; + File* get(std::size_t i); + std::size_t indexOf(const File* f) const; + + const File* selectedFile() const; + File* selectedFile(); + std::size_t selectedIndex() const; + void select(std::size_t i); + + std::vector& allFiles(); + + bool isFiltered() const; + +private: + std::vector m_allFiles; + std::vector m_filteredFiles; + std::size_t m_selection; + bool m_filtered; +}; + + +struct PaintContext +{ + mutable QPainter painter; + Geometry geo; + File* file; + std::size_t thumbIndex; + std::size_t fileIndex; + + PaintContext(QWidget* w, Geometry geo); +}; + +} // namespace + + class ImagesTab : public ModInfoDialogTab { Q_OBJECT; - friend class ImagesScrollbar; - friend class ImagesThumbnails; + friend class ImagesTabHelpers::Scrollbar; + friend class ImagesTabHelpers::ThumbnailsWidget; public: ImagesTab( @@ -214,94 +325,21 @@ public: void restoreState(const Settings& s) override; private: - static constexpr std::size_t BadIndex = - std::numeric_limits::max(); - - struct File - { - QString path; - QImage original, thumbnail; - bool failed = false; - - File(QString path) - : path(std::move(path)) - { - } - - void ensureOriginalLoaded() - { - if (original.isNull()) { - if (!original.load(path)) { - qCritical() << "failed to load image from " << path; - failed = true; - } - } - } - }; - - struct Colors - { - QColor border, background, selection; - }; - - struct PaintContext - { - mutable QPainter painter; - ImagesGeometry geo; - File* file; - std::size_t thumbIndex; - std::size_t fileIndex; - - PaintContext(QWidget* w, ImagesGeometry geo) - : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) - { - } - }; - - class Files - { - public: - Files(); - - void clear(); - - void add(File f); - void addFiltered(File* f); - - bool empty() const; - std::size_t size() const; - - void switchToAll(); - void switchToFiltered(); - - const File* get(std::size_t i) const; - File* get(std::size_t i); - std::size_t indexOf(const File* f) const; - - const File* selectedFile() const; - File* selectedFile(); - std::size_t selectedIndex() const; - void select(std::size_t i); - - std::vector& allFiles(); - - bool isFiltered() const; - - private: - std::vector m_allFiles; - std::vector m_filteredFiles; - std::size_t m_selection; - bool m_filtered; - }; - + using ScalableImage = ImagesTabHelpers::ScalableImage; + using Files = ImagesTabHelpers::Files; + using File = ImagesTabHelpers::File; + using Theme = ImagesTabHelpers::Theme; + using Metrics = ImagesTabHelpers::Metrics; + using PaintContext = ImagesTabHelpers::PaintContext; + using Geometry = ImagesTabHelpers::Geometry; ScalableImage* m_image; std::vector m_supportedFormats; Files m_files; - int m_margins, m_border, m_padding, m_spacing; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; - Colors m_colors; + Theme m_theme; + Metrics m_metrics; void getSupportedFormats(); void enableDDS(bool b); @@ -325,18 +363,17 @@ private: std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; - ImagesGeometry makeGeometry() const; + Geometry makeGeometry() const; void paintThumbnail(const PaintContext& cx); void paintThumbnailBackground(const PaintContext& cx); void paintThumbnailBorder(const PaintContext& cx); void paintThumbnailImage(const PaintContext& cx); + void paintThumbnailText(const PaintContext& cx); void checkFiltering(); void switchToAll(); void switchToFiltered(); - bool needsReload(const ImagesGeometry& geo, const File& file) const; - void reload(const ImagesGeometry& geo, File& file); void updateScrollbar(); }; -- cgit v1.3.1 From 162fa30c3eb10db25f6ecc7bea84d38a2ab106a1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:43:46 -0400 Subject: add image dimension to toolbar and tooltip don't scroll when selecting an image with the mouse --- src/modinfodialog.ui | 9 ++++++++- src/modinfodialogimages.cpp | 35 +++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 2 +- 3 files changed, 36 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 3da13261..01d70d73 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -174,7 +174,7 @@ - 3 + 6 0 @@ -281,6 +281,13 @@
+ + + + 0x0 + + +
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 8c323ac6..3d50876b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -28,6 +28,12 @@ QRect centeredRect(const QRect& rect, const QSize& size) size.height()); } +QString dimensionString(const QSize& s) +{ + return QString::fromUtf8("%1 \xc3\x97 %2") + .arg(s.width()).arg(s.height()); +} + ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, @@ -152,9 +158,9 @@ void ImagesTab::switchToAll() // reselect old if (oldSelection) { - m_files.select(m_files.indexOf(oldSelection)); + select(m_files.indexOf(oldSelection)); } else { - m_files.select(BadIndex); + select(BadIndex); } } @@ -226,7 +232,7 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(std::size_t i) +void ImagesTab::select(std::size_t i, bool ev) { m_files.select(i); @@ -238,11 +244,17 @@ void ImagesTab::select(std::size_t i) ui->imagesPath->setText(QDir::toNativeSeparators(f->path())); ui->imagesExplore->setEnabled(true); + ui->imagesSize->setText(dimensionString(f->original().size())); + m_image->setImage(f->original()); - ensureVisible(i); + + if (ev) { + ensureVisible(i); + } } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); + ui->imagesSize->clear(); m_image->clear(); } @@ -444,7 +456,12 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) const auto i = fileIndexAtPos(e->pos()); if (i != BadIndex) { - select(i); + // 'false' so the selection is not made visible + // + // the only way to click on a thumbnail is if it's already visible, so the + // only thing that can happen is a click on a partially visible thumbnail, + // which would scroll so it is fully visible, and that's just annoying + select(i, false); } } @@ -510,9 +527,11 @@ void ImagesTab::showTooltip(QHelpEvent* e) return; } - QToolTip::showText( - e->globalPos(), QDir::toNativeSeparators(f->path()), - ui->imagesThumbnails); + const auto s = QString("%1 (%2)") + .arg(QDir::toNativeSeparators(f->path())) + .arg(dimensionString(f->original().size())); + + QToolTip::showText(e->globalPos(), s, ui->imagesThumbnails); } void ImagesTab::onExplore() diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 0d34b46a..566113bf 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -356,7 +356,7 @@ private: void onShowDDS(); void onFilterChanged(); - void select(std::size_t i); + void select(std::size_t i, bool ensureVisible=true); void moveSelection(int by); void ensureVisible(std::size_t i); -- cgit v1.3.1 From c90a822444da5842a12786dc923b2a481850608f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:57:27 -0400 Subject: added path to editor toolbar moved url to top in nexus tab and made it readonly --- src/modinfodialog.ui | 67 +++++++++++++++++++++++++++++++--------------- src/modinfodialognexus.cpp | 11 -------- src/modinfodialognexus.h | 1 - src/texteditor.cpp | 18 +++++++++---- src/texteditor.h | 1 + 5 files changed, 60 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 01d70d73..4de65e95 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -887,7 +887,7 @@ text-align: left; - + @@ -996,19 +996,58 @@ p, li { white-space: pre-wrap; } - - - 150 - 16777215 - - 32 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + URL + + + + + + + true + + + + + + @@ -1054,20 +1093,6 @@ p, li { white-space: pre-wrap; }
- - - - - - URL - - - - - - - - diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index adf79060..e1fbe352 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -24,7 +24,6 @@ NexusTab::NexusTab( connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); - connect(ui->url, &QLineEdit::editingFinished, [&]{ onUrlChanged(); }); connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); @@ -242,16 +241,6 @@ void NexusTab::onVersionChanged() updateVersionColor(); } -void NexusTab::onUrlChanged() -{ - if (m_loading) { - return; - } - - mod()->setURL(ui->url->text()); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); -} - void NexusTab::onOpenLink() { const int modID = mod()->getNexusID(); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 92330704..8528f0af 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -64,7 +64,6 @@ private: void onVersionChanged(); void onRefreshBrowser(); void onEndorse(); - void onUrlChanged(); }; #endif // MODINFODIALOGNEXUS_H diff --git a/src/texteditor.cpp b/src/texteditor.cpp index 9bbe3ddd..130cd76f 100644 --- a/src/texteditor.cpp +++ b/src/texteditor.cpp @@ -454,8 +454,9 @@ void TextEditorLineNumbers::updateArea(const QRect &rect, int dy) } -TextEditorToolbar::TextEditorToolbar(TextEditor& editor) - : m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr) +TextEditorToolbar::TextEditorToolbar(TextEditor& editor) : + m_editor(editor), m_save(nullptr), m_wordWrap(nullptr), m_explore(nullptr), + m_path(nullptr) { m_save = new QAction( QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); @@ -467,10 +468,13 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) m_wordWrap = new QAction( QIcon(":/MO/gui/word-wrap"), QObject::tr("&Word wrap"), &editor); + m_wordWrap->setCheckable(true); + m_explore = new QAction( QObject::tr("&Open in Explorer"), &editor); - m_wordWrap->setCheckable(true); + m_path = new QLineEdit; + m_path->setReadOnly(true); QObject::connect(m_save, &QAction::triggered, [&]{ m_editor.save(); }); QObject::connect(m_wordWrap, &QAction::triggered, [&]{ m_editor.toggleWordWrap(); }); @@ -492,6 +496,8 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) b->setDefaultAction(m_explore); layout->addWidget(b); + layout->addWidget(m_path); + QObject::connect(&m_editor, &TextEditor::modified, [&](bool b){ onTextModified(b); }); QObject::connect(&m_editor, &TextEditor::wordWrapChanged, [&](bool b){ onWordWrap(b); }); QObject::connect(&m_editor, &TextEditor::loaded, [&](QString f){ onLoaded(f); }); @@ -507,12 +513,14 @@ void TextEditorToolbar::onWordWrap(bool b) m_wordWrap->setChecked(b); } -void TextEditorToolbar::onLoaded(const QString& s) +void TextEditorToolbar::onLoaded(const QString& path) { - const auto hasDoc = !s.isEmpty(); + const auto hasDoc = !path.isEmpty(); m_explore->setEnabled(hasDoc); m_wordWrap->setEnabled(hasDoc); + m_path->setEnabled(hasDoc); + m_path->setText(path); } void HTMLEditor::focusOutEvent(QFocusEvent* e) diff --git a/src/texteditor.h b/src/texteditor.h index 798222a3..dc53f1c4 100644 --- a/src/texteditor.h +++ b/src/texteditor.h @@ -17,6 +17,7 @@ private: QAction* m_save; QAction* m_wordWrap; QAction* m_explore; + QLineEdit* m_path; void onTextModified(bool b); void onWordWrap(bool b); -- cgit v1.3.1 From 4b958d9efed3834ceaa57f6c50a1e8288eff01b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:12:22 -0400 Subject: added visibility flags reduced paging size by one, feels much more natural --- src/modinfodialogimages.cpp | 33 ++++++++++++++++++++++----------- src/modinfodialogimages.h | 11 +++++++++-- 2 files changed, 31 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 3d50876b..02fc7c20 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -116,6 +116,14 @@ void ImagesTab::update() { checkFiltering(); updateScrollbar(); + + // visibility needs to be rechecked here because the scrollbar configuration + // may have changed in updateScrollbar(), in which case any ensureVisible() + // calls in checkFiltering() might have been incorrect + if (m_files.selectedIndex() != BadIndex) { + ensureVisible(m_files.selectedIndex(), Visibility::Partial); + } + ui->imagesThumbnails->update(); setHasData(m_files.size() > 0); @@ -232,7 +240,7 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(std::size_t i, bool ev) +void ImagesTab::select(std::size_t i, Visibility v) { m_files.select(i); @@ -247,10 +255,7 @@ void ImagesTab::select(std::size_t i, bool ev) ui->imagesSize->setText(dimensionString(f->original().size())); m_image->setImage(f->original()); - - if (ev) { - ensureVisible(i); - } + ensureVisible(i, v); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); @@ -293,17 +298,25 @@ void ImagesTab::moveSelection(int by) select(i); } -void ImagesTab::ensureVisible(std::size_t i) +void ImagesTab::ensureVisible(std::size_t i, Visibility v) { + if (v == Visibility::Ignore) { + return; + } + const auto geo = makeGeometry(); const auto fullyVisible = geo.fullyVisibleCount(); + const auto partiallyVisible = fullyVisible + 1; + const auto first = ui->imagesScrollerVBar->value(); + const auto last = (v == Visibility::Full ? + first + fullyVisible : first + partiallyVisible); if (i < first) { // go up ui->imagesScrollerVBar->setValue(static_cast(i)); - } else if (i >= first + fullyVisible) { + } else if (i >= last) { // go down if (i >= fullyVisible) { @@ -456,12 +469,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) const auto i = fileIndexAtPos(e->pos()); if (i != BadIndex) { - // 'false' so the selection is not made visible - // // the only way to click on a thumbnail is if it's already visible, so the // only thing that can happen is a click on a partially visible thumbnail, // which would scroll so it is fully visible, and that's just annoying - select(i, false); + select(i, Visibility::Ignore); } } @@ -574,7 +585,7 @@ void ImagesTab::updateScrollbar() const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setSingleStep(1); - ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible - 1)); ui->imagesScrollerVBar->setEnabled(true); } } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 566113bf..d22a6ab2 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -325,6 +325,13 @@ public: void restoreState(const Settings& s) override; private: + enum class Visibility + { + Ignore = 0, + Full, + Partial + }; + using ScalableImage = ImagesTabHelpers::ScalableImage; using Files = ImagesTabHelpers::Files; using File = ImagesTabHelpers::File; @@ -356,9 +363,9 @@ private: void onShowDDS(); void onFilterChanged(); - void select(std::size_t i, bool ensureVisible=true); + void select(std::size_t i, Visibility v=Visibility::Full); void moveSelection(int by); - void ensureVisible(std::size_t i); + void ensureVisible(std::size_t i, Visibility v); std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; -- cgit v1.3.1 From a818954ee5d02b8723f5b48f458a0c76f9ba1935 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:30:46 -0400 Subject: added explore menu item to conflict lists and filetree changed filetree context menu to always show all items added mnemonics to conflicts context menu --- src/modinfodialog.cpp | 6 ++ src/modinfodialog.h | 1 + src/modinfodialogconflicts.cpp | 43 ++++++++-- src/modinfodialogconflicts.h | 3 + src/modinfodialogfiletree.cpp | 180 ++++++++++++++++++++++++----------------- src/modinfodialogfiletree.h | 2 + 6 files changed, 154 insertions(+), 81 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 7696023f..2772994c 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -66,6 +66,12 @@ bool canOpenFile(bool isArchive, const QString&) return !isArchive; } +bool canExploreFile(bool isArchive, const QString&) +{ + // can explore anything as long as it's not in an archive + return !isArchive; +} + bool canHideFile(bool isArchive, const QString& filename) { if (isArchive) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 6dc66003..273af8c7 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -36,6 +36,7 @@ class MainWindow; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); +bool canExploreFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); bool canUnhideFile(bool isArchive, const QString& filename); diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index e631db30..a3383a50 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -7,6 +7,7 @@ using namespace MOShared; using namespace MOBase; +namespace shell = MOBase::shell; // if there are more than 50 selected items in the conflict tree, don't bother // checking whether menu items apply to them, just show all of them @@ -91,6 +92,11 @@ public: return canPreviewFile(pluginContainer, isArchive(), fileName()); } + bool canExplore() const + { + return canExploreFile(isArchive(), fileName()); + } + private: QString m_before; QString m_relativeName; @@ -533,6 +539,16 @@ void ConflictsTab::previewItems(QTreeView* tree) }); } +void ConflictsTab::exploreItems(QTreeView* tree) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for_each_in_selection(tree, [&](const ConflictItem* item) { + shell::ExploreFile(item->fileName()); + return true; + }); +} + void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) { auto actions = createMenuActions(tree); @@ -557,6 +573,15 @@ void ConflictsTab::showContextMenu(const QPoint &pos, QTreeView* tree) menu.addAction(actions.preview); } + // explore + if (actions.explore) { + connect(actions.explore, &QAction::triggered, [&]{ + exploreItems(tree); + }); + + menu.addAction(actions.explore); + } + // hide if (actions.hide) { connect(actions.hide, &QAction::triggered, [&]{ @@ -603,6 +628,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) bool enableUnhide = true; bool enableOpen = true; bool enablePreview = true; + bool enableExplore = true; bool enableGoto = true; const auto n = smallSelectionSize(tree); @@ -626,6 +652,7 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableUnhide = item->canUnhide(); enableOpen = item->canOpen(); enablePreview = item->canPreview(plugin()); + enableExplore = item->canExplore(); enableGoto = item->hasAlts(); } else { @@ -634,6 +661,9 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) enableOpen = false; enablePreview = false; + // can't explore multiple files + enableExplore = false; + // don't bother with this on multiple selection, at least for now enableGoto = false; @@ -664,21 +694,24 @@ ConflictsTab::Actions ConflictsTab::createMenuActions(QTreeView* tree) Actions actions; - actions.hide = new QAction(tr("Hide"), parentWidget()); + actions.hide = new QAction(tr("&Hide"), parentWidget()); actions.hide->setEnabled(enableHide); // note that it is possible for hidden files to appear if they override other // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), parentWidget()); + actions.unhide = new QAction(tr("&Unhide"), parentWidget()); actions.unhide->setEnabled(enableUnhide); - actions.open = new QAction(tr("Open/Execute"), parentWidget()); + actions.open = new QAction(tr("&Open/Execute"), parentWidget()); actions.open->setEnabled(enableOpen); - actions.preview = new QAction(tr("Preview"), parentWidget()); + actions.preview = new QAction(tr("&Preview"), parentWidget()); actions.preview->setEnabled(enablePreview); - actions.gotoMenu = new QMenu(tr("Go to..."), parentWidget()); + actions.explore = new QAction(tr("Open in &Explorer"), parentWidget()); + actions.explore->setEnabled(enableExplore); + + actions.gotoMenu = new QMenu(tr("&Go to..."), parentWidget()); actions.gotoMenu->setEnabled(enableGoto); if (enableGoto && n == 1) { diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index fc7dd32a..3fa12231 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -115,6 +115,7 @@ public: void openItems(QTreeView* tree); void previewItems(QTreeView* tree); + void exploreItems(QTreeView* tree); void changeItemsVisibility(QTreeView* tree, bool visible); @@ -127,7 +128,9 @@ private: QAction* unhide = nullptr; QAction* open = nullptr; QAction* preview = nullptr; + QAction* explore = nullptr; QMenu* gotoMenu = nullptr; + std::vector gotoActions; }; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 24faec5e..6690dd2f 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -24,8 +24,9 @@ FileTreeTab::FileTreeTab( ui->filetree->setColumnWidth(0, 300); m_actions.newFolder = new QAction(tr("&New Folder"), ui->filetree); - m_actions.open = new QAction(tr("&Open"), ui->filetree); + m_actions.open = new QAction(tr("&Open/Execute"), ui->filetree); m_actions.preview = new QAction(tr("&Preview"), ui->filetree); + m_actions.explore = new QAction(tr("Open in &Explorer"), ui->filetree); m_actions.rename = new QAction(tr("&Rename"), ui->filetree); m_actions.del = new QAction(tr("&Delete"), ui->filetree); m_actions.hide = new QAction(tr("&Hide"), ui->filetree); @@ -34,6 +35,7 @@ FileTreeTab::FileTreeTab( connect(m_actions.newFolder, &QAction::triggered, [&]{ onCreateDirectory(); }); connect(m_actions.open, &QAction::triggered, [&]{ onOpen(); }); connect(m_actions.preview, &QAction::triggered, [&]{ onPreview(); }); + connect(m_actions.explore, &QAction::triggered, [&]{ onExplore(); }); connect(m_actions.rename, &QAction::triggered, [&]{ onRename(); }); connect(m_actions.del, &QAction::triggered, [&]{ onDelete(); }); connect(m_actions.hide, &QAction::triggered, [&]{ onHide(); }); @@ -140,6 +142,17 @@ void FileTreeTab::onPreview() core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); } +void FileTreeTab::onExplore() +{ + auto selection = singleSelection(); + + if (selection.isValid()) { + shell::ExploreFile(m_fs->filePath(selection)); + } else { + shell::ExploreFile(mod()->absolutePath()); + } +} + void FileTreeTab::onRename() { auto selection = singleSelection(); @@ -316,103 +329,118 @@ void FileTreeTab::onContextMenu(const QPoint &pos) QMenu menu(ui->filetree); - menu.addAction(m_actions.newFolder); - - if (selection.size() > 0) { - bool enableOpen = true; - bool enablePreview = true; - bool enableRename = true; - bool enableDelete = true; - bool enableHide = true; - bool enableUnhide = true; + bool enableNewFolder = true; + bool enableOpen = true; + bool enablePreview = true; + bool enableExplore = true; + bool enableRename = true; + bool enableDelete = true; + bool enableHide = true; + bool enableUnhide = true; + + if (selection.size() == 0) { + // no selection, only new folder and explore + enableOpen = false; + enablePreview = false; + enableRename = false; + enableDelete = false; + enableHide = false; + enableUnhide = false; + } else if (selection.size() == 1) { + // single selection + + // only enable open action if a file is selected + bool hasFiles = false; + + for (auto index : selection) { + if (m_fs->fileInfo(index).isFile()) { + hasFiles = true; + break; + } + } - if (selection.size() == 1) { - // single selection + if (!hasFiles) { + enableOpen = false; + enablePreview = false; + } - // only enable open action if a file is selected - bool hasFiles = false; + const QString fileName = m_fs->fileName(selection[0]); - for (auto index : selection) { - if (m_fs->fileInfo(index).isFile()) { - hasFiles = true; - break; - } - } + if (!canPreviewFile(plugin(), false, fileName)) { + enablePreview = false; + } - if (!hasFiles) { - enableOpen = false; - enablePreview = false; - } + if (!canExploreFile(false, fileName)) { + enableExplore = false; + } - const QString fileName = m_fs->fileName(selection[0]); + if (!canHideFile(false, fileName)) { + enableHide = false; + } - if (!canPreviewFile(plugin(), false, fileName)) { - enablePreview = 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; + enablePreview = false; - if (!canHideFile(false, fileName)) { - enableHide = false; - } + // can't explore multiple files + enableExplore = 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; - enablePreview = false; - enableRename = false; + // can't rename multiple files + enableRename = false; - if (selection.size() < max_scan_for_context_menu) { - // if the number of selected items is low, checking them to accurately - // show the menu items is worth it - enableHide = false; - enableUnhide = false; + if (selection.size() < max_scan_for_context_menu) { + // 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 : selection) { - const QString fileName = m_fs->fileName(index); + for (const auto& index : selection) { + const QString fileName = m_fs->fileName(index); - if (canHideFile(false, fileName)) { - enableHide = true; - } + if (canHideFile(false, fileName)) { + enableHide = true; + } - if (canUnhideFile(false, fileName)) { - enableUnhide = true; - } + if (canUnhideFile(false, fileName)) { + enableUnhide = true; + } - if (enableHide && enableUnhide) { - // found both, no need to check more - break; - } + if (enableHide && enableUnhide) { + // found both, no need to check more + break; } } } + } - if (enableOpen) { - menu.addAction(m_actions.open); - } + menu.addAction(m_actions.newFolder); + m_actions.newFolder->setEnabled(enableNewFolder); - if (enablePreview) { - menu.addAction(m_actions.preview); - } + menu.addAction(m_actions.open); + m_actions.open->setEnabled(enableOpen); - if (enableRename) { - menu.addAction(m_actions.rename); - } + menu.addAction(m_actions.preview); + m_actions.preview->setEnabled(enablePreview); - if (enableDelete) { - menu.addAction(m_actions.del); - } + menu.addAction(m_actions.explore); + m_actions.explore->setEnabled(enableExplore); - if (enableHide) { - menu.addAction(m_actions.hide); - } + menu.addAction(m_actions.rename); + m_actions.rename->setEnabled(enableRename); - if (enableUnhide) { - menu.addAction(m_actions.unhide); - } - } + menu.addAction(m_actions.del); + m_actions.del->setEnabled(enableDelete); + + menu.addAction(m_actions.hide); + m_actions.hide->setEnabled(enableHide); + + menu.addAction(m_actions.unhide); + m_actions.unhide->setEnabled(enableUnhide); menu.exec(ui->filetree->viewport()->mapToGlobal(pos)); } diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 2145f298..9f5206b9 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -20,6 +20,7 @@ private: QAction *newFolder = nullptr; QAction *open = nullptr; QAction *preview = nullptr; + QAction *explore = nullptr; QAction *rename = nullptr; QAction *del = nullptr; QAction *hide = nullptr; @@ -32,6 +33,7 @@ private: void onCreateDirectory(); void onOpen(); void onPreview(); + void onExplore(); void onRename(); void onDelete(); void onHide(); -- cgit v1.3.1 From e1990df0a2f25b4ac9227655fd5d597cf8ab6cf4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:36:40 -0400 Subject: fixed tab re-ordering not being used if the old setting was present --- src/modinfodialog.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2772994c..ca20b318 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -427,6 +427,11 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().setValue("mod_info_tab_order", tabState); } + // remove old settings + if (s.directInterface().contains("mod_info_tabs")) { + s.directInterface().remove("mod_info_tabs"); + } + for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); } -- cgit v1.3.1 From 6547197e5f685439371446b499427848ed687871 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:01:38 -0400 Subject: remember state of various widgets remove obsolete settings --- src/modinfodialog.cpp | 12 ++++++++---- src/modinfodialogesps.cpp | 11 +++++++++++ src/modinfodialogesps.h | 2 ++ src/modinfodialogimages.cpp | 4 ++++ src/modinfodialogtab.h | 23 +++++++++++++++++++++++ src/modinfodialogtextfiles.cpp | 19 +++++++++++++++---- src/modinfodialogtextfiles.h | 3 +++ 7 files changed, 66 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ca20b318..bcb979d2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -427,10 +427,14 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().setValue("mod_info_tab_order", tabState); } - // remove old settings - if (s.directInterface().contains("mod_info_tabs")) { - s.directInterface().remove("mod_info_tabs"); - } + // remove 2.2.0 settings + s.directInterface().remove("mod_info_tabs"); + s.directInterface().remove("mod_info_conflict_expanders"); + s.directInterface().remove("mod_info_conflicts"); + s.directInterface().remove("mod_info_advanced_conflicts"); + s.directInterface().remove("mod_info_conflicts_overwrite"); + s.directInterface().remove("mod_info_conflicts_noconflict"); + s.directInterface().remove("mod_info_conflicts_overwritten"); for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 7961096d..d00eccf3 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,6 +1,7 @@ #include "modinfodialogesps.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include using MOBase::reportError; @@ -275,6 +276,16 @@ void ESPsTab::update() setHasData(m_inactiveModel->rowCount() > 0 || m_activeModel->rowCount() > 0); } +void ESPsTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), ui->ESPsSplitter); +} + +void ESPsTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), ui->ESPsSplitter); +} + void ESPsTab::onActivate() { const auto index = ui->inactiveESPList->currentIndex(); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index c972074a..217863c6 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -18,6 +18,8 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update(); + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; private: ESPListModel* m_inactiveModel; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 02fc7c20..307fa8e8 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -133,12 +133,16 @@ void ImagesTab::saveState(Settings& s) { s.directInterface().setValue( "mod_info_dialog_images_show_dds", m_ddsEnabled); + + saveWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { ui->imagesShowDDS->setChecked(s.directInterface() .value("mod_info_dialog_images_show_dds", false).toBool()); + + restoreWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::checkFiltering() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 058035ab..ce29c868 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -67,6 +67,23 @@ protected: void emitModOpen(QString name); void setHasData(bool b); + // this needs to be a template because saveState() and restoreState() are + // not in QWidget, but they're in various widgets + // + template + void saveWidgetState(QSettings& s, Widget* w) + { + s.setValue(settingName(w), w->saveState()); + } + + template + void restoreWidgetState(const QSettings& s, Widget* w) + { + if (s.contains(settingName(w))) { + w->restoreState(s.value(settingName(w)).toByteArray()); + } + } + private: OrganizerCore& m_core; PluginContainer& m_plugin; @@ -76,6 +93,12 @@ private: int m_tabID; bool m_hasData; bool m_firstActivation; + + + QString settingName(QWidget* w) + { + return "geometry/modinfodialog_" + w->objectName(); + } }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 5b035c53..52891bf5 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,6 +1,7 @@ #include "modinfodialogtextfiles.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include class FileListModel : public QAbstractItemModel @@ -102,14 +103,14 @@ GenericFilesTab::GenericFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id, QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_list(list), m_editor(e), m_model(new FileListModel) + m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); m_editor->setupToolbar(); - sp->setSizes({200, 1}); - sp->setStretchFactor(0, 0); - sp->setStretchFactor(1, 1); + m_splitter->setSizes({200, 1}); + m_splitter->setStretchFactor(0, 0); + m_splitter->setStretchFactor(1, 1); m_filter.setEdit(filter); m_filter.setList(m_list); @@ -171,6 +172,16 @@ void GenericFilesTab::update() setHasData(m_model->rowCount() > 0); } +void GenericFilesTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), m_splitter); +} + +void GenericFilesTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), m_splitter); +} + void GenericFilesTab::onSelection( const QModelIndex& current, const QModelIndex& previous) { diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 037c6bb3..ffe49904 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -19,10 +19,13 @@ public: bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update() override; + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; protected: QListView* m_list; TextEditor* m_editor; + QSplitter* m_splitter; FileListModel* m_model; FilterWidget m_filter; -- cgit v1.3.1 From 9ec0feba4395d492d814c03cf8be67a196bab36e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:57:21 -0400 Subject: fixed crash when opening separators --- src/modinfodialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index bcb979d2..676f3480 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -345,7 +345,9 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) feedFiles(becauseOriginChanged); for (auto& tabInfo : m_tabs) { - tabInfo.tab->update(); + if (tabInfo.isVisible()) { + tabInfo.tab->update(); + } } setTabsColors(); -- cgit v1.3.1 From 093eb5650bdd010d9c779e64d3ca63682ba6ca19 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 15:17:05 -0400 Subject: fixed problems with re-ordering tabs, realPos was never updated properly and it wasn't actually used everywhere --- src/modinfodialog.cpp | 40 ++++++++++++++++++++++++++++++++++++---- src/modinfodialog.h | 2 ++ 2 files changed, 38 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 676f3480..a5e8d8cd 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -170,6 +170,7 @@ ModInfoDialog::ModInfoDialog( } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); + connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -249,12 +250,14 @@ ModInfoDialog::TabInfo* ModInfoDialog::currentTab() return nullptr; } - const auto i = static_cast(index); - if (i >= m_tabs.size()) { - return nullptr; + for (auto& tabInfo : m_tabs) { + if (tabInfo.realPos == index) { + return &tabInfo; + } } - return &m_tabs[i]; + qCritical() << "tab index " << index << " not found"; + return nullptr; } void ModInfoDialog::update(bool firstTime) @@ -272,6 +275,7 @@ void ModInfoDialog::update(bool firstTime) } if (ui->tabWidget->currentIndex() == oldTab) { + // manually fire activated() because the tab index hasn't been changed if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } @@ -600,6 +604,34 @@ void ModInfoDialog::onTabChanged() } } +void ModInfoDialog::onTabMoved() +{ + // reset + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // for each tab in the widget + for (int i=0; itabWidget->count(); ++i) { + const auto* w = ui->tabWidget->widget(i); + + bool found = false; + + // find the corresponding tab info + for (auto& tabInfo : m_tabs) { + if (tabInfo.widget == w) { + tabInfo.realPos = i; + found = true; + break; + } + } + + if (!found) { + qCritical() << "unknown tab at index " << i; + } + } +} + void ModInfoDialog::on_nextButton_clicked() { auto mod = m_mainWindow->nextModInList(); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 273af8c7..030b3f93 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -158,8 +158,10 @@ private: void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + void onOriginModified(std::size_t tabIndex, int originID); void onTabChanged(); + void onTabMoved(); template std::unique_ptr createTab(int index) -- cgit v1.3.1 From b4c26c516412b2f7270be02494b85be9cad0cadb Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 17:28:15 -0400 Subject: fix inactive esps being marked as active because path separators changed from / to \ since the change to std::filesystem --- src/modinfodialogesps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index d00eccf3..8dceaa31 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -12,7 +12,7 @@ public: ESPItem(QString rootPath, QString relativePath) : m_rootPath(std::move(rootPath)), m_active(false) { - if (relativePath.contains('/')) { + if (relativePath.contains('/') || relativePath.contains('\\')) { m_inactivePath = relativePath; } else { m_activePath = relativePath; -- cgit v1.3.1 From 41292e02bdebea8b09c2c11ee87221672e970cdf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 28 Jun 2019 14:21:10 -0400 Subject: changed how the mod url works: - it is now independent from the nexus mod url - it can be set in addition to having a valid mod id - both urls can be displayed in the context menu re-arranged some of the widgets on the nexus tab added a track button added a custom url checkbox and open in browser button added a max-width to the browser --- src/mainwindow.cpp | 44 +++--- src/modinfo.cpp | 19 +++ src/modinfo.h | 29 +++- src/modinfodialog.ui | 341 ++++++++++++++++++++++++++------------------- src/modinfodialognexus.cpp | 147 +++++++++++++------ src/modinfodialognexus.h | 11 +- src/modinforegular.cpp | 85 ++++++++++- src/modinforegular.h | 16 +-- 8 files changed, 462 insertions(+), 230 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d75e8d9d..d48fae4f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3429,18 +3429,16 @@ void MainWindow::visitOnNexus_clicked() int row_idx; ModInfo::Ptr info; QString gameName; - QString webUrl; + for (QModelIndex idx : selection->selectedRows()) { row_idx = idx.data(Qt::UserRole + 1).toInt(); info = ModInfo::getByIndex(row_idx); int modID = info->getNexusID(); - webUrl = info->getURL(); gameName = info->getGameName(); if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); - } - else if (webUrl != "") { - linkClicked(webUrl); + } else { + qCritical() << "mod '" << info->name() << "' has no nexus id"; } } } @@ -3450,14 +3448,13 @@ void MainWindow::visitOnNexus_clicked() if (modID > 0) { linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); } else { - MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); + MessageDialog::showMessage(tr("Nexus ID for this mod is unknown"), this); } } } void MainWindow::visitWebPage_clicked() { - QItemSelectionModel *selection = ui->modList->selectionModel(); if (selection->hasSelection() && selection->selectedRows().count() > 1) { int count = selection->selectedRows().count(); @@ -3471,28 +3468,22 @@ void MainWindow::visitWebPage_clicked() int row_idx; ModInfo::Ptr info; QString gameName; - QString webUrl; for (QModelIndex idx : selection->selectedRows()) { row_idx = idx.data(Qt::UserRole + 1).toInt(); info = ModInfo::getByIndex(row_idx); - int modID = info->getNexusID(); - webUrl = info->getURL(); - gameName = info->getGameName(); - if (modID > 0) { - linkClicked(NexusInterface::instance(&m_PluginContainer)->getModURL(modID, gameName)); - } - else if (webUrl != "") { - linkClicked(webUrl); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + linkClicked(url.toString()); } } } else { ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - if (info->getURL() != "") { - linkClicked(info->getURL()); - } - else { - MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + linkClicked(url.toString()); } } } @@ -4711,7 +4702,7 @@ void MainWindow::exportModListCSV() builder.writeHeader(); auto indexesByPriority = m_OrganizerCore.currentProfile()->getAllIndexesByPriority(); - for (auto& iter : indexesByPriority) { + for (auto& iter : indexesByPriority) { ModInfo::Ptr info = ModInfo::getByIndex(iter.second); bool enabled = m_OrganizerCore.currentProfile()->modEnabled(iter.second); if ((selectedRowID == 1) && !enabled) { @@ -4991,8 +4982,13 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (info->getNexusID() > 0) { menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - } else if ((info->getURL() != "")) { - menu.addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked())); + } + + const auto url = info->parseCustomURL(); + if (url.isValid()) { + menu.addAction( + tr("Visit on %1").arg(url.host()), + this, SLOT(visitWebPage_clicked())); } menu.addAction(tr("Open in Explorer"), this, SLOT(openExplorer_clicked())); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index bc2979ef..585d4963 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -520,3 +520,22 @@ void ModInfo::testValid() dirIter.next(); } } + +QUrl ModInfo::parseCustomURL() const +{ + if (!hasCustomURL() || getCustomURL().isEmpty()) { + return {}; + } + + const auto url = QUrl::fromUserInput(getCustomURL()); + + if (!url.isValid()) { + qCritical() + << "mod '" << name() << "' has an invalid custom url " + << "'" << getCustomURL() << "'"; + + return {}; + } + + return url; +} diff --git a/src/modinfo.h b/src/modinfo.h index f1d816fe..e395f45b 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -733,14 +733,31 @@ public: virtual void doConflictCheck() const {} /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const &) {} + * @brief sets whether this mod uses a custom url + **/ + virtual void setHasCustomURL(bool) {} /** - * @returns the URL for a mod - */ - virtual QString getURL() const { return ""; } + * @brief returns whether this mod uses a custom url + **/ + virtual bool hasCustomURL() const { return false; } + + /** + * @brief sets the custom url + **/ + virtual void setCustomURL(QString const &) {} + + /** + * @brief returns the custom url + **/ + virtual QString getCustomURL() const { return ""; } + + /** + * If hasCustomURL() is true and getCustomURL() is not empty, tries to parse + * the url using QUrl::fromUserInput() and returns it. Otherwise, returns an + * empty QUrl. + **/ + QUrl parseCustomURL() const; signals: diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 4de65e95..78f19a36 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -491,7 +491,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -887,138 +887,8 @@ text-align: left; - - - - - - 0 - 0 - - - - Refresh - - - Refresh all information from Nexus. - - - Refresh - - - - :/MO/gui/refresh:/MO/gui/refresh - - - Qt::ToolButtonTextBesideIcon - - - - - - - Open in Browser - - - - :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png - - - Qt::ToolButtonTextBesideIcon - - - - - - - Endorse - - - - :/MO/gui/icon_favorite:/MO/gui/icon_favorite - - - Qt::ToolButtonTextBesideIcon - - - - - - - Mod ID - - - - - - - Mod ID for this mod on Nexus. - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> - - - - - - - Source Game - - - - - - - Source game for this mod. - - - <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - - - Version - - - - - - - 32 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + + 0 @@ -1032,17 +902,169 @@ p, li { white-space: pre-wrap; } 0 - - - URL - - + + + + + Mod ID + + + + + + + Mod ID for this mod on Nexus. + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: <a href=" https://www.nexusmods.com/skyrimspecialedition/mods/6194"><span style=" text-decoration: underline; color:#0000ff;">https://www.nexusmods.com/skyrimspecialedition/mods/6194</span></a>. In this example, 6194 is the id you're looking for. Besides: The above is the link to Mod Organizer 2 on Nexus. Why not go there now and endorse us?</p></body></html> + + + + + + + Source Game + + + + + + + Source game for this mod. + + + <html><head/><body><p>Source game for this mod. This determines where the mod was downloaded from and decides where to fetch info, version updates, and send endorsements. Changing this will likely require you to enter a new Mod ID.</p></body></html> + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + + + Version + + + + + + + 32 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + - - - true - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Refresh + + + Refresh all information from Nexus. + + + Refresh + + + + :/MO/gui/refresh:/MO/gui/refresh + + + + + + + Open in Browser + + + + :/MO/gui/resources/internet-web-browser.png:/MO/gui/resources/internet-web-browser.png + + + + + + + Endorse + + + + :/MO/gui/icon_favorite:/MO/gui/icon_favorite + + + + + + + Track + + + + :/MO/gui/tracked:/MO/gui/tracked + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -1093,6 +1115,41 @@ p, li { white-space: pre-wrap; } + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Use Custom URL + + + + + + + + + + Open in Browser + + + + + + diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index e1fbe352..8c8ce55a 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -7,6 +7,8 @@ #include #include +namespace shell = MOBase::shell; + bool isValidModID(int id) { return (id > 0); @@ -22,15 +24,20 @@ NexusTab::NexusTab( ui->endorse->setVisible(core().settings().endorsementIntegration()); connect(ui->modID, &QLineEdit::editingFinished, [&]{ onModIDChanged(); }); - connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); - connect(ui->openInBrowser, &QToolButton::clicked, [&]{ onOpenLink(); }); - connect(ui->endorse, &QToolButton::clicked, [&]{ onEndorse(); }); - connect(ui->refresh, &QToolButton::clicked, [&]{ onRefreshBrowser(); }); - connect( ui->sourceGame, static_cast(&QComboBox::currentIndexChanged), [&]{ onSourceGameChanged(); }); + connect(ui->version, &QLineEdit::editingFinished, [&]{ onVersionChanged(); }); + + connect(ui->refresh, &QPushButton::clicked, [&]{ onRefreshBrowser(); }); + connect(ui->visitNexus, &QPushButton::clicked, [&]{ onVisitNexus(); }); + connect(ui->endorse, &QPushButton::clicked, [&]{ onEndorse(); }); + connect(ui->track, &QPushButton::clicked, [&]{ onTrack(); }); + + connect(ui->hasCustomURL, &QCheckBox::toggled, [&]{ onCustomURLToggled(); }); + connect(ui->customURL, &QLineEdit::editingFinished, [&]{ onCustomURLChanged(); }); + connect(ui->visitCustomURL, &QPushButton::clicked, [&]{ onVisitCustomURL(); }); } NexusTab::~NexusTab() @@ -52,7 +59,8 @@ void NexusTab::clear() ui->sourceGame->clear(); ui->version->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); - ui->url->clear(); + ui->hasCustomURL->setChecked(false); + ui->customURL->clear(); setHasData(false); } @@ -89,7 +97,7 @@ void NexusTab::update() connect( page, &NexusTabWebpage::linkClicked, - [&](const QUrl& url){ MOBase::shell::OpenLink(url); }); + [&](const QUrl& url){ shell::OpenLink(url); }); ui->endorse->setEnabled( (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || @@ -138,15 +146,52 @@ void NexusTab::updateWebpage() const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); - ui->openInBrowser->setToolTip(nexusLink); - mod()->setURL(nexusLink); + ui->visitNexus->setToolTip(nexusLink); refreshData(modID); } else { onModChanged(); } ui->version->setText(mod()->getVersion().displayString()); - ui->url->setText(mod()->getURL()); + ui->hasCustomURL->setChecked(mod()->hasCustomURL()); + ui->customURL->setText(mod()->getCustomURL()); + ui->customURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + + updateTracking(); +} + +void NexusTab::updateTracking() +{ + if (mod()->trackedState() == ModInfo::TRACKED_TRUE) { + ui->track->setChecked(true); + ui->track->setText(tr("Tracked")); + } else { + ui->track->setChecked(false); + ui->track->setText(tr("Untracked")); + } +} + +void NexusTab::refreshData(int modID) +{ + if (tryRefreshData(modID)) { + m_requestStarted = true; + } else { + onModChanged(); + } +} + +bool NexusTab::tryRefreshData(int modID) +{ + if (isValidModID(modID) && !m_requestStarted) { + if (mod()->updateNXMInfo()) { + ui->browser->setHtml(""); + return true; + } + } + + return false; } void NexusTab::onModChanged() @@ -165,6 +210,9 @@ void NexusTab::onModChanged() font-size: 14px; background: #404040; color: #f1f1f1; + max-width: 1060px; + margin-left: auto; + margin-right: auto; } a @@ -178,12 +226,11 @@ void NexusTab::onModChanged() )"; if (nexusDescription.isEmpty()) { - descriptionAsHTML = descriptionAsHTML.arg(tr( - "
" - "

Uh oh!

" - "

Sorry, there is no description available for this mod. :(

" - "
")); - + descriptionAsHTML = descriptionAsHTML.arg(tr(R"( +
+

This mod does not have a valid Nexus ID. You can add a custom web + page for it in the "Custom URL" box below.

+
)")); } else { descriptionAsHTML = descriptionAsHTML.arg( BBCode::convertToHTML(nexusDescription)); @@ -191,6 +238,7 @@ void NexusTab::onModChanged() ui->browser->page()->setHtml(descriptionAsHTML); updateVersionColor(); + updateTracking(); } void NexusTab::onModIDChanged() @@ -241,18 +289,6 @@ void NexusTab::onVersionChanged() updateVersionColor(); } -void NexusTab::onOpenLink() -{ - const int modID = mod()->getNexusID(); - - if (isValidModID(modID)) { - const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); - - MOBase::shell::OpenLink(QUrl(nexusLink)); - } -} - void NexusTab::onRefreshBrowser() { const auto modID = mod()->getNexusID(); @@ -265,28 +301,59 @@ void NexusTab::onRefreshBrowser() } } +void NexusTab::onVisitNexus() +{ + const int modID = mod()->getNexusID(); + + if (isValidModID(modID)) { + const QString nexusLink = NexusInterface::instance(&plugin()) + ->getModURL(modID, mod()->getGameName()); + + shell::OpenLink(QUrl(nexusLink)); + } +} + void NexusTab::onEndorse() { core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); } -void NexusTab::refreshData(int modID) +void NexusTab::onTrack() { - if (tryRefreshData(modID)) { - m_requestStarted = true; - } else { - onModChanged(); + core().loggedInAction(parentWidget(), [m=mod()] { + if (m->trackedState() == ModInfo::TRACKED_TRUE) { + m->track(false); + } else { + m->track(true); + } + }); +} + +void NexusTab::onCustomURLToggled() +{ + if (m_loading) { + return; } + + mod()->setHasCustomURL(ui->hasCustomURL->isChecked()); + ui->customURL->setEnabled(mod()->hasCustomURL()); + ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); } -bool NexusTab::tryRefreshData(int modID) +void NexusTab::onCustomURLChanged() { - if (isValidModID(modID) && !m_requestStarted) { - if (mod()->updateNXMInfo()) { - ui->browser->setHtml(""); - return true; - } + if (m_loading) { + return; } - return false; + mod()->setCustomURL(ui->customURL->text()); + ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); +} + +void NexusTab::onVisitCustomURL() +{ + const auto url = mod()->parseCustomURL(); + if (url.isValid()) { + shell::OpenLink(url); + } } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 8528f0af..930c1ffc 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -53,17 +53,24 @@ private: void cleanup(); void updateVersionColor(); void updateWebpage(); + void updateTracking(); void refreshData(int modID); bool tryRefreshData(int modID); - void onModChanged(); - void onOpenLink(); + void onModIDChanged(); void onSourceGameChanged(); void onVersionChanged(); + void onRefreshBrowser(); + void onVisitNexus(); void onEndorse(); + void onTrack(); + + void onCustomURLToggled(); + void onCustomURLChanged(); + void onVisitCustomURL(); }; #endif // MODINFODIALOGNEXUS_H diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 4333e351..448447e1 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -100,7 +100,70 @@ void ModInfoRegular::readMeta() m_Repository = metaFile.value("repository", "Nexus").toString(); m_Converted = metaFile.value("converted", false).toBool(); m_Validated = metaFile.value("validated", false).toBool(); - m_URL = metaFile.value("url", "").toString(); + + // this handles changes to how the URL works after 2.2.0 + // + // in 2.2.0, "hasCustomUrl" does not exist and "url" is only used when the mod + // id is invalid, although it can be set at any time in the mod info dialog + // + // post 2.2.0, a custom url can be set on any mod, whether the mod id is + // valid or not, so an additional flag "hasCustomURL" is required, with a + // corresponding checkbox in the mod info dialog + // + // there are several cases to handle to make sure no data is lost and to + // determine whether the user has set a custom url before: + // + // 1) some mods have an incorrect url set along with a valid mod id; + // there is apparently a bug with the fomod installer that can set the + // url of a mod to a value used by a _previous_ installation + // + // 2) it is possible to set the url even if the mod id is valid, in which + // case it is saved, but never used in 2.2.0 + // + // 3) opening the mod info dialog on the nexus tab for a mod that has a + // valid id will force the url to be the same as what the plugin gives + // back + // + // the algorithm is as follows: + // always read the url from the meta file and store it so this piece of data + // is never lost; the problem then only becomes about whether to enable + // hasCustomURL + // + // if hasCustomURL is present in the meta file, just read that and be + // done with it + // + // if not, then the flag depends on the mod id and the url + // if the mod id is valid, the custom url is disabled; although the url + // could be _set_ by the user when a mod id was valid, it was never + // _used_, so the behaviour won't change + // + // if the mod id is invalid, the url should normally be empty, unless the + // user specified one, in which case hasCustomURL should be true + // (the only case where this fails is if a mod id was valid before and + // the user visited the nexus tab, in which case the url was set + // automatically, but then the id was manually changed to 0 + // + // in that case, the mod id is invalid and the url is not empty, but it + // was never set by the user; this case is impossible to distinguish + // from a user manually entering a url, and so is handled as such) + + // always read the url + m_CustomURL = metaFile.value("url").toString(); + + if (metaFile.contains("hasCustomURL")) { + m_HasCustomURL = metaFile.value("hasCustomURL").toBool(); + } else { + if (m_NexusID > 0) { + // the mod id is valid, disable the custom url + m_HasCustomURL = false; + } else { + if (!m_CustomURL.isEmpty()) { + // the mod id is invalid and the url is not empty, enable it + m_HasCustomURL = true; + } + } + } + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); m_LastNexusUpdate = QDateTime::fromString(metaFile.value("lastNexusUpdate", "").toString(), Qt::ISODate); m_NexusLastModified = QDateTime::fromString(metaFile.value("nexusLastModified", QDateTime::currentDateTimeUtc()).toString(), Qt::ISODate); @@ -166,7 +229,8 @@ void ModInfoRegular::saveMeta() metaFile.setValue("comments", m_Comments); metaFile.setValue("notes", m_Notes); metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("url", m_URL); + metaFile.setValue("url", m_CustomURL); + metaFile.setValue("hasCustomURL", m_HasCustomURL); metaFile.setValue("nexusFileStatus", m_NexusFileStatus); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); metaFile.setValue("lastNexusUpdate", m_LastNexusUpdate.toString(Qt::ISODate)); @@ -769,18 +833,27 @@ void ModInfoRegular::setNexusLastModified(QDateTime time) emit modDetailsUpdated(true); } -void ModInfoRegular::setURL(QString const &url) +void ModInfoRegular::setCustomURL(QString const &url) { - m_URL = url; + m_CustomURL = url; m_MetaInfoChanged = true; } -QString ModInfoRegular::getURL() const +QString ModInfoRegular::getCustomURL() const { - return m_URL; + return m_CustomURL; } +void ModInfoRegular::setHasCustomURL(bool b) +{ + m_HasCustomURL = b; + m_MetaInfoChanged = true; +} +bool ModInfoRegular::hasCustomURL() const +{ + return m_HasCustomURL; +} QStringList ModInfoRegular::archives(bool checkOnDisk) { diff --git a/src/modinforegular.h b/src/modinforegular.h index cfe713ca..705e66a8 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -397,15 +397,10 @@ public: void readMeta(); - /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const &); - - /** - * @returns the URL for a mod - */ - virtual QString getURL() const; + virtual void setHasCustomURL(bool b) override; + virtual bool hasCustomURL() const override; + virtual void setCustomURL(QString const &) override; + virtual QString getCustomURL() const override; private: @@ -432,7 +427,8 @@ private: QString m_Notes; QString m_NexusDescription; QString m_Repository; - QString m_URL; + QString m_CustomURL; + bool m_HasCustomURL; QString m_GameName; mutable QStringList m_Archives; -- cgit v1.3.1 From ee71f40ce9e4edf985ee13437afc6be94c45e925 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 03:59:24 -0400 Subject: convert fs::path to utf-16 instead of ansi --- src/modinfodialog.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a5e8d8cd..b3055b5a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -360,16 +360,18 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(bool becauseOriginChanged) { namespace fs = std::filesystem; + const auto rootPath = m_mod->absolutePath(); if (rootPath.length() > 0) { - - for (const auto& entry : fs::recursive_directory_iterator(rootPath.toStdString())) { + fs::path path(rootPath.toStdWString()); + for (const auto& entry : fs::recursive_directory_iterator(path)) { if (!entry.is_regular_file()) { continue; } - const auto fileName = QString::fromWCharArray(entry.path().c_str()); + const auto fileName = QString::fromWCharArray( + entry.path().native().c_str()); for (auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { -- cgit v1.3.1 From 16b6e772a7ae1561ba87c6d76d001b19635761cd Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 1 Jul 2019 19:56:12 -0400 Subject: fixed conflict lists not expanding vertically properly --- src/modinfodialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 78f19a36..e54b94b3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -506,7 +506,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e 100 - + 0 -- cgit v1.3.1 From c6e80f1a5eed4a57663e7fc55c2727258eaad552 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 11:05:31 -0400 Subject: focus tabs when they need a confirmation to close dialog did not handle closeEvent() at all, it only checked the close button bunch of comments --- src/modinfodialog.cpp | 34 +++++++++++++++-- src/modinfodialog.h | 6 ++- src/modinfodialogtab.cpp | 5 +++ src/modinfodialogtab.h | 87 ++++++++++++++++++++++++++++++++++++++++-- src/modinfodialogtextfiles.cpp | 2 + 5 files changed, 126 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index b3055b5a..4af479c4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -154,7 +154,7 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, [this, i](int originID) { - onOriginModified(static_cast(i), originID); + onOriginModified(originID); }); connect( @@ -167,6 +167,15 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, [&]{ setTabsColors(); }); + + connect( + tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, + [&, i=static_cast(i)] + { + if (i < m_tabs.size()) { + switchToTab(ETabs(m_tabs[i].tab->tabID())); + } + }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -546,6 +555,7 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } + // removing all tabs ui->tabWidget->clear(); // reset real positions @@ -566,7 +576,7 @@ void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) } } -void ModInfoDialog::onOriginModified(std::size_t tabIndex, int originID) +void ModInfoDialog::onOriginModified(int originID) { emit originModified(originID); updateTabs(true); @@ -581,15 +591,31 @@ void ModInfoDialog::onDeleteShortcut() } } +void ModInfoDialog::closeEvent(QCloseEvent* e) +{ + if (tryClose()) { + e->accept(); + } else { + e->ignore(); + } +} + void ModInfoDialog::on_closeButton_clicked() +{ + if (tryClose()) { + close(); + } +} + +bool ModInfoDialog::tryClose() { for (auto& tabInfo : m_tabs) { if (!tabInfo.tab->canClose()) { - return; + return false; } } - close(); + return true; } void ModInfoDialog::onTabChanged() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 030b3f93..899a3eab 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -117,6 +117,9 @@ public: signals: void originModified(int originID); +protected: + void closeEvent(QCloseEvent* e); + private slots: void on_closeButton_clicked(); void on_nextButton_clicked(); @@ -158,8 +161,9 @@ private: void switchToTab(ETabs id); void reAddTabs(const std::vector& visibility, ETabs sel); std::vector getOrderedTabNames() const; + bool tryClose(); - void onOriginModified(std::size_t tabIndex, int originID); + void onOriginModified(int originID); void onTabChanged(); void onTabMoved(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index f5eeeed1..0468b405 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -137,6 +137,11 @@ void ModInfoDialogTab::setHasData(bool b) } } +void ModInfoDialogTab::setFocus() +{ + emit wantsFocus(); +} + NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index ce29c868..3f98314a 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -10,6 +10,36 @@ namespace Ui { class ModInfoDialog; } class Settings; class OrganizerCore; +// base class for all tabs in the mod info dialog +// +// when the dialog is opened or when next/previous is clicked, the sequence is: +// setMod(), clear(), feedFile() an update() +// +// when the dialog is closed, canClose() is called on all tabs +// +// when a tab is selected for the first time for the current mod, +// firstActivation() is called; this is used by NexusTab to refresh stuff +// +// when the dialog is first shown, restoreState() is called on all tabs and +// saveState() is called when the dialog is closed +// +// there isn't a good framework for keyboard shortcuts because only the delete +// key is used for now, which calls deletedRequested() on all tabs until one +// returns true +// +// each tab override canHandleSeparators() and canHandleUnmanaged() to return +// true if they can handle separators or unmanaged mods; if these return false +// (which they do by default), the tabs will be removed from the widget entirely +// +// when tabs modify the origin and call emitOriginModified() (such as the +// conflicts tabs), all tabs that return true in usesOriginFiles() will go +// through the full update sequence as above +// +// tabs can call emitModOpen() to request showing a different mod +// +// hasDataChanged() should be called when a tab goes from having data to being +// empty or vice versa; this will update the tab text color +// class ModInfoDialogTab : public QObject { Q_OBJECT; @@ -21,13 +51,63 @@ public: ModInfoDialogTab& operator=(ModInfoDialogTab&&) = default; virtual ~ModInfoDialogTab() = default; + // called by ModInfoDialog every time this tab is selected; this will call + // firstActivation() the first time it's called, until resetFirstActivation() + // is called + // void activated(); + + // called by ModInfoDialog when the selected mod has changed, to make sure + // activated() will call firstActivation() next time + // void resetFirstActivation(); + + // called when the selected mod changed, `mod` can never be empty, but + // `origin` can (if the mod is not active, for example) + // + // derived classes can override this to connect to events on the mod for + // examples (see NexusTab), but must call the base class implementation + // + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + + // this tab should clear its user interface; clear() will always be called + // before feedFile() and update() + // virtual void clear() = 0; - virtual void update(); + + // the dialog will go through each file in the mod and call feedFile() + // with it on all tabs; if a tab handles the file, it should return true to + // prevent other tabs from displaying it + // + // this prevents individual tabs from having to go through the filesystem + // independently, which would kill performance, but it cannot be the only way + // to update tabs because some of them don't actually use files (like + // NotesTab) or they use the internal structures such as `FilesOrigin` (like + // ConflictsTab) + // + // once all the files have been fed to tabs, update() will be called; tabs + // that need to do additional work after being fed files, or tabs that are + // unable to use feedFile() at all can use update() to do work + // virtual bool feedFile(const QString& rootPath, const QString& filename); + + // called after all the files on the filesystem have been sent through + // feedFile() + // + // this can be used to do a final processing of the files handled by + // feedFile() (ImagesTab will set up the scrollbars, for example) or something + // completely different (CategoriesTab will fill up the lists) + // + virtual void update(); + + // called when a tab is visible for the first time for the current mod, can + // be used to do expensive work that's not worth doing until the tab is + // actually selected by the user + // virtual void firstActivation(); + + // virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); @@ -38,8 +118,6 @@ public: virtual bool canHandleUnmanaged() const; virtual bool usesOriginFiles() const; - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); - ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; @@ -50,6 +128,7 @@ signals: void originModified(int originID); void modOpen(QString name); void hasDataChanged(); + void wantsFocus(); protected: Ui::ModInfoDialog* ui; @@ -67,6 +146,8 @@ protected: void emitModOpen(QString name); void setHasData(bool b); + void setFocus(); + // this needs to be a template because saveState() and restoreState() are // not in QWidget, but they're in various widgets // diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 52891bf5..bc44ee3e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -133,6 +133,8 @@ bool GenericFilesTab::canClose() return true; } + setFocus(); + const int res = QMessageBox::question( parentWidget(), QObject::tr("Save changes?"), -- cgit v1.3.1 From 82d985064e5105ded4b20d357eaf7cd1b97fe9da Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:02:07 -0400 Subject: added a ModInfoDialogTabContext to avoid passing too many things to tab constructors mod is passed to ctors to make sure they can never be empty only call deleteRequest() to selected mod comments --- src/mainwindow.cpp | 4 +- src/modinfodialog.cpp | 40 +++++++++++------- src/modinfodialog.h | 36 ++++------------ src/modinfodialogcategories.cpp | 14 +++--- src/modinfodialogcategories.h | 4 +- src/modinfodialogconflicts.cpp | 12 +++--- src/modinfodialogconflicts.h | 4 +- src/modinfodialogesps.cpp | 8 ++-- src/modinfodialogesps.h | 4 +- src/modinfodialogfiletree.cpp | 14 +++--- src/modinfodialogfiletree.h | 4 +- src/modinfodialogimages.cpp | 9 ++-- src/modinfodialogimages.h | 4 +- src/modinfodialognexus.cpp | 85 +++++++++++++++++++------------------ src/modinfodialognexus.h | 4 +- src/modinfodialogtab.cpp | 33 ++++++++------- src/modinfodialogtab.h | 94 +++++++++++++++++++++++++++++++++++++---- src/modinfodialogtextfiles.cpp | 30 ++++++------- src/modinfodialogtextfiles.h | 14 +++--- 19 files changed, 226 insertions(+), 191 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d48fae4f..7ab555fa 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,11 +3223,9 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer, modInfo); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - dialog.setMod(modInfo); - //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { dialog.setTab(ModInfoDialog::ETabs(tab)); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4af479c4..4ef010e4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -127,7 +127,8 @@ bool ModInfoDialog::TabInfo::isVisible() const ModInfoDialog::ModInfoDialog( - MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), @@ -138,6 +139,7 @@ ModInfoDialog::ModInfoDialog( auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + setMod(mod); m_tabs = createTabs(); for (int i=0; itabWidget->count(); ++i) { @@ -184,19 +186,26 @@ ModInfoDialog::ModInfoDialog( ModInfoDialog::~ModInfoDialog() = default; +template +std::unique_ptr createTab(ModInfoDialog& d, int index) +{ + return std::make_unique(ModInfoDialogTabContext( + *d.m_core, *d.m_plugin, &d, d.ui.get(), index, d.m_mod, d.getOrigin())); +} + std::vector ModInfoDialog::createTabs() { std::vector v; - v.push_back(createTab(TAB_TEXTFILES)); - v.push_back(createTab(TAB_INIFILES)); - v.push_back(createTab(TAB_IMAGES)); - v.push_back(createTab(TAB_ESPS)); - v.push_back(createTab(TAB_CONFLICTS)); - v.push_back(createTab(TAB_CATEGORIES)); - v.push_back(createTab(TAB_NEXUS)); - v.push_back(createTab(TAB_NOTES)); - v.push_back(createTab(TAB_FILETREE)); + v.push_back(createTab(*this, TAB_TEXTFILES)); + v.push_back(createTab(*this, TAB_INIFILES)); + v.push_back(createTab(*this, TAB_IMAGES)); + v.push_back(createTab(*this, TAB_ESPS)); + v.push_back(createTab(*this, TAB_CONFLICTS)); + v.push_back(createTab(*this, TAB_CATEGORIES)); + v.push_back(createTab(*this, TAB_NEXUS)); + v.push_back(createTab(*this, TAB_NOTES)); + v.push_back(createTab(*this, TAB_FILETREE)); return v; } @@ -218,6 +227,7 @@ int ModInfoDialog::exec() void ModInfoDialog::setMod(ModInfo::Ptr mod) { + Q_ASSERT(mod); m_mod = mod; for (auto& tabInfo : m_tabs) { @@ -584,10 +594,8 @@ void ModInfoDialog::onOriginModified(int originID) void ModInfoDialog::onDeleteShortcut() { - for (auto& tabInfo : m_tabs) { - if (tabInfo.tab->deleteRequested()) { - break; - } + if (auto* tabInfo=currentTab()) { + tabInfo->tab->deleteRequested(); } } @@ -663,7 +671,7 @@ void ModInfoDialog::onTabMoved() void ModInfoDialog::on_nextButton_clicked() { auto mod = m_mainWindow->nextModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } @@ -674,7 +682,7 @@ void ModInfoDialog::on_nextButton_clicked() void ModInfoDialog::on_prevButton_clicked() { auto mod = m_mainWindow->previousModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 899a3eab..9eb00a3b 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -66,7 +66,11 @@ protected: **/ class ModInfoDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + + template + friend std::unique_ptr createTab( + ModInfoDialog& d, int index); public: enum ETabs { @@ -81,30 +85,12 @@ public: TAB_FILETREE }; - /** - * @brief constructor - * - * @param modInfo info structure about the mod to display - * @param parent parend widget - **/ - ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); + ModInfoDialog( + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod); ~ModInfoDialog(); - /** - * @brief retrieve the (user-modified) version of the mod - * - * @return the (user-modified) version of the mod - **/ - QString getModVersion() const; - - /** - * @brief retrieve the (user-modified) mod id - * - * @return the (user-modified) id of the mod - **/ - const int getModID() const; - void setMod(ModInfo::Ptr mod); void setMod(const QString& name); void setTab(ETabs id); @@ -166,12 +152,6 @@ private: void onOriginModified(int originID); void onTabChanged(); void onTabMoved(); - - template - std::unique_ptr createTab(int index) - { - return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); - } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 8ffded59..c61e248e 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -3,10 +3,8 @@ #include "categories.h" #include "modinfo.h" -CategoriesTab::CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id) +CategoriesTab::CategoriesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -30,7 +28,7 @@ void CategoriesTab::update() clear(); add( - CategoryFactory::instance(), mod()->getCategories(), + CategoryFactory::instance(), mod().getCategories(), ui->categories->invisibleRootItem(), 0); updatePrimary(); @@ -81,7 +79,7 @@ void CategoriesTab::updatePrimary() { ui->primaryCategories->clear(); - int primaryCategory = mod()->getPrimaryCategory(); + int primaryCategory = mod().getPrimaryCategory(); addChecked(ui->categories->invisibleRootItem()); @@ -111,7 +109,7 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) for (int i = 0; i < currentNode->childCount(); ++i) { QTreeWidgetItem *childNode = currentNode->child(i); - mod()->setCategory( + mod().setCategory( childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); save(childNode); @@ -134,6 +132,6 @@ void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) void CategoriesTab::onPrimaryChanged(int index) { if (index != -1) { - mod()->setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); + mod().setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); } } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 392023e7..c8b52fec 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -5,9 +5,7 @@ class CategoryFactory; class CategoriesTab : public ModInfoDialogTab { public: - CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + CategoriesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index a3383a50..511d48ad 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -382,11 +382,9 @@ void for_each_in_selection(QTreeView* tree, F&& f) } -ConflictsTab::ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_general(this, ui, oc), m_advanced(this, ui, oc) +ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(cx), // don't move, cx is used again + m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core) { connect( &m_general, &GeneralConflictsTab::modOpen, @@ -872,7 +870,7 @@ bool GeneralConflictsTab::update() int numOverwritten = 0; if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { // careful: these two strings are moved into createXItem() below @@ -1085,7 +1083,7 @@ void AdvancedConflictsTab::update() clear(); if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); const auto& files = m_tab->origin()->getFiles(); m_model->reserve(files.size()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3fa12231..a77c2ac9 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -103,9 +103,7 @@ class ConflictsTab : public ModInfoDialogTab Q_OBJECT; public: - ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ConflictsTab(ModInfoDialogTabContext cx); void update() override; void clear() override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 8dceaa31..fba5d39a 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -224,11 +224,9 @@ private: -ESPsTab::ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) +ESPsTab::ESPsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), + m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) { ui->inactiveESPList->setModel(m_inactiveModel); ui->activeESPList->setModel(m_activeModel); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index 217863c6..b128f279 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -11,9 +11,7 @@ class ESPsTab : public ModInfoDialogTab Q_OBJECT; public: - ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ESPsTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 6690dd2f..35480e2c 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -13,10 +13,8 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; -FileTreeTab::FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_fs(nullptr) +FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); @@ -58,7 +56,7 @@ void FileTreeTab::clear() void FileTreeTab::update() { - const auto rootPath = mod()->absolutePath(); + const auto rootPath = mod().absolutePath(); m_fs->setRootPath(rootPath); ui->filetree->setRootIndex(m_fs->index(rootPath)); @@ -139,7 +137,7 @@ void FileTreeTab::onPreview() return; } - core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); + core().previewFile(parentWidget(), mod().name(), m_fs->filePath(selection)); } void FileTreeTab::onExplore() @@ -149,7 +147,7 @@ void FileTreeTab::onExplore() if (selection.isValid()) { shell::ExploreFile(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } } @@ -205,7 +203,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 9f5206b9..f9fa62d4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -6,9 +6,7 @@ class FileTreeTab : public ModInfoDialogTab { public: - FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + FileTreeTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 307fa8e8..cf33f9f5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -35,12 +35,9 @@ QString dimensionString(const QSize& s) } -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), - m_ddsAvailable(false), m_ddsEnabled(false) +ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_image(new ScalableImage), + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d22a6ab2..8d9b965b 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -314,9 +314,7 @@ class ImagesTab : public ModInfoDialogTab friend class ImagesTabHelpers::ThumbnailsWidget; public: - ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ImagesTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 8c8ce55a..81546f58 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -14,11 +14,8 @@ bool isValidModID(int id) return (id > 0); } -NexusTab::NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), - m_loading(false) +NexusTab::NexusTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -70,9 +67,9 @@ void NexusTab::update() clear(); - ui->modID->setText(QString("%1").arg(mod()->getNexusID())); + ui->modID->setText(QString("%1").arg(mod().getNexusID())); - QString gameName = mod()->getGameName(); + QString gameName = mod().getGameName(); ui->sourceGame->addItem( core().managedGame()->gameName(), core().managedGame()->gameShortName()); @@ -100,10 +97,10 @@ void NexusTab::update() [&](const QUrl& url){ shell::OpenLink(url); }); ui->endorse->setEnabled( - (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || - (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); + (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || + (mod().endorsedState() == ModInfo::ENDORSED_NEVER)); - setHasData(mod()->getNexusID() >= 0); + setHasData(mod().getNexusID() >= 0); } void NexusTab::firstActivation() @@ -128,10 +125,10 @@ bool NexusTab::usesOriginFiles() const void NexusTab::updateVersionColor() { - if (mod()->getVersion() != mod()->getNewestVersion()) { + if (mod().getVersion() != mod().getNewestVersion()) { ui->version->setStyleSheet("color: red"); ui->version->setToolTip(tr("Current Version: %1").arg( - mod()->getNewestVersion().canonicalString())); + mod().getNewestVersion().canonicalString())); } else { ui->version->setStyleSheet("color: green"); ui->version->setToolTip(tr("No update available")); @@ -140,11 +137,11 @@ void NexusTab::updateVersionColor() void NexusTab::updateWebpage() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); ui->visitNexus->setToolTip(nexusLink); refreshData(modID); @@ -152,19 +149,19 @@ void NexusTab::updateWebpage() onModChanged(); } - ui->version->setText(mod()->getVersion().displayString()); - ui->hasCustomURL->setChecked(mod()->hasCustomURL()); - ui->customURL->setText(mod()->getCustomURL()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + ui->version->setText(mod().getVersion().displayString()); + ui->hasCustomURL->setChecked(mod().hasCustomURL()); + ui->customURL->setText(mod().getCustomURL()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); updateTracking(); } void NexusTab::updateTracking() { - if (mod()->trackedState() == ModInfo::TRACKED_TRUE) { + if (mod().trackedState() == ModInfo::TRACKED_TRUE) { ui->track->setChecked(true); ui->track->setText(tr("Tracked")); } else { @@ -185,7 +182,7 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { if (isValidModID(modID) && !m_requestStarted) { - if (mod()->updateNXMInfo()) { + if (mod().updateNXMInfo()) { ui->browser->setHtml(""); return true; } @@ -198,7 +195,7 @@ void NexusTab::onModChanged() { m_requestStarted = false; - const QString nexusDescription = mod()->getNexusDescription(); + const QString nexusDescription = mod().getNexusDescription(); QString descriptionAsHTML = R"( @@ -247,12 +244,12 @@ void NexusTab::onModIDChanged() return; } - const int oldID = mod()->getNexusID(); + const int oldID = mod().getNexusID(); const int newID = ui->modID->text().toInt(); if (oldID != newID){ - mod()->setNexusID(newID); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setNexusID(newID); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); ui->browser->page()->setHtml(""); @@ -270,9 +267,9 @@ void NexusTab::onSourceGameChanged() for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { - mod()->setGameName(game->gameShortName()); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); - refreshData(mod()->getNexusID()); + mod().setGameName(game->gameShortName()); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + refreshData(mod().getNexusID()); return; } } @@ -285,16 +282,16 @@ void NexusTab::onVersionChanged() } MOBase::VersionInfo version(ui->version->text()); - mod()->setVersion(version); + mod().setVersion(version); updateVersionColor(); } void NexusTab::onRefreshBrowser() { - const auto modID = mod()->getNexusID(); + const auto modID = mod().getNexusID(); if (isValidModID(modID)) { - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -303,11 +300,11 @@ void NexusTab::onRefreshBrowser() void NexusTab::onVisitNexus() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); shell::OpenLink(QUrl(nexusLink)); } @@ -315,12 +312,16 @@ void NexusTab::onVisitNexus() void NexusTab::onEndorse() { - core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()]{ m->endorse(true); }); } void NexusTab::onTrack() { - core().loggedInAction(parentWidget(), [m=mod()] { + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()] { if (m->trackedState() == ModInfo::TRACKED_TRUE) { m->track(false); } else { @@ -335,9 +336,9 @@ void NexusTab::onCustomURLToggled() return; } - mod()->setHasCustomURL(ui->hasCustomURL->isChecked()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); + mod().setHasCustomURL(ui->hasCustomURL->isChecked()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); } void NexusTab::onCustomURLChanged() @@ -346,13 +347,13 @@ void NexusTab::onCustomURLChanged() return; } - mod()->setCustomURL(ui->customURL->text()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + mod().setCustomURL(ui->customURL->text()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); } void NexusTab::onVisitCustomURL() { - const auto url = mod()->parseCustomURL(); + const auto url = mod().parseCustomURL(); if (url.isValid()) { shell::OpenLink(url); } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 930c1ffc..6478375b 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -33,9 +33,7 @@ signals: class NexusTab : public ModInfoDialogTab { public: - NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + NexusTab(ModInfoDialogTabContext cx); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 0468b405..554df6df 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -3,11 +3,9 @@ #include "texteditor.h" #include "directoryentry.h" -ModInfoDialogTab::ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabID(id), m_hasData(false), m_firstActivation(true) +ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : + ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) { } @@ -82,8 +80,15 @@ void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) m_origin = origin; } -ModInfo::Ptr ModInfoDialogTab::mod() const +ModInfo& ModInfoDialogTab::mod() const { + Q_ASSERT(m_mod); + return *m_mod; +} + +ModInfo::Ptr ModInfoDialogTab::modPtr() const +{ + Q_ASSERT(m_mod); return m_mod; } @@ -143,10 +148,8 @@ void ModInfoDialogTab::setFocus() } -NotesTab::NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) +NotesTab::NotesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -161,8 +164,8 @@ void NotesTab::clear() void NotesTab::update() { - const auto comments = mod()->comments(); - const auto notes = mod()->notes(); + const auto comments = mod().comments(); + const auto notes = mod().notes(); ui->comments->setText(comments); ui->notes->setText(notes); @@ -176,7 +179,7 @@ bool NotesTab::canHandleSeparators() const void NotesTab::onComments() { - mod()->setComments(ui->comments->text()); + mod().setComments(ui->comments->text()); checkHasData(); } @@ -184,9 +187,9 @@ void NotesTab::onNotes() { // Avoid saving html stub if notes field is empty. if (ui->notes->toPlainText().isEmpty()) { - mod()->setNotes({}); + mod().setNotes({}); } else { - mod()->setNotes(ui->notes->toHtml()); + mod().setNotes(ui->notes->toHtml()); } checkHasData(); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 3f98314a..eb574de0 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -10,6 +10,33 @@ namespace Ui { class ModInfoDialog; } class Settings; class OrganizerCore; +// helper struct to avoid passing too much stuff to tab constructors +// +struct ModInfoDialogTabContext +{ + OrganizerCore& core; + PluginContainer& plugin; + QWidget* parent; + Ui::ModInfoDialog* ui; + int id; + ModInfo::Ptr mod; + MOShared::FilesOrigin* origin; + + ModInfoDialogTabContext( + OrganizerCore& core, + PluginContainer& plugin, + QWidget* parent, + Ui::ModInfoDialog* ui, + int id, + ModInfo::Ptr mod, + MOShared::FilesOrigin* origin) : + core(core), plugin(plugin), parent(parent), ui(ui), id(id), + mod(mod), origin(origin) + { + } +}; + + // base class for all tabs in the mod info dialog // // when the dialog is opened or when next/previous is clicked, the sequence is: @@ -38,7 +65,7 @@ class OrganizerCore; // tabs can call emitModOpen() to request showing a different mod // // hasDataChanged() should be called when a tab goes from having data to being -// empty or vice versa; this will update the tab text color +// empty or vice versa; this will update the tab text colour // class ModInfoDialogTab : public QObject { @@ -107,20 +134,75 @@ public: // virtual void firstActivation(); + // called when closing the dialog, can return false to stop the dialog from + // closing + // + // this is typically used by tabs that require manual saving, like text files; + // tabs that refuse to close should focus themselves before showing whatever + // confirmation they have // virtual bool canClose(); + + + // called after the dialog is closed, tabs should save whatever UI state they + // want + // virtual void saveState(Settings& s); + + // called before the is shown, tabs should restore whatever UI state they + // saved in saveState() + // virtual void restoreState(const Settings& s); + + // called on the selected tab when the Delete key is pressed on the keyboard; + // tabs _must_ check which widget currently has focus to decide whether this + // should be handled or not; do not blindly delete stuff when this is called + // + // if the delete request was handled, this should return true + // virtual bool deleteRequested(); + + // return true if this tab can handle a separator mod, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // + // if a tab can show meaningful information about a separator (like + // categories or notes), it should return true + // virtual bool canHandleSeparators() const; + + // return true if this tab can handle unmanaged mods, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // virtual bool canHandleUnmanaged() const; + + // return true if this tab uses the files from the mod's origin, defaults to + // false + // + // tabs that do not care about the files inside a mod should return false, + // such as the notes or categories tab + // + // mods that return true will be updated anytime a tab calls + // emitOriginModifed() + // virtual bool usesOriginFiles() const; - ModInfo::Ptr mod() const; + + // returns the currently selected mod + // + ModInfo& mod() const; + + // returns the currently selected mod, can never be empty + // + ModInfo::Ptr modPtr() const; + + // returns the origin of the selected mod; this can be null for mods that + // don't have an origin, like deactivated mods + // MOShared::FilesOrigin* origin() const; + int tabID() const; bool hasData() const; @@ -133,9 +215,7 @@ signals: protected: Ui::ModInfoDialog* ui; - ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ModInfoDialogTab(ModInfoDialogTabContext cx); OrganizerCore& core(); PluginContainer& plugin(); @@ -186,9 +266,7 @@ private: class NotesTab : public ModInfoDialogTab { public: - NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + NotesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index bc44ee3e..7a09fa4e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -99,10 +99,10 @@ private: GenericFilesTab::GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : - ModInfoDialogTab(oc, plugin, parent, ui, id), + ModInfoDialogTabContext cx, + QListView* list, QSplitter* sp, + TextEditor* e, QLineEdit* filter) : + ModInfoDialogTab(std::move(cx)), m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); @@ -208,13 +208,10 @@ void GenericFilesTab::select(const QModelIndex& index) } -TextFilesTab::TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, - ui->textFileEditor, ui->textFileFilter) +TextFilesTab::TextFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->textFileList, cx.ui->tabTextSplitter, + cx.ui->textFileEditor, cx.ui->textFileFilter) { } @@ -231,13 +228,10 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, - ui->iniFileEditor, ui->iniFileFilter) +IniFilesTab::IniFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->iniFileList, cx.ui->tabIniSplitter, + cx.ui->iniFileEditor, cx.ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index ffe49904..725ac999 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -30,9 +30,9 @@ protected: FilterWidget m_filter; GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); + ModInfoDialogTabContext cx, + QListView* list, QSplitter* splitter, + TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -45,9 +45,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + TextFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -57,9 +55,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + IniFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From 4aa59cdc7dd779c7e864a1c4e96c6b52c61879ff Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:35:33 -0400 Subject: added modinfodialogfwd.h, mostly for the enum that's used in various places renamed ETabs to ModInfoTabIDs and changed all ints to use the enum instead added ModInfoPtr to avoid having to include modinfo.h just to get ModInfo::Ptr --- src/CMakeLists.txt | 2 ++ src/iuserinterface.h | 5 ++-- src/mainwindow.cpp | 37 +++++++++++++++------------- src/mainwindow.h | 7 +++--- src/modinfodialog.cpp | 60 ++++++++++++++++++++-------------------------- src/modinfodialog.h | 50 ++++++-------------------------------- src/modinfodialogfwd.h | 50 ++++++++++++++++++++++++++++++++++++++ src/modinfodialognexus.cpp | 2 +- src/modinfodialognexus.h | 2 +- src/modinfodialogtab.cpp | 7 +++--- src/modinfodialogtab.h | 20 ++++++++-------- src/organizercore.cpp | 8 +++---- 12 files changed, 132 insertions(+), 118 deletions(-) create mode 100644 src/modinfodialogfwd.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1d27f444..929ee296 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,7 @@ SET(organizer_HDRS modinfodialogconflicts.h modinfodialogesps.h modinfodialogfiletree.h + modinfodialogfwd.h modinfodialogimages.h modinfodialognexus.h modinfodialogtab.h @@ -379,6 +380,7 @@ set(modinfo\\dialog modinfodialogconflicts modinfodialogesps modinfodialogfiletree + modinfodialogfwd modinfodialogimages modinfodialognexus modinfodialogtab diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 034fa029..bba8de2b 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -2,7 +2,7 @@ #define IUSERINTERFACE_H -#include "modinfo.h" +#include "modinfodialogfwd.h" #include "ilockedwaitingforprocess.h" #include #include @@ -29,7 +29,8 @@ public: virtual bool closeWindow() = 0; virtual void setWindowEnabled(bool enabled) = 0; - virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; + virtual void displayModInformation( + ModInfoPtr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) = 0; virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7ab555fa..a596c542 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3192,7 +3192,8 @@ void MainWindow::overwriteClosed(int) } -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) +void MainWindow::displayModInformation( + ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) { if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); @@ -3227,8 +3228,8 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); //Open the tab first if we want to use the standard indexes of the tabs. - if (tab != -1) { - dialog.setTab(ModInfoDialog::ETabs(tab)); + if (tabID != ModInfoTabIDs::None) { + dialog.setTab(tabID); } dialog.restoreState(m_OrganizerCore.settings()); @@ -3247,7 +3248,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, m_OrganizerCore.modList()->modInfoChanged(modInfo); } - if (m_OrganizerCore.currentProfile()->modEnabled(index) + if (m_OrganizerCore.currentProfile()->modEnabled(modIndex) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); @@ -3258,7 +3259,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure() , modInfo->name() - , m_OrganizerCore.currentProfile()->getModPriority(index) + , m_OrganizerCore.currentProfile()->getModPriority(modIndex) , modInfo->absolutePath() , modInfo->stealFiles() , modInfo->archives()); @@ -3347,7 +3348,7 @@ ModInfo::Ptr MainWindow::previousModInList() return {}; } -void MainWindow::displayModInformation(const QString &modName, int tab) +void MainWindow::displayModInformation(const QString &modName, ModInfoTabIDs tabID) { unsigned int index = ModInfo::getIndex(modName); if (index == UINT_MAX) { @@ -3356,14 +3357,14 @@ void MainWindow::displayModInformation(const QString &modName, int tab) } ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - displayModInformation(modInfo, index, tab); + displayModInformation(modInfo, index, tabID); } -void MainWindow::displayModInformation(int row, int tab) +void MainWindow::displayModInformation(int row, ModInfoTabIDs tabID) { ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - displayModInformation(modInfo, row, tab); + displayModInformation(modInfo, row, tabID); } @@ -4048,16 +4049,18 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) try { m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); sourceIdx.column(); - int tab = -1; + + auto tab = ModInfoTabIDs::None; + switch (sourceIdx.column()) { - case ModList::COL_NOTES: tab = ModInfoDialog::TAB_NOTES; break; - case ModList::COL_VERSION: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_MODID: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_GAME: tab = ModInfoDialog::TAB_NEXUS; break; - case ModList::COL_CATEGORY: tab = ModInfoDialog::TAB_CATEGORIES; break; - case ModList::COL_FLAGS: tab = ModInfoDialog::TAB_CONFLICTS; break; - default: tab = -1; + case ModList::COL_NOTES: tab = ModInfoTabIDs::Notes; break; + case ModList::COL_VERSION: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_MODID: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_GAME: tab = ModInfoTabIDs::Nexus; break; + case ModList::COL_CATEGORY: tab = ModInfoTabIDs::Categories; break; + case ModList::COL_FLAGS: tab = ModInfoTabIDs::Conflicts; break; } + displayModInformation(sourceIdx.row(), tab); // workaround to cancel the editor that might have opened because of // selection-click diff --git a/src/mainwindow.h b/src/mainwindow.h index f204211e..00f15a2b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -151,7 +151,8 @@ public: virtual void disconnectPlugins(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + void displayModInformation( + ModInfo::Ptr modInfo, unsigned int modIndex, ModInfoTabIDs tabID) override; bool confirmExit(); @@ -235,7 +236,7 @@ private: QList findFileInfos(const QString &path, const std::function &filter) const; bool modifyExecutablesDialog(); - void displayModInformation(int row, int tab = -1); + void displayModInformation(int row, ModInfoTabIDs tab=ModInfoTabIDs::None); void testExtractBSA(int modIndex); void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); @@ -551,7 +552,7 @@ private slots: void editCategories(); void deselectFilters(); - void displayModInformation(const QString &modName, int tab); + void displayModInformation(const QString &modName, ModInfoTabIDs tabID); void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4ef010e4..c8ffa35b 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -131,7 +131,7 @@ ModInfoDialog::ModInfoDialog( ModInfo::Ptr mod) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), - m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), + m_core(core), m_plugin(plugin), m_initialTab(ModInfoTabIDs::None), m_arrangingTabs(false) { ui->setupUi(this); @@ -155,16 +155,11 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::originModified, - [this, i](int originID) { - onOriginModified(originID); - }); + [this](int originID){ onOriginModified(originID); }); connect( tabInfo.tab.get(), &ModInfoDialogTab::modOpen, - [&](const QString& name){ - setMod(name); - update(); - }); + [&](const QString& name){ setMod(name); update(); }); connect( tabInfo.tab.get(), &ModInfoDialogTab::hasDataChanged, @@ -172,12 +167,7 @@ ModInfoDialog::ModInfoDialog( connect( tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, - [&, i=static_cast(i)] - { - if (i < m_tabs.size()) { - switchToTab(ETabs(m_tabs[i].tab->tabID())); - } - }); + [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); @@ -187,32 +177,32 @@ ModInfoDialog::ModInfoDialog( ModInfoDialog::~ModInfoDialog() = default; template -std::unique_ptr createTab(ModInfoDialog& d, int index) +std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) { return std::make_unique(ModInfoDialogTabContext( - *d.m_core, *d.m_plugin, &d, d.ui.get(), index, d.m_mod, d.getOrigin())); + *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); } std::vector ModInfoDialog::createTabs() { std::vector v; - v.push_back(createTab(*this, TAB_TEXTFILES)); - v.push_back(createTab(*this, TAB_INIFILES)); - v.push_back(createTab(*this, TAB_IMAGES)); - v.push_back(createTab(*this, TAB_ESPS)); - v.push_back(createTab(*this, TAB_CONFLICTS)); - v.push_back(createTab(*this, TAB_CATEGORIES)); - v.push_back(createTab(*this, TAB_NEXUS)); - v.push_back(createTab(*this, TAB_NOTES)); - v.push_back(createTab(*this, TAB_FILETREE)); + v.push_back(createTab(*this, ModInfoTabIDs::TextFiles)); + v.push_back(createTab(*this, ModInfoTabIDs::IniFiles)); + v.push_back(createTab(*this, ModInfoTabIDs::Images)); + v.push_back(createTab(*this, ModInfoTabIDs::Esps)); + v.push_back(createTab(*this, ModInfoTabIDs::Conflicts)); + v.push_back(createTab(*this, ModInfoTabIDs::Categories)); + v.push_back(createTab(*this, ModInfoTabIDs::Nexus)); + v.push_back(createTab(*this, ModInfoTabIDs::Notes)); + v.push_back(createTab(*this, ModInfoTabIDs::Filetree)); return v; } int ModInfoDialog::exec() { - const auto selectFirst = (m_initialTab == -1); + const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); update(true); @@ -252,7 +242,7 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(ETabs id) +void ModInfoDialog::setTab(ModInfoTabIDs id) { if (!isVisible()) { m_initialTab = id; @@ -288,9 +278,9 @@ void ModInfoDialog::update(bool firstTime) updateTabs(); - if (m_initialTab >= 0) { + if (m_initialTab != ModInfoTabIDs::None) { switchToTab(m_initialTab); - m_initialTab = ETabs(-1); + m_initialTab = ModInfoTabIDs::None; } if (ui->tabWidget->currentIndex() == oldTab) { @@ -335,11 +325,11 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) // remember selection const int selIndex = ui->tabWidget->currentIndex(); - ETabs sel = ETabs(-1); + auto sel = ModInfoTabIDs::None; for (const auto& tabInfo : m_tabs) { if (tabInfo.realPos == selIndex) { - sel = ETabs(tabInfo.tab->tabID()); + sel = tabInfo.tab->tabID(); break; } } @@ -420,7 +410,7 @@ void ModInfoDialog::setTabsColors() } } -void ModInfoDialog::switchToTab(ETabs id) +void ModInfoDialog::switchToTab(ModInfoTabIDs id) { for (const auto& tabInfo : m_tabs) { if (tabInfo.tab->tabID() == id) { @@ -429,7 +419,8 @@ void ModInfoDialog::switchToTab(ETabs id) } } - qDebug() << "can't switch to tab " << id << ", not available"; + qDebug() + << "can't switch to tab ID " << static_cast(id) << ", not available"; } MOShared::FilesOrigin* ModInfoDialog::getOrigin() @@ -525,7 +516,8 @@ std::vector ModInfoDialog::getOrderedTabNames() const return v; } -void ModInfoDialog::reAddTabs(const std::vector& visibility, ETabs sel) +void ModInfoDialog::reAddTabs( + const std::vector& visibility, ModInfoTabIDs sel) { Q_ASSERT(visibility.size() == m_tabs.size()); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 9eb00a3b..effb5d98 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" #include "filerenamer.h" +#include "modinfodialogfwd.h" namespace Ui { class ModInfoDialog; } namespace MOShared { class FilesOrigin; } @@ -34,32 +35,6 @@ class Settings; class ModInfoDialogTab; class MainWindow; -bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); -bool canOpenFile(bool isArchive, const QString& filename); -bool canExploreFile(bool isArchive, const QString& filename); -bool canHideFile(bool isArchive, const QString& filename); -bool canUnhideFile(bool isArchive, const QString& filename); - -FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); -FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); - -int naturalCompare(const QString& a, const QString& b); - - -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const - { - QStyledItemDelegate::initStyleOption(o, i); - o->textElideMode = Qt::ElideLeft; - } -}; - - /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system @@ -70,21 +45,9 @@ class ModInfoDialog : public MOBase::TutorableDialog template friend std::unique_ptr createTab( - ModInfoDialog& d, int index); + ModInfoDialog& d, ModInfoTabIDs index); public: - enum ETabs { - TAB_TEXTFILES, - TAB_INIFILES, - TAB_IMAGES, - TAB_ESPS, - TAB_CONFLICTS, - TAB_CATEGORIES, - TAB_NEXUS, - TAB_NOTES, - TAB_FILETREE - }; - ModInfoDialog( MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, ModInfo::Ptr mod); @@ -93,7 +56,8 @@ public: void setMod(ModInfo::Ptr mod); void setMod(const QString& name); - void setTab(ETabs id); + + void setTab(ModInfoTabIDs id); int exec() override; @@ -130,7 +94,7 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; - ETabs m_initialTab; + ModInfoTabIDs m_initialTab; bool m_arrangingTabs; std::vector createTabs(); @@ -144,8 +108,8 @@ private: void updateTabs(bool becauseOriginChanged=false); void feedFiles(bool becauseOriginChanged); void setTabsColors(); - void switchToTab(ETabs id); - void reAddTabs(const std::vector& visibility, ETabs sel); + void switchToTab(ModInfoTabIDs id); + void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); std::vector getOrderedTabNames() const; bool tryClose(); diff --git a/src/modinfodialogfwd.h b/src/modinfodialogfwd.h new file mode 100644 index 00000000..9ede766f --- /dev/null +++ b/src/modinfodialogfwd.h @@ -0,0 +1,50 @@ +#ifndef MODINFODIALOGFWD_H +#define MODINFODIALOGFWD_H + +#include "filerenamer.h" + +class ModInfo; +using ModInfoPtr = QSharedPointer; + +enum class ModInfoTabIDs +{ + None = -1, + TextFiles = 0, + IniFiles, + Images, + Esps, + Conflicts, + Categories, + Nexus, + Notes, + Filetree +}; + +class PluginContainer; + +bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); +bool canOpenFile(bool isArchive, const QString& filename); +bool canExploreFile(bool isArchive, const QString& filename); +bool canHideFile(bool isArchive, const QString& filename); +bool canUnhideFile(bool isArchive, const QString& filename); + +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); + +int naturalCompare(const QString& a, const QString& b); + + +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + +#endif // MODINFODIALOGFWD_H diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 81546f58..04683c89 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -108,7 +108,7 @@ void NexusTab::firstActivation() updateWebpage(); } -void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void NexusTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) { cleanup(); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 6478375b..7f894dbf 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -40,7 +40,7 @@ public: void clear() override; void update() override; void firstActivation() override; - void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) override; bool usesOriginFiles() const override; private: diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 554df6df..9748d059 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -2,6 +2,7 @@ #include "ui_modinfodialog.h" #include "texteditor.h" #include "directoryentry.h" +#include "modinfo.h" ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), @@ -74,7 +75,7 @@ bool ModInfoDialogTab::usesOriginFiles() const return true; } -void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void ModInfoDialogTab::setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin) { m_mod = mod; m_origin = origin; @@ -86,7 +87,7 @@ ModInfo& ModInfoDialogTab::mod() const return *m_mod; } -ModInfo::Ptr ModInfoDialogTab::modPtr() const +ModInfoPtr ModInfoDialogTab::modPtr() const { Q_ASSERT(m_mod); return m_mod; @@ -97,7 +98,7 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } -int ModInfoDialogTab::tabID() const +ModInfoTabIDs ModInfoDialogTab::tabID() const { return m_tabID; } diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index eb574de0..283d9e73 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -1,7 +1,7 @@ #ifndef MODINFODIALOGTAB_H #define MODINFODIALOGTAB_H -#include "modinfo.h" +#include "modinfodialogfwd.h" #include namespace MOShared { class FilesOrigin; } @@ -18,8 +18,8 @@ struct ModInfoDialogTabContext PluginContainer& plugin; QWidget* parent; Ui::ModInfoDialog* ui; - int id; - ModInfo::Ptr mod; + ModInfoTabIDs id; + ModInfoPtr mod; MOShared::FilesOrigin* origin; ModInfoDialogTabContext( @@ -27,8 +27,8 @@ struct ModInfoDialogTabContext PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, - int id, - ModInfo::Ptr mod, + ModInfoTabIDs id, + ModInfoPtr mod, MOShared::FilesOrigin* origin) : core(core), plugin(plugin), parent(parent), ui(ui), id(id), mod(mod), origin(origin) @@ -96,7 +96,7 @@ public: // derived classes can override this to connect to events on the mod for // examples (see NexusTab), but must call the base class implementation // - virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void setMod(ModInfoPtr mod, MOShared::FilesOrigin* origin); // this tab should clear its user interface; clear() will always be called // before feedFile() and update() @@ -195,7 +195,7 @@ public: // returns the currently selected mod, can never be empty // - ModInfo::Ptr modPtr() const; + ModInfoPtr modPtr() const; // returns the origin of the selected mod; this can be null for mods that // don't have an origin, like deactivated mods @@ -203,7 +203,7 @@ public: MOShared::FilesOrigin* origin() const; - int tabID() const; + ModInfoTabIDs tabID() const; bool hasData() const; signals: @@ -249,9 +249,9 @@ private: OrganizerCore& m_core; PluginContainer& m_plugin; QWidget* m_parent; - ModInfo::Ptr m_mod; + ModInfoPtr m_mod; MOShared::FilesOrigin* m_origin; - int m_tabID; + ModInfoTabIDs m_tabID; bool m_hasData; bool m_firstActivation; diff --git a/src/organizercore.cpp b/src/organizercore.cpp index e073924b..99ddda1d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -966,8 +966,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation(modInfo, modIndex, - ModInfoDialog::TAB_INIFILES); + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); m_DownloadManager.markInstalled(fileName); @@ -1033,8 +1033,8 @@ void OrganizerCore::installDownload(int index) "want to configure them now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_UserInterface->displayModInformation(modInfo, modIndex, - ModInfoDialog::TAB_INIFILES); + m_UserInterface->displayModInformation( + modInfo, modIndex, ModInfoTabIDs::IniFiles); } m_ModInstalled(modName); -- cgit v1.3.1 From d9a7ef636bc111f71de697caed89171335a5f3e3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 13:21:48 -0400 Subject: reordering tabs, then switching to a mod with a different tab set would discard the changes comments, moving some stuff around --- src/mainwindow.cpp | 2 +- src/modinfodialog.cpp | 163 ++++++++++++++++++++++++++----------------------- src/modinfodialog.h | 134 +++++++++++++++++++++++++++++++++++----- src/modinfodialog.ui | 6 +- src/modinfodialogtab.h | 61 +++++++++++++++++- 5 files changed, 268 insertions(+), 98 deletions(-) (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a596c542..7681b482 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3229,7 +3229,7 @@ void MainWindow::displayModInformation( //Open the tab first if we want to use the standard indexes of the tabs. if (tabID != ModInfoTabIDs::None) { - dialog.setTab(tabID); + dialog.selectTab(tabID); } dialog.restoreState(m_OrganizerCore.settings()); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c8ffa35b..adbd22df 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -170,8 +170,11 @@ ModInfoDialog::ModInfoDialog( [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } - connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabChanged(); }); + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); + connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); + connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); + connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); } ModInfoDialog::~ModInfoDialog() = default; @@ -242,7 +245,7 @@ void ModInfoDialog::setMod(const QString& name) setMod(mod); } -void ModInfoDialog::setTab(ModInfoTabIDs id) +void ModInfoDialog::selectTab(ModInfoTabIDs id) { if (!isVisible()) { m_initialTab = id; @@ -323,6 +326,10 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) return; } + // something changed; save the current order (if necessary) because some tabs + // will be removed and others added + saveTabOrder(Settings::instance()); + // remember selection const int selIndex = ui->tabWidget->currentIndex(); auto sel = ModInfoTabIDs::None; @@ -337,6 +344,68 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) reAddTabs(visibility, sel); } +void ModInfoDialog::reAddTabs( + const std::vector& visibility, ModInfoTabIDs sel) +{ + Q_ASSERT(visibility.size() == m_tabs.size()); + + // ordered tab names from settings + const auto orderedNames = getOrderedTabNames(); + + bool canSort = true; + + // gathering visible tabs + std::vector visibleTabs; + for (std::size_t i=0; iobjectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + if (itor == orderedNames.end()) { + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; + } + } + } + } + + // sorting tabs + if (canSort) { + std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ + auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); + auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + + // this was checked above + Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); + + return (aItor < bItor); + }); + } + + + // removing all tabs + ui->tabWidget->clear(); + + // reset real positions + for (auto& tabInfo : m_tabs) { + tabInfo.realPos = -1; + } + + // add visible tabs + for (std::size_t i=0; i(i); + ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + + if (tabInfo.tab->tabID() == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } + } +} + void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); @@ -440,10 +509,7 @@ MOShared::FilesOrigin* ModInfoDialog::getOrigin() void ModInfoDialog::saveState(Settings& s) const { - const auto tabState = saveTabState(); - if (!tabState.isEmpty()) { - s.directInterface().setValue("mod_info_tab_order", tabState); - } + saveTabOrder(s); // remove 2.2.0 settings s.directInterface().remove("mod_info_tabs"); @@ -466,21 +532,24 @@ void ModInfoDialog::restoreState(const Settings& s) } } -QString ModInfoDialog::saveTabState() const +void ModInfoDialog::saveTabOrder(Settings& s) const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible - return {}; + return; } - QString result; - QTextStream stream(&result); + QString names; for (int i=0; itabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName() << " "; + if (!names.isEmpty()) { + names += " "; + } + + names += ui->tabWidget->widget(i)->objectName(); } - return result.trimmed(); + s.directInterface().setValue("mod_info_tab_order", names); } std::vector ModInfoDialog::getOrderedTabNames() const @@ -516,68 +585,6 @@ std::vector ModInfoDialog::getOrderedTabNames() const return v; } -void ModInfoDialog::reAddTabs( - const std::vector& visibility, ModInfoTabIDs sel) -{ - Q_ASSERT(visibility.size() == m_tabs.size()); - - // ordered tab names from settings - const auto orderedNames = getOrderedTabNames(); - - bool canSort = true; - - // gathering visible tabs - std::vector visibleTabs; - for (std::size_t i=0; iobjectName(); - auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); - if (itor == orderedNames.end()) { - qCritical() << "can't sort tabs, '" << objectName << "' not found"; - canSort = false; - } - } - } - } - - // sorting tabs - if (canSort) { - std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ - auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); - auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); - - // this was checked above - Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); - - return (aItor < bItor); - }); - } - - - // removing all tabs - ui->tabWidget->clear(); - - // reset real positions - for (auto& tabInfo : m_tabs) { - tabInfo.realPos = -1; - } - - // add visible tabs - for (std::size_t i=0; i(i); - ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); - - if (tabInfo.tab->tabID() == sel) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - } - } -} - void ModInfoDialog::onOriginModified(int originID) { emit originModified(originID); @@ -600,7 +607,7 @@ void ModInfoDialog::closeEvent(QCloseEvent* e) } } -void ModInfoDialog::on_closeButton_clicked() +void ModInfoDialog::onCloseButton() { if (tryClose()) { close(); @@ -618,7 +625,7 @@ bool ModInfoDialog::tryClose() return true; } -void ModInfoDialog::onTabChanged() +void ModInfoDialog::onTabSelectionChanged() { if (m_arrangingTabs) { // this can be fired while re-arranging tabs, which happens before mods @@ -660,7 +667,7 @@ void ModInfoDialog::onTabMoved() } } -void ModInfoDialog::on_nextButton_clicked() +void ModInfoDialog::onNextMod() { auto mod = m_mainWindow->nextModInList(); if (!mod || mod == m_mod) { @@ -671,7 +678,7 @@ void ModInfoDialog::on_nextButton_clicked() update(); } -void ModInfoDialog::on_prevButton_clicked() +void ModInfoDialog::onPreviousMod() { auto mod = m_mainWindow->previousModInList(); if (!mod || mod == m_mod) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index effb5d98..035eb6d6 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -43,6 +43,9 @@ class ModInfoDialog : public MOBase::TutorableDialog { Q_OBJECT; + // creates a tab, it's a friend because it uses a bunch of member variables + // to create ModInfoDialogTabContext + // template friend std::unique_ptr createTab( ModInfoDialog& d, ModInfoTabIDs index); @@ -54,37 +57,67 @@ public: ~ModInfoDialog(); - void setMod(ModInfo::Ptr mod); - void setMod(const QString& name); - - void setTab(ModInfoTabIDs id); + // switches to the tab with the given id + // + void selectTab(ModInfoTabIDs id); + // updates all tabs, selects the initial tab and opens the dialog + // int exec() override; + // saves the dialog state and calls saveState() on all tabs + // void saveState(Settings& s) const; + + // restores the dialog state and calls restoreState() on all tabs + // void restoreState(const Settings& s); signals: + // emitted when a tab changes the origin + // void originModified(int originID); protected: + // forwards to tryClose() + // void closeEvent(QCloseEvent* e); -private slots: - void on_closeButton_clicked(); - void on_nextButton_clicked(); - void on_prevButton_clicked(); - private: + // represents a single tab + // struct TabInfo { + // tab implementation std::unique_ptr tab; + + // actual position in the tab bar, updated every time a tab is moved int realPos; + + // widget used by the QTabWidget for this tab + // + // because QTabWidget doesn't support simply hiding tabs, they have to be + // completely removed from the widget when they don't support the current + // mod + // + // therefore, `widget, `caption` and `icon` are remembered so tabs can be + // removed and re-added when navigating between mods + // + // `widget` is also used figure out which tab is where when they're + // re-ordered QWidget* widget; + + // caption for this tab, see `widget` QString caption; + + // icon for this tab, see `widget` QIcon icon; + TabInfo(std::unique_ptr tab); + + // returns whether this tab is part of the tab widget + // bool isVisible() const; }; @@ -94,28 +127,99 @@ private: OrganizerCore* m_core; PluginContainer* m_plugin; std::vector m_tabs; + + // initial tab requested by the main window when the dialog is opened; whether + // the request can be honoured depends on what tabs are present ModInfoTabIDs m_initialTab; + + // set to true when tabs are being removed and re-added while navigating + // between mods; since the current index changes while this is happening, + // onTabSelectionChanged() will be called repeatedly + // + // however, it will check this flag and ignore the event so first activations + // are not fired incorrectly bool m_arrangingTabs; + + // creates all the tabs + // std::vector createTabs(); + + + // sets the currently selected mod; resets first activation, but doesn't + // update anything + // + void setMod(ModInfo::Ptr mod); + + // sets the currently selected mod, if found; forwards to setMod() above + // + void setMod(const QString& name); + + + // returns the currently selected tab, taking re-ordering in to account; + // shouldn't be null, but could be + // TabInfo* currentTab(); - void restoreTabState(const QString& state); - QString saveTabState() const; + + // returns a list of tab object names in their visual order, used by + // saveState() + // + void saveTabOrder(Settings& s) const; + + + // fully updates the dialog; sets the title, the tab visibility and updates + // all the tabs; used when the current mod changes + // + // see setTabsVisibility() for firstTime + // void update(bool firstTime=false); + + // builds the list of visible tabs; if the list is different from what's + // currently displayed, or firstTime is true, forwards to reAddTabs() + void setTabsVisibility(bool firstTime); + + // clears the tab widgets and re-adds the tabs having the visible flag in + // the given vector, following the + void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); + void onDeleteShortcut(); MOShared::FilesOrigin* getOrigin(); - void setTabsVisibility(bool firstTime); void updateTabs(bool becauseOriginChanged=false); void feedFiles(bool becauseOriginChanged); void setTabsColors(); void switchToTab(ModInfoTabIDs id); - void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); std::vector getOrderedTabNames() const; bool tryClose(); - void onOriginModified(int originID); - void onTabChanged(); + + // called when the user clicks the close button; closing the dialog by other + // means ends up in closeEvent(); forwards to tryClose() + // + void onCloseButton(); + + // called when the user clicks the previous button; asks the main window for + // the previous mod in the list and loads it + // + void onPreviousMod(); + + // called when the user clicks the next button; asks the main window for the + // next mod in the list and loads it + // + void onNextMod(); + + // called when the selects a tab; handles first activation + // + void onTabSelectionChanged(); + + // called when the user re-orders tabs; sets the correct TabInfo::realPos for + // all tabs + // void onTabMoved(); + + // called when a tab has modified the origin; emits originModified() and + // updates all the tabs that use origin files + // + void onOriginModified(int originID); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e54b94b3..5b559992 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -1260,7 +1260,7 @@ p, li { white-space: pre-wrap; } - + Previous @@ -1270,7 +1270,7 @@ p, li { white-space: pre-wrap; } - + Next @@ -1293,7 +1293,7 @@ p, li { white-space: pre-wrap; } - + Close diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 283d9e73..c43fa076 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -203,13 +203,29 @@ public: MOShared::FilesOrigin* origin() const; + // return this tab's ID + // ModInfoTabIDs tabID() const; + + // returns whether this tab has data; derived classes should call setHasData() + // bool hasData() const; signals: + // emitted when a tab modified the files in a mod + // void originModified(int originID); + + // emitted when a tab wants to open a mod by name + // void modOpen(QString name); + + // emitted when a tab used to have data and is now empty, or vice versa + // void hasDataChanged(); + + // emitted when a tab wants focus + // void wantsFocus(); protected: @@ -219,15 +235,27 @@ protected: OrganizerCore& core(); PluginContainer& plugin(); - QWidget* parentWidget(); + // emits originModified + // void emitOriginModified(); + + // emits modOpen + // void emitModOpen(QString name); + + // emits hasDataChanged + // void setHasData(bool b); + // emits wantsFocus + // void setFocus(); + + // saves the sate of the given widget in geometry/modinfodialog_[objectname] + // // this needs to be a template because saveState() and restoreState() are // not in QWidget, but they're in various widgets // @@ -237,6 +265,12 @@ protected: s.setValue(settingName(w), w->saveState()); } + // restores the sate of the given widget from + // geometry/modinfodialog_[objectname] + // + // this needs to be a template because saveState() and restoreState() are + // not in QWidget, but they're in various widgets + // template void restoreWidgetState(const QSettings& s, Widget* w) { @@ -246,16 +280,33 @@ protected: } private: + // core OrganizerCore& m_core; + + // plugin PluginContainer& m_plugin; + + // parent widget, used to display modal dialogs QWidget* m_parent; + + // current mod, never null ModInfoPtr m_mod; + + // current mod origin, may be null MOShared::FilesOrigin* m_origin; + + // tab ID ModInfoTabIDs m_tabID; + + // whether the tab has data bool m_hasData; + + // true if the tab has never been selected for the current mod bool m_firstActivation; + // used by saveWidgetState() and restoreWidgetState() + // QString settingName(QWidget* w) { return "geometry/modinfodialog_" + w->objectName(); @@ -263,6 +314,8 @@ private: }; +// the Notes tab +// class NotesTab : public ModInfoDialogTab { public: @@ -270,7 +323,13 @@ public: void clear() override; void update() override; + + // returns true, separators can have notes + // bool canHandleSeparators() const override; + + // returns false, notes don't use files + // bool usesOriginFiles() const override; private: -- cgit v1.3.1 From 971f890826412994a56fc1b37b10d3bf21e1275d Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:36:59 -0400 Subject: some refactoring updateTabs() creates a list of tabs to update instead of checking for each file comments --- src/modinfodialog.cpp | 307 +++++++++++++++++++++++++++--------------- src/modinfodialog.h | 55 ++++++-- src/modinfodialogfiletree.cpp | 1 - 3 files changed, 241 insertions(+), 122 deletions(-) (limited to 'src') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index adbd22df..16690019 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +namespace fs = std::filesystem; const int max_scan_for_context_menu = 50; @@ -140,15 +141,51 @@ ModInfoDialog::ModInfoDialog( connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); setMod(mod); - m_tabs = createTabs(); + createTabs(); - for (int i=0; itabWidget->count(); ++i) { - if (static_cast(i) >= m_tabs.size()) { - qCritical() << "mod info dialog has more tabs than expected"; - break; - } + connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); + connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); + connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); + connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); + connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); +} + +ModInfoDialog::~ModInfoDialog() = default; + +template +std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) +{ + return std::make_unique(ModInfoDialogTabContext( + *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); +} + +void ModInfoDialog::createTabs() +{ + m_tabs.clear(); + + m_tabs.push_back(createTab(*this, ModInfoTabIDs::TextFiles)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::IniFiles)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Images)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Esps)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Conflicts)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Categories)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Nexus)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Notes)); + m_tabs.push_back(createTab(*this, ModInfoTabIDs::Filetree)); + + // check for tabs in the ui not having a corresponding tab in the list + int count = ui->tabWidget->count(); + if (count < 0 || count > static_cast(m_tabs.size())) { + qCritical() << "mod info dialog has more tabs than expected"; + count = static_cast(m_tabs.size()); + } + // for each tab in the widget; connects the widgets with the tab objects + // + for (int i=0; i(i)]; + + // remembering tab information so tabs can be removed and re-added tabInfo.widget = ui->tabWidget->widget(i); tabInfo.caption = ui->tabWidget->tabText(i); tabInfo.icon = ui->tabWidget->tabIcon(i); @@ -169,50 +206,18 @@ ModInfoDialog::ModInfoDialog( tabInfo.tab.get(), &ModInfoDialogTab::wantsFocus, [&, id=tabInfo.tab->tabID()]{ switchToTab(id); }); } - - connect(ui->tabWidget, &QTabWidget::currentChanged, [&]{ onTabSelectionChanged(); }); - connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, [&]{ onTabMoved(); }); - connect(ui->close, &QPushButton::clicked, [&]{ onCloseButton(); }); - connect(ui->previousMod, &QPushButton::clicked, [&]{ onPreviousMod(); }); - connect(ui->nextMod, &QPushButton::clicked, [&]{ onNextMod(); }); -} - -ModInfoDialog::~ModInfoDialog() = default; - -template -std::unique_ptr createTab(ModInfoDialog& d, ModInfoTabIDs id) -{ - return std::make_unique(ModInfoDialogTabContext( - *d.m_core, *d.m_plugin, &d, d.ui.get(), id, d.m_mod, d.getOrigin())); -} - -std::vector ModInfoDialog::createTabs() -{ - std::vector v; - - v.push_back(createTab(*this, ModInfoTabIDs::TextFiles)); - v.push_back(createTab(*this, ModInfoTabIDs::IniFiles)); - v.push_back(createTab(*this, ModInfoTabIDs::Images)); - v.push_back(createTab(*this, ModInfoTabIDs::Esps)); - v.push_back(createTab(*this, ModInfoTabIDs::Conflicts)); - v.push_back(createTab(*this, ModInfoTabIDs::Categories)); - v.push_back(createTab(*this, ModInfoTabIDs::Nexus)); - v.push_back(createTab(*this, ModInfoTabIDs::Notes)); - v.push_back(createTab(*this, ModInfoTabIDs::Filetree)); - - return v; } int ModInfoDialog::exec() { + // whether to select the first tab; if the main window requested a specific + // tab, it is selected when encountered in update() const auto selectFirst = (m_initialTab == ModInfoTabIDs::None); update(true); - if (selectFirst) { - if (ui->tabWidget->count() > 0) { - ui->tabWidget->setCurrentIndex(0); - } + if (selectFirst && ui->tabWidget->count() > 0) { + ui->tabWidget->setCurrentIndex(0); } return TutorableDialog::exec(); @@ -223,6 +228,8 @@ void ModInfoDialog::setMod(ModInfo::Ptr mod) Q_ASSERT(mod); m_mod = mod; + // resetting the first activation flag so selecting tabs will trigger it + // again for (auto& tabInfo : m_tabs) { tabInfo.tab->resetFirstActivation(); } @@ -248,6 +255,7 @@ void ModInfoDialog::setMod(const QString& name) void ModInfoDialog::selectTab(ModInfoTabIDs id) { if (!isVisible()) { + // can't select a tab if the dialog hasn't been properly updated yet m_initialTab = id; return; } @@ -262,42 +270,55 @@ ModInfoDialog::TabInfo* ModInfoDialog::currentTab() return nullptr; } + // looking for the actual tab at that position for (auto& tabInfo : m_tabs) { if (tabInfo.realPos == index) { return &tabInfo; } } - qCritical() << "tab index " << index << " not found"; return nullptr; } void ModInfoDialog::update(bool firstTime) { + // remembering the current selection, will be restored if the tab still + // exists const int oldTab = ui->tabWidget->currentIndex(); setWindowTitle(m_mod->name()); + + // rebuilding the tab widget if needed depending on what tabs are valid for + // the current mod setTabsVisibility(firstTime); + // updating the data in all tabs updateTabs(); + // switching to the initial tab, if any if (m_initialTab != ModInfoTabIDs::None) { switchToTab(m_initialTab); m_initialTab = ModInfoTabIDs::None; } if (ui->tabWidget->currentIndex() == oldTab) { - // manually fire activated() because the tab index hasn't been changed if (auto* tabInfo=currentTab()) { + // activated() has to be fired manually because the tab index hasn't been + // changed tabInfo->tab->activated(); + } else { + qCritical() << "tab index " << oldTab << " not found"; } } } void ModInfoDialog::setTabsVisibility(bool firstTime) { + // this flag is picked up by onTabSelectionChanged() to avoid triggering + // activation events while moving tabs around QScopedValueRollback arrangingTabs(m_arrangingTabs, true); + // one bool per tab to indicate whether the tab should be visible std::vector visibility(m_tabs.size()); bool changed = false; @@ -307,14 +328,16 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) bool visible = true; + // a tab is visible if it can handle the current mod if (m_mod->hasFlag(ModInfo::FLAG_FOREIGN)) { visible = tabInfo.tab->canHandleUnmanaged(); } else if (m_mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { visible = tabInfo.tab->canHandleSeparators(); } + // if the visibility of this tab is changing, set changed to true because + // the tabs have to be rebuilt const auto currentlyVisible = (ui->tabWidget->indexOf(tabInfo.widget) != -1); - if (visible != currentlyVisible) { changed = true; } @@ -322,25 +345,23 @@ void ModInfoDialog::setTabsVisibility(bool firstTime) visibility[i] = visible; } + // the tabs have to be rebuilt the first time the dialog is shown, or when + // the visibility of any tab has changed if (!firstTime && !changed) { return; } - // something changed; save the current order (if necessary) because some tabs - // will be removed and others added + // save the current order (if necessary) because some tabs will be removed and + // others added saveTabOrder(Settings::instance()); - // remember selection - const int selIndex = ui->tabWidget->currentIndex(); + // remember selection, if any auto sel = ModInfoTabIDs::None; - - for (const auto& tabInfo : m_tabs) { - if (tabInfo.realPos == selIndex) { - sel = tabInfo.tab->tabID(); - break; - } + if (const auto* tabInfo=currentTab()) { + sel = tabInfo->tab->tabID(); } + // removes all tabs and re-adds the visible ones reAddTabs(visibility, sel); } @@ -352,21 +373,31 @@ void ModInfoDialog::reAddTabs( // ordered tab names from settings const auto orderedNames = getOrderedTabNames(); + // whether the tabs can be sorted; if the object name of a tab widget is not + // found in orderedNames, the list cannot be sorted safely bool canSort = true; // gathering visible tabs std::vector visibleTabs; for (std::size_t i=0; iobjectName(); - auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); - if (itor == orderedNames.end()) { - qCritical() << "can't sort tabs, '" << objectName << "' not found"; - canSort = false; - } + if (!visibility[i]) { + // this tab is not visible, skip it + continue; + } + + // this tab is visible + visibleTabs.push_back(&m_tabs[i]); + + if (canSort) { + // make sure the widget object name is found in the list + const auto objectName = m_tabs[i].widget->objectName(); + auto itor = std::find(orderedNames.begin(), orderedNames.end(), objectName); + + if (itor == orderedNames.end()) { + // this shouldn't happen, it means there's a tab in the UI that's no + // in the list + qCritical() << "can't sort tabs, '" << objectName << "' not found"; + canSort = false; } } } @@ -374,10 +405,16 @@ void ModInfoDialog::reAddTabs( // sorting tabs if (canSort) { std::sort(visibleTabs.begin(), visibleTabs.end(), [&](auto&& a, auto&& b){ - auto aItor = std::find(orderedNames.begin(), orderedNames.end(), a->widget->objectName()); - auto bItor = std::find(orderedNames.begin(), orderedNames.end(), b->widget->objectName()); + // looking the names in the ordered list + auto aItor = std::find( + orderedNames.begin(), orderedNames.end(), + a->widget->objectName()); - // this was checked above + auto bItor = std::find( + orderedNames.begin(), orderedNames.end(), + b->widget->objectName()); + + // this shouldn't happen, it was checked above Q_ASSERT(aItor != orderedNames.end() && bItor != orderedNames.end()); return (aItor < bItor); @@ -397,9 +434,13 @@ void ModInfoDialog::reAddTabs( for (std::size_t i=0; i(i); + + // adding tab ui->tabWidget->addTab(tabInfo.widget, tabInfo.icon, tabInfo.caption); + // selecting if (tabInfo.tab->tabID() == sel) { ui->tabWidget->setCurrentIndex(static_cast(i)); } @@ -410,59 +451,72 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) { auto* origin = getOrigin(); + // list of tabs that should be updated + std::vector interestedTabs; + for (auto& tabInfo : m_tabs) { + // don't touch invisible tabs if (!tabInfo.isVisible()) { continue; } + // updateTabs() is also called from onOriginModified() to update all the + // tabs that depend on the origin; if updateTabs() is called because the + // origin changed, but the tab doesn't use origin files, it can be safely + // skipped + // + // this happens for tabs like notes and categories, which don't need to + // be updated when files change if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { continue; } - tabInfo.tab->setMod(m_mod, origin); - tabInfo.tab->clear(); + // this tab should be updated + interestedTabs.push_back(&tabInfo); } - feedFiles(becauseOriginChanged); + for (auto* tabInfo : interestedTabs) { + // set the current mod + tabInfo->tab->setMod(m_mod, origin); - for (auto& tabInfo : m_tabs) { - if (tabInfo.isVisible()) { - tabInfo.tab->update(); - } + // clear + tabInfo->tab->clear(); } + // feed all the files from the filesystem + feedFiles(interestedTabs); + + // call update() on all tabs + for (auto* tabInfo : interestedTabs) { + tabInfo->tab->update(); + } + + // update the text colours setTabsColors(); } -void ModInfoDialog::feedFiles(bool becauseOriginChanged) +void ModInfoDialog::feedFiles(std::vector& interestedTabs) { - namespace fs = std::filesystem; - const auto rootPath = m_mod->absolutePath(); + if (rootPath.isEmpty()) { + return; + } - if (rootPath.length() > 0) { - fs::path path(rootPath.toStdWString()); - for (const auto& entry : fs::recursive_directory_iterator(path)) { - if (!entry.is_regular_file()) { - continue; - } - - const auto fileName = QString::fromWCharArray( - entry.path().native().c_str()); + const fs::path fsPath(rootPath.toStdWString()); - for (auto& tabInfo : m_tabs) { - if (!tabInfo.isVisible()) { - continue; - } + for (const auto& entry : fs::recursive_directory_iterator(fsPath)) { + if (!entry.is_regular_file()) { + // skip directories + continue; + } - if (becauseOriginChanged && !tabInfo.tab->usesOriginFiles()) { - continue; - } + const auto filePath = QString::fromStdWString(entry.path().native()); - if (tabInfo.tab->feedFile(rootPath, fileName)) { - break; - } + // for each tab + for (auto* tabInfo : interestedTabs) { + if (tabInfo->tab->feedFile(rootPath, filePath)) { + break; } } } @@ -470,41 +524,53 @@ void ModInfoDialog::feedFiles(bool becauseOriginChanged) void ModInfoDialog::setTabsColors() { + const auto p = m_mainWindow->palette(); + for (const auto& tabInfo : m_tabs) { - const auto c = tabInfo.tab->hasData() ? + if (!tabInfo.isVisible()) { + // don't bother with invisible tabs + continue; + } + + const QColor color = tabInfo.tab->hasData() ? QColor::Invalid : - m_mainWindow->palette().color(QPalette::Disabled, QPalette::WindowText); + p.color(QPalette::Disabled, QPalette::WindowText); - ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); + ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, color); } } void ModInfoDialog::switchToTab(ModInfoTabIDs id) { + // look a tab with the given id for (const auto& tabInfo : m_tabs) { if (tabInfo.tab->tabID() == id) { + // use realPos to select the proper tab in the widget ui->tabWidget->setCurrentIndex(tabInfo.realPos); return; } } + // this could happen if the tab is not visible right now qDebug() - << "can't switch to tab ID " << static_cast(id) << ", not available"; + << "can't switch to tab ID " << static_cast(id) + << ", not available"; } MOShared::FilesOrigin* ModInfoDialog::getOrigin() { - MOShared::FilesOrigin* origin = nullptr; - auto* ds = m_core->directoryStructure(); - if (ds->originExists(ToWString(m_mod->name()))) { - auto* origin = &ds->getOriginByName(ToWString(m_mod->name())); - if (!origin->isDisabled()) { - return origin; - } + + if (!ds->originExists(m_mod->name().toStdWString())) { + return nullptr; } - return nullptr; + auto* origin = &ds->getOriginByName(m_mod->name().toStdWString()); + if (origin->isDisabled()) { + return nullptr; + } + + return origin; } void ModInfoDialog::saveState(Settings& s) const @@ -520,6 +586,7 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().remove("mod_info_conflicts_noconflict"); s.directInterface().remove("mod_info_conflicts_overwritten"); + // save state for each tab for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); } @@ -527,6 +594,10 @@ void ModInfoDialog::saveState(Settings& s) const void ModInfoDialog::restoreState(const Settings& s) { + // tab order is not restored here, it will be picked up if tabs have to be + // removed and re-added + + // restore state for each tab for (const auto& tabInfo : m_tabs) { tabInfo.tab->restoreState(s); } @@ -536,6 +607,13 @@ void ModInfoDialog::saveTabOrder(Settings& s) const { if (static_cast(m_tabs.size()) != ui->tabWidget->count()) { // only save tab state when all tabs are visible + // + // if not all tabs are visible, it becomes very difficult to figure out in + // what order the user wants these tabs to be, so just avoid saving it + // completely + // + // this means that reordering tabs when not all tabs are visible is not + // saved, but it's better than breaking everything return; } @@ -559,7 +637,7 @@ std::vector ModInfoDialog::getOrderedTabNames() const std::vector v; if (settings.contains("mod_info_tabs")) { - // old byte array + // old byte array from 2.2.0 QDataStream stream(settings.value("mod_info_tabs").toByteArray()); int count = 0; @@ -587,12 +665,16 @@ std::vector ModInfoDialog::getOrderedTabNames() const void ModInfoDialog::onOriginModified(int originID) { + // tell the main window the origin changed emit originModified(originID); + + // update tabs that depend on the origin updateTabs(true); } void ModInfoDialog::onDeleteShortcut() { + // forward the request to the current tab if (auto* tabInfo=currentTab()) { tabInfo->tab->deleteRequested(); } @@ -616,6 +698,10 @@ void ModInfoDialog::onCloseButton() bool ModInfoDialog::tryClose() { + // cancel the close if any tab returns false; for example. this can happen if + // a tab has unsaved content, pops a confirmation dialog, and the user clicks + // cancel + for (auto& tabInfo : m_tabs) { if (!tabInfo.tab->canClose()) { return false; @@ -634,6 +720,7 @@ void ModInfoDialog::onTabSelectionChanged() return; } + // this will call firstActivation() on the tab if needed if (auto* tabInfo=currentTab()) { tabInfo->tab->activated(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 035eb6d6..34555b0c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -141,9 +141,9 @@ private: bool m_arrangingTabs; - // creates all the tabs + // creates all the tabs and connects events // - std::vector createTabs(); + void createTabs(); // sets the currently selected mod; resets first activation, but doesn't @@ -155,17 +155,16 @@ private: // void setMod(const QString& name); + // returns the origin of the current mod, may be null + // + MOShared::FilesOrigin* getOrigin(); + // returns the currently selected tab, taking re-ordering in to account; // shouldn't be null, but could be // TabInfo* currentTab(); - // returns a list of tab object names in their visual order, used by - // saveState() - // - void saveTabOrder(Settings& s) const; - // fully updates the dialog; sets the title, the tab visibility and updates // all the tabs; used when the current mod changes @@ -179,16 +178,50 @@ private: void setTabsVisibility(bool firstTime); // clears the tab widgets and re-adds the tabs having the visible flag in - // the given vector, following the + // the given vector, following the tab order from the settings + // void reAddTabs(const std::vector& visibility, ModInfoTabIDs sel); - void onDeleteShortcut(); - MOShared::FilesOrigin* getOrigin(); + // called by update(); clears tabs, feeds files and calls update() on all + // tabs, then setTabsColors() + // void updateTabs(bool becauseOriginChanged=false); - void feedFiles(bool becauseOriginChanged); + + // goes through all files on the filesystem for the current mod and calls + // feedFile() on every tab until one accepts it + // + void feedFiles(std::vector& interestedTabs); + + // goes through all tabs and sets the tab text colour depending on whether + // they have data or not + // void setTabsColors(); + + + // called when the delete key is pressed anywhere in the dialog; forwards to + // ModInfoDialogTab::deleteRequest() for the currently selected tab + // + void onDeleteShortcut(); + + + // finds the tab with the given id and selects it + // void switchToTab(ModInfoTabIDs id); + + + // saves the current tab order; used by saveState(), but also by + // setTabsVisibility() to make sure any changes to order are saved before + // re-adding tabs + // + void saveTabOrder(Settings& s) const; + + // returns a list of tab names in the order they should appear on the widget + // std::vector getOrderedTabNames() const; + + // asks all the tabs if they accept closing the dialog, returns false if one + // objected + // bool tryClose(); diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 35480e2c..0b519932 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -314,7 +314,6 @@ void FileTreeTab::changeVisibility(bool visible) qDebug().nospace() << (visible ? "unhiding" : "hiding") << " filetree files done"; if (changed) { - qDebug().nospace() << "triggering refresh"; if (origin()) { emitOriginModified(); } -- cgit v1.3.1 From 7fe637ce4421e0c6d6ee6b103db5fcc4ef676c25 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:57:33 -0400 Subject: display error when loading an image fails --- src/modinfodialogimages.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index cf33f9f5..69866902 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -888,11 +888,20 @@ File::File(QString path) void File::ensureOriginalLoaded() { - if (m_original.isNull()) { - if (!m_original.load(m_path)) { - qCritical() << "failed to load image from " << m_path; - m_failed = true; - } + if (!m_original.isNull()) { + // already loaded + return; + } + + QImageReader reader(m_path); + + if (!reader.read(&m_original)) { + qCritical().noquote().nospace() + << "failed to load '" << m_path << "'\n" + << reader.errorString() << " " + << "(error " << static_cast(reader.error()) << ")"; + + m_failed = true; } } -- cgit v1.3.1