From 6c470aff829bed12853321c25262b84934242098 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Sat, 18 May 2019 03:53:20 -0400 Subject: changed the top conflict treeview to support multiple selection this required making some changes to both trees because they were sharing m_ConflictsContextItem that was set on right clicks; both trees are now independent hide/unhide is always shown on multiple selection to avoid scanning all the selected items --- src/modinfodialog.ui | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 0211e704..39173a14 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -430,6 +430,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft -- cgit v1.3.1 From a2b1f6a3604d8a2e4bc47c2b91fb58e6d5b45af6 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 16:11:10 -0400 Subject: added third tree with non-conflicting files --- src/modinfodialog.cpp | 11 ++++++++++- src/modinfodialog.ui | 50 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 12 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 555b62db..3c68f4e8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -501,6 +501,7 @@ void ModInfoDialog::refreshLists() ui->overwriteTree->clear(); ui->overwrittenTree->clear(); + ui->noconflictTree->clear(); if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); @@ -535,7 +536,15 @@ void ModInfoDialog::refreshLists() } ui->overwriteTree->addTopLevelItem(item); ++numOverwrite; - } else {// otherwise don't display the file + } else {// otherwise, put the file in the nonconflict tree + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); + item->setData(0, Qt::UserRole, fileName); + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + item->setFont(0, font); + } + ui->noconflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 39173a14..25fce40c 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -526,22 +526,50 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + - - - Non-Conflicted files - - + + + + + <html><head/><body><p>The following files have no conflicts</p></body></html> + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + - - - QFrame::Sunken + + + Qt::CustomContextMenu - - QLCDNumber::Flat + + Qt::ElideLeft + + + true + + + true + + + 1 + + + File + + -- cgit v1.3.1 From 00f8cbff05c3f548baba59b0cb525205cc1d80e3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 17:38:17 -0400 Subject: expandable sections in the conflict tab --- src/modinfodialog.cpp | 65 ++++++++++++++- src/modinfodialog.h | 21 +++++ src/modinfodialog.ui | 218 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 237 insertions(+), 67 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3c68f4e8..3f8b58a9 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -265,6 +265,61 @@ bool FileRenamer::renameFailed(const QString& oldName, const QString& newName) } +ExpanderWidget::ExpanderWidget() + : m_button(nullptr), m_content(nullptr) +{ +} + +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); + m_button->setCheckable(true); + + if (o) + open(); + else + close(); + + QObject::connect(m_button, &QToolButton::clicked, [&]{ toggle(); }); +} + +void ExpanderWidget::open() +{ + m_button->setArrowType(Qt::DownArrow); + m_button->setChecked(false); + m_content->show(); +} + +void ExpanderWidget::close() +{ + m_button->setArrowType(Qt::RightArrow); + m_button->setChecked(false); + m_content->hide(); +} + +void ExpanderWidget::toggle() +{ + if (opened()) + close(); + else + open(); +} + +bool ExpanderWidget::opened() const +{ + return m_content->isVisible(); +} + + 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), @@ -378,6 +433,10 @@ 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); } @@ -501,7 +560,7 @@ void ModInfoDialog::refreshLists() ui->overwriteTree->clear(); ui->overwrittenTree->clear(); - ui->noconflictTree->clear(); + ui->noConflictTree->clear(); if (m_Origin != nullptr) { std::vector files = m_Origin->getFiles(); @@ -520,7 +579,7 @@ void ModInfoDialog::refreshLists() } altString << m_Directory->getOriginByID(altIter->first).getName(); } - QStringList fields(relativeName.prepend("...")); + QStringList fields(relativeName); fields.append(ToQString(altString.str())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); @@ -544,7 +603,7 @@ void ModInfoDialog::refreshLists() font.setItalic(true); item->setFont(0, font); } - ui->noconflictTree->addTopLevelItem(item); + ui->noConflictTree->addTopLevelItem(item); ++numNonConflicting; } } else { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 66c50be5..731611d2 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -183,6 +183,25 @@ private: }; +class ExpanderWidget +{ +public: + ExpanderWidget(); + ExpanderWidget(QToolButton* button, QWidget* content); + + void set(QToolButton* button, QWidget* content, bool opened=false); + + void open(); + void close(); + void toggle(); + bool opened() const; + +private: + QToolButton* m_button; + QWidget* m_content; +}; + + /** * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system @@ -383,6 +402,8 @@ private: std::map m_RealTabPos; + ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 25fce40c..0d96f621 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -400,28 +400,57 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - + + + 0 + - + - - - The following conflicted files are provided by this mod - - - - - - - QFrame::Sunken - - - 1 - - - QLCDNumber::Flat - - + + + + + + 0 + 0 + + + + border: none + + + The following conflicted files are provided by this mod + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + @@ -470,23 +499,49 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - - - - The following conflicted files are provided by other mods - - - + - - - QFrame::Sunken - - - QLCDNumber::Flat - - + + + + + + 0 + 0 + + + + border: none + + + The following conflicted files are provided by other mods + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + @@ -526,16 +581,38 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + - + - + + + + 0 + 0 + + + + border: none; + - <html><head/><body><p>The following files have no conflicts</p></body></html> + The following files have no conflicts + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -548,32 +625,45 @@ Most mods do not have optional esps, so chances are good you are looking at an e - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 1 - - - - File - - - - + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 1 + + + + File + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + -- cgit v1.3.1 From c5f61733850d6737814041f645a009e1cc60a7ed Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 20 May 2019 17:58:00 -0400 Subject: made expander use the full width context menu for noconflict tree --- src/modinfodialog.cpp | 37 +++++++++++++++++++++++++++++++++-- src/modinfodialog.h | 4 ++++ src/modinfodialog.ui | 54 +++++++++------------------------------------------ 3 files changed, 48 insertions(+), 47 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 3f8b58a9..d32b112f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1732,7 +1732,6 @@ void ModInfoDialog::openOverwriteDataFile() void ModInfoDialog::previewOverwrittenDataFile() { - // the overwritten tree only supports single selection, but check just in case const auto selection = ui->overwrittenTree->selectedItems(); if (!selection.empty()) { previewDataFile(selection[0]); @@ -1741,13 +1740,28 @@ void ModInfoDialog::previewOverwrittenDataFile() void ModInfoDialog::openOverwrittenDataFile() { - // the overwritten tree only supports single selection, but check just in case const auto selection = ui->overwrittenTree->selectedItems(); if (!selection.empty()) { openDataFile(selection[0]); } } +void ModInfoDialog::previewNoConflictDataFile() +{ + const auto selection = ui->noConflictTree->selectedItems(); + if (!selection.empty()) { + previewDataFile(selection[0]); + } +} + +void ModInfoDialog::openNoConflictDataFile() +{ + const auto selection = ui->noConflictTree->selectedItems(); + if (!selection.empty()) { + openDataFile(selection[0]); + } +} + void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) { if (!item) { @@ -1992,6 +2006,25 @@ void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint & } } +void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) +{ + auto* item = ui->noConflictTree->itemAt(pos.x(), pos.y()); + + if (item != nullptr) { + if (!item->data(1, Qt::UserRole + 2).toBool()) { + QMenu menu; + + menu.addAction(tr("Open/Execute"), this, SLOT(openNoConflictDataFile())); + + if (canPreviewConflictItem(item)) { + menu.addAction(tr("Preview"), this, SLOT(previewNoConflictDataFile())); + } + + menu.exec(ui->noConflictTree->viewport()->mapToGlobal(pos)); + } + } +} + void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 731611d2..32efa210 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -319,6 +319,9 @@ private slots: void previewOverwrittenDataFile(); void openOverwrittenDataFile(); + void previewNoConflictDataFile(); + void openNoConflictDataFile(); + void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -355,6 +358,7 @@ private slots: 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_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 0d96f621..1187de87 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -407,7 +407,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -417,26 +417,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none + border: none; +text-align: left; The following conflicted files are provided by this mod - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -501,7 +489,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -511,26 +499,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none + border: none; +text-align: left; The following conflicted files are provided by other mods - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -583,7 +559,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + @@ -593,26 +569,14 @@ Most mods do not have optional esps, so chances are good you are looking at an e - border: none; + border: none; +text-align: left; The following files have no conflicts - - - - Qt::Horizontal - - - - 40 - 20 - - - - -- cgit v1.3.1 From 2d52299743d056ae6da3c98ca9dc34abca3138bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 27 May 2019 12:07:10 -0400 Subject: multiple selection for noconflict and overwritten lists set uniformRowHeights for all three lists for faster rendering, all items are text only all three lists use the same code for the context menu: - createConflictMenuActions() returns a struct with the QActions that are valid for the selection - showConflictMenu() plugs in the handlers and shows the menu --- src/modinfodialog.cpp | 190 +++++++++++++++++++++----------------------------- src/modinfodialog.h | 37 ++++++---- src/modinfodialog.ui | 21 ++++-- 3 files changed, 119 insertions(+), 129 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 19a03ea7..024679f3 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1636,13 +1636,12 @@ FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const return renamer.rename(oldName, newName); } -void ModInfoDialog::changeConflictFilesVisibility(bool hide) +void ModInfoDialog::changeConflictItemsVisibility( + const QList& items, bool hide) { bool changed = false; bool stop = false; - const auto items = ui->overwriteTree->selectedItems(); - qDebug().nospace() << (hide ? "hiding" : "unhiding") << " " << items.size() << " conflict files"; @@ -1707,63 +1706,21 @@ void ModInfoDialog::changeConflictFilesVisibility(bool hide) } } -void ModInfoDialog::hideConflictFiles() -{ - changeConflictFilesVisibility(true); -} - -void ModInfoDialog::unhideConflictFiles() -{ - changeConflictFilesVisibility(false); -} - -void ModInfoDialog::previewOverwriteDataFile() -{ - // the menu item is only shown for a single selection, but check just in case - const auto selection = ui->overwriteTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openOverwriteDataFile() +void ModInfoDialog::openConflictItems(const QList& items) { - // the menu item is only shown for a single selection, but check just in case - const auto selection = ui->overwriteTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + openDataFile(item); } } -void ModInfoDialog::previewOverwrittenDataFile() +void ModInfoDialog::previewConflictItems(const QList& items) { - const auto selection = ui->overwrittenTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openOverwrittenDataFile() -{ - const auto selection = ui->overwrittenTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); - } -} - -void ModInfoDialog::previewNoConflictDataFile() -{ - const auto selection = ui->noConflictTree->selectedItems(); - if (!selection.empty()) { - previewDataFile(selection[0]); - } -} - -void ModInfoDialog::openNoConflictDataFile() -{ - const auto selection = ui->noConflictTree->selectedItems(); - if (!selection.empty()) { - openDataFile(selection[0]); + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + previewDataFile(item); } } @@ -1846,17 +1803,71 @@ bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) { - const auto selection = ui->overwriteTree->selectedItems(); - if (selection.empty()) { + 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::showConflictMenu(const QPoint &pos, QTreeWidget* tree) +{ + auto actions = createConflictMenuActions(tree->selectedItems()); + + QMenu menu; + + if (actions.open) { + connect(actions.open, &QAction::triggered, [&]{ + openConflictItems(tree->selectedItems()); + }); + + menu.addAction(actions.open); + } + + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewConflictItems(tree->selectedItems()); + }); + + menu.addAction(actions.preview); + } + + if (actions.hide) { + connect(actions.hide, &QAction::triggered, [&]{ + changeConflictItemsVisibility(tree->selectedItems(), false); + }); + + menu.addAction(actions.hide); + } + + if (actions.unhide) { + connect(actions.unhide, &QAction::triggered, [&]{ + changeConflictItemsVisibility(tree->selectedItems(), true); + }); + + menu.addAction(actions.unhide); + } + + if (menu.isEmpty()) { return; } - // for a single selection, hide/unhide is not shown for files from - // archives and whether the action is hide or unhide depends on the current - // state - // - // for multiple selection, both actions are shown unconditionally and - // handled in hideConflictFiles() and unhideConflictFiles() + 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; @@ -1866,7 +1877,7 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po // this is a single selection const auto* item = selection[0]; if (!item) { - return; + return {}; } enableHide = canHideConflictItem(item); @@ -1903,66 +1914,27 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po } } - - QMenu menu; + ConflictActions actions; if (enableHide) { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFiles())); + actions.hide = new QAction(tr("Hide")); } // note that it is possible for hidden files to appear if they override other // hidden files from another mod if (enableUnhide) { - menu.addAction(tr("Unhide"), this, SLOT(unhideConflictFiles())); + actions.unhide = new QAction(tr("Unhide")); } if (enableOpen) { - menu.addAction(tr("Open/Execute"), this, SLOT(openOverwriteDataFile())); + actions.open = new QAction(tr("Open/Execute")); } if (enablePreview) { - menu.addAction(tr("Preview"), this, SLOT(previewOverwriteDataFile())); + actions.preview = new QAction(tr("Preview")); } - menu.exec(ui->overwriteTree->viewport()->mapToGlobal(pos)); -} - -void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) -{ - auto* item = ui->overwrittenTree->itemAt(pos.x(), pos.y()); - - if (item != nullptr) { - if (!item->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - - menu.addAction(tr("Open/Execute"), this, SLOT(openOverwrittenDataFile())); - - if (canPreviewConflictItem(item)) { - menu.addAction(tr("Preview"), this, SLOT(previewOverwrittenDataFile())); - } - - menu.exec(ui->overwrittenTree->viewport()->mapToGlobal(pos)); - } - } -} - -void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) -{ - auto* item = ui->noConflictTree->itemAt(pos.x(), pos.y()); - - if (item != nullptr) { - if (!item->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - - menu.addAction(tr("Open/Execute"), this, SLOT(openNoConflictDataFile())); - - if (canPreviewConflictItem(item)) { - menu.addAction(tr("Preview"), this, SLOT(previewNoConflictDataFile())); - } - - menu.exec(ui->noConflictTree->viewport()->mapToGlobal(pos)); - } - } + return actions; } void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index c0730afa..983da8d4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -329,18 +329,6 @@ private: int tabIndex(const QString &tabId); private slots: - - void hideConflictFiles(); - void unhideConflictFiles(); - void previewOverwriteDataFile(); - void openOverwriteDataFile(); - - void previewOverwrittenDataFile(); - void openOverwrittenDataFile(); - - void previewNoConflictDataFile(); - void openNoConflictDataFile(); - void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); void linkClicked(QString url); @@ -393,6 +381,18 @@ private slots: void createTweak(); private: + struct ConflictActions + { + QAction* hide; + QAction* unhide; + QAction* open; + QAction* preview; + + ConflictActions() + : hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr) + { + } + }; Ui::ModInfoDialog *ui; @@ -442,12 +442,21 @@ private: void openDataFile(const QTreeWidgetItem* item); void previewDataFile(const QTreeWidgetItem* item); - void changeConflictFilesVisibility(bool hide); - void changeFiletreeVisibility(bool hide); + void changeFiletreeVisibility(bool visible); + + void openConflictItems(const QList& items); + void previewConflictItems(const QList& items); + void changeConflictItemsVisibility( + const QList& items, bool visible); bool canPreviewFile(bool isArchive, const QString& filename) const; bool canHideFile(bool isArchive, const QString& filename) const; bool canUnhideFile(bool isArchive, const QString& filename) const; + + void showConflictMenu(const QPoint &pos, QTreeWidget* tree); + + ConflictActions createConflictMenuActions( + const QList selection); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 1187de87..840c81ee 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -453,24 +453,21 @@ text-align: left; Qt::ElideLeft + + true + true true - - false - 2 365 - - false - 200 @@ -526,9 +523,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true @@ -596,9 +599,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true -- cgit v1.3.1 From f9e9d79a7d433b79a0beb5d11272a606f066affe Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 12:43:57 -0400 Subject: moved all widgets in the conflict tab into a parent widget instead of directly being in the tab, allows for future additions to the tab --- src/modinfodialog.ui | 453 ++++++++++++++++++++++++++------------------------- 1 file changed, 234 insertions(+), 219 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 840c81ee..1fe431dd 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -400,243 +400,258 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - + 0 - - - - - - - - 0 - 0 - - - - border: none; -text-align: left; - - - The following conflicted files are provided by this mod - - - - - - - QFrame::Sunken - - - 1 - - - QLCDNumber::Flat - - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 2 + + + + 0 + 100 + - - 365 - - - 200 - - - - File + + + 0 - - - - Overwritten Mods + + 0 - - - - - - - - - - - - 0 - 0 - - - - border: none; + + 0 + + + 0 + + + + + + + + + + 0 + 0 + + + + border: none; text-align: left; - + + + The following conflicted files are provided by this mod + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + Qt::ElideLeft + + + true + + + true + + + false + + + 2 + + + 365 + + + false + + + 200 + + - The following conflicted files are provided by other mods - - - - - - - QFrame::Sunken + File - - QLCDNumber::Flat - - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 2 - - - 365 - - - 200 - - - - File - - - - - Providing Mod - - - - - - - - - - - - - 0 - 0 - + + + + Overwritten Mods - - border: none; + + + + + + + + + + + + 0 + 0 + + + + border: none; text-align: left; - + + + The following conflicted files are provided by other mods + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 2 + + + 365 + + + 200 + + - The following files have no conflicts + File - - - - - - QFrame::Sunken + + + + Providing Mod - - QLCDNumber::Flat + + + + + + + + + + + + 0 + 0 + + + + border: none; +text-align: left; + + + The following files have no conflicts + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft + + + true + + + true + + + 1 + + + + File - - - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - true - - - 1 - - - - File - - + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + - - - - Qt::Vertical - - - - 20 - 0 - - - - -- cgit v1.3.1 From 2687fa8231cf4b4d9d9bc5bd90ca7c287f97ead2 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 14:18:32 -0400 Subject: added new tabs in the conflict tab --- src/modinfodialog.ui | 568 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 351 insertions(+), 217 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 1fe431dd..190f5b56 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -401,255 +401,384 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - - 0 - - - - - 0 - 100 - + + + 1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - + + + General + + + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + - - - - 0 - 0 - - - - border: none; + + + + + + + + 0 + 0 + + + + border: none; text-align: left; + + + The following conflicted files are provided by this mod + + + + + + + QFrame::Sunken + + + 1 + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu - - The following conflicted files are provided by this mod + + QAbstractItemView::ExtendedSelection + + Qt::ElideLeft + + + true + + + true + + + false + + + 2 + + + 365 + + + false + + + 200 + + + + File + + + + + Overwritten Mods + + - - - QFrame::Sunken + + + + + + + + 0 + 0 + + + + border: none; +text-align: left; + + + The following conflicted files are provided by other mods + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu - - 1 + + Qt::ElideLeft + + + true + + + true - - QLCDNumber::Flat + + 2 + + 365 + + + 200 + + + + File + + + + + Providing Mod + + - - - - - - - - Qt::CustomContextMenu - - - QAbstractItemView::ExtendedSelection - - - Qt::ElideLeft - - - true - - - true - - - false - - - 2 - - - 365 - - - false - - - 200 - - - - File - - - - - Overwritten Mods - - - - - - - - - - - - 0 - 0 - - - - border: none; + + + + + + + + 0 + 0 + + + + border: none; text-align: left; + + + The following files have no conflicts + + + + + + + QFrame::Sunken + + + QLCDNumber::Flat + + + + + + + + + + + Qt::CustomContextMenu + + + Qt::ElideLeft - - The following conflicted files are provided by other mods + + true + + + true + + + 1 + + + File + + - - - QFrame::Sunken + + + Qt::Vertical - - QLCDNumber::Flat + + + 20 + 0 + - + - - - - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 2 - - - 365 - - - 200 - - - - File - - - - - Providing Mod - - - - - - - - + + + + + + + Advanced + + + + + + + 0 + + + 0 + + + 0 + + + 0 + - - - - 0 - 0 - - - - border: none; -text-align: left; - - - The following files have no conflicts - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + 243 + + + + Overwrites + + + + + File + + + + + Overwritten by + + + + + - - - QFrame::Sunken - - - QLCDNumber::Flat - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Show files that have no conflicts + + + + + + + + 20 + + + 0 + + + 0 + + + 0 + + + + + Filter + + + + + + + - - - - - - - Qt::CustomContextMenu - - - Qt::ElideLeft - - - true - - - true - - - 1 - - - - File - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - + + + + @@ -1052,6 +1181,11 @@ p, li { white-space: pre-wrap; } QLineEdit modidlineedit.h + + MOBase::LineEditClear + QLineEdit + lineeditclear.h + -- cgit v1.3.1 From ab385347afc3317049d85a5ee4b011592d8b3dce Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 15:39:09 -0400 Subject: allow for refreshing individual conflict tabs advanced conflict list now has items --- src/modinfodialog.cpp | 132 +++++++++++++++++++++++++++++++++++++++----------- src/modinfodialog.h | 11 +++-- src/modinfodialog.ui | 3 ++ 3 files changed, 116 insertions(+), 30 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index f5b6abc8..2d2cc559 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -437,6 +437,10 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_overwriteExpander.set(ui->overwriteExpander, ui->overwriteTree, true); m_overwrittenExpander.set(ui->overwrittenExpander, ui->overwrittenTree, true); m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); + + connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { + refreshConflictLists(false, true); + }); } @@ -597,55 +601,76 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const void ModInfoDialog::refreshLists() { - refreshConflictLists(); + refreshConflictLists(true, true); refreshFiles(); } -void ModInfoDialog::refreshConflictLists() +void ModInfoDialog::refreshConflictLists( + bool refreshGeneral, bool refreshAdvanced) { int numNonConflicting = 0; int numOverwrite = 0; int numOverwritten = 0; - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - ui->noConflictTree->clear(); + 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()) { - QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - QString fileName = relativeName.mid(0).prepend(m_RootPath); + 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 (file->getOrigin(archive) == m_Origin->getID()) { - const auto& alternatives = file->getAlternatives(); + if (refreshGeneral) { + if (fileOrigin == m_Origin->getID()) { + if (!alternatives.empty()) { + ui->overwriteTree->addTopLevelItem(createOverwriteItem( + archive, fileName, relativeName, alternatives)); - if (!alternatives.empty()) { - ui->overwriteTree->addTopLevelItem(createOverwriteItem( - archive, fileName, relativeName, alternatives)); + ++numOverwrite; + } else { + // otherwise, put the file in the noconflict tree + ui->noConflictTree->addTopLevelItem(createNoConflictItem( + archive, fileName, relativeName)); - ++numOverwrite; + ++numNonConflicting; + } } else { - // otherwise, put the file in the nonconflict tree - ui->noConflictTree->addTopLevelItem(createNoConflictItem( - archive, fileName, relativeName)); + ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( + fileOrigin, archive, fileName, relativeName)); - ++numNonConflicting; + ++numOverwritten; } - } else { - ui->overwrittenTree->addTopLevelItem(createOverwrittenItem( - file, archive, fileName, relativeName)); + } + + if (refreshAdvanced) { + auto* advancedItem = createAdvancedConflictItem( + fileOrigin, archive, fileName, relativeName, alternatives); - ++numOverwritten; + if (advancedItem) { + ui->conflictsAdvancedList->addTopLevelItem(advancedItem); + } } } } - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); + if (refreshGeneral) { + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); + } } QTreeWidgetItem* ModInfoDialog::createOverwriteItem( @@ -697,10 +722,10 @@ QTreeWidgetItem* ModInfoDialog::createNoConflictItem( } QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( - const MOShared::FileEntry::Ptr& file, - bool archive, const QString& fileName, const QString& relativeName) + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName) { - const FilesOrigin &realOrigin = m_Directory->getOriginByID(file->getOrigin(archive)); + const FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); @@ -720,6 +745,59 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( return item; } +QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( + 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) + { + auto altOrigin = m_Directory->getOriginByID(alt.first); + + if (altOrigin.getPriority() > beforePrio) { + if (altOrigin.getPriority() < m_Origin->getPriority()) { + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } + } + + if (altOrigin.getPriority() < afterPrio) { + if (altOrigin.getPriority() > m_Origin->getPriority()) { + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } + } + } + + if (after.isEmpty()) { + FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); + + if (realOrigin.getID() != m_Origin->getID()) { + after = ToQString(realOrigin.getName()); + } + } + } + + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + if (before.isEmpty() && after.isEmpty()) { + return nullptr; + } + } + + QTreeWidgetItem* item = new QTreeWidgetItem; + item->setText(0, before); + item->setText(1, relativeName); + item->setText(2, after); + + return item; +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 3c193cd3..69b0069e 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -429,7 +429,7 @@ private: ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; - void refreshConflictLists(); + void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); QTreeWidgetItem* createOverwriteItem( @@ -440,8 +440,13 @@ private: bool archive, const QString& fileName, const QString& relativeName); QTreeWidgetItem* createOverwrittenItem( - const MOShared::FileEntry::Ptr& file, - bool archive, const QString& fileName, const QString& relativeName); + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName); + + QTreeWidgetItem* createAdvancedConflictItem( + int fileOrigin, bool archive, + const QString& fileName, const QString& relativeName, + const MOShared::FileEntry::AlternativesVector& alternatives); void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 190f5b56..b31f49d3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -744,6 +744,9 @@ text-align: left; Show files that have no conflicts + + true + -- cgit v1.3.1 From 260462fe6dd37899744b95f777cdff3c738fad2c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 28 May 2019 16:10:10 -0400 Subject: added show all/nearest mods radio buttons --- src/modinfodialog.cpp | 48 ++++++++++++++++++++++++++++++++++++++---------- src/modinfodialog.ui | 17 +++++++++++++++++ 2 files changed, 55 insertions(+), 10 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2d2cc559..e35aa0e1 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -441,6 +441,14 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo connect(ui->conflictsAdvancedShowNoConflict, &QCheckBox::clicked, [&] { refreshConflictLists(false, true); }); + + connect(ui->conflictsAdvancedShowAll, &QRadioButton::clicked, [&] { + refreshConflictLists(false, true); + }); + + connect(ui->conflictsAdvancedShowNearest, &QRadioButton::clicked, [&] { + refreshConflictLists(false, true); + }); } @@ -760,26 +768,46 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( { auto altOrigin = m_Directory->getOriginByID(alt.first); - if (altOrigin.getPriority() > beforePrio) { + if (ui->conflictsAdvancedShowAll->isChecked()) { if (altOrigin.getPriority() < m_Origin->getPriority()) { - before = ToQString(altOrigin.getName()); - beforePrio = altOrigin.getPriority(); + if (!before.isEmpty()) { + before += ", "; + } + + before += ToQString(altOrigin.getName()); + } else if (altOrigin.getPriority() > m_Origin->getPriority()) { + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(altOrigin.getName()); + } + } else { + if (altOrigin.getPriority() > beforePrio) { + if (altOrigin.getPriority() < m_Origin->getPriority()) { + before = ToQString(altOrigin.getName()); + beforePrio = altOrigin.getPriority(); + } } - } - if (altOrigin.getPriority() < afterPrio) { - if (altOrigin.getPriority() > m_Origin->getPriority()) { - after = ToQString(altOrigin.getName()); - afterPrio = altOrigin.getPriority(); + if (altOrigin.getPriority() < afterPrio) { + if (altOrigin.getPriority() > m_Origin->getPriority()) { + after = ToQString(altOrigin.getName()); + afterPrio = altOrigin.getPriority(); + } } } } - if (after.isEmpty()) { + if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { FilesOrigin &realOrigin = m_Directory->getOriginByID(fileOrigin); if (realOrigin.getID() != m_Origin->getID()) { - after = ToQString(realOrigin.getName()); + if (!after.isEmpty()) { + after += ", "; + } + + after += ToQString(realOrigin.getName()); } } } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index b31f49d3..e391da46 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -749,6 +749,23 @@ text-align: left; + + + + Show all conflicting mods + + + true + + + + + + + Show nearest conflicting mod + + + -- cgit v1.3.1 From 4d90e502266dfb886e1d089fb26780d19a38e5a7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 May 2019 09:45:30 -0400 Subject: added new filterwidget, will eventually get the operators functionality from the mod/pluging lists --- src/CMakeLists.txt | 3 ++ src/filterwidget.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/filterwidget.h | 27 ++++++++++++++++ src/modinfodialog.cpp | 2 ++ src/modinfodialog.h | 3 ++ src/modinfodialog.ui | 7 +--- 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/filterwidget.cpp create mode 100644 src/filterwidget.h (limited to 'src/modinfodialog.ui') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cae26334..93597d62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,6 +115,7 @@ SET(organizer_SRCS lcdnumber.cpp forcedloaddialog.cpp forcedloaddialogwidget.cpp + filterwidget.cpp shared/windows_error.cpp shared/error_report.cpp @@ -211,6 +212,7 @@ SET(organizer_HDRS lcdnumber.h forcedloaddialog.h forcedloaddialogwidget.h + filterwidget.h shared/windows_error.h shared/error_report.h @@ -391,6 +393,7 @@ set(utilities set(widgets descriptionpage genericicondelegate + filterwidget icondelegate lcdnumber logbuffer diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp new file mode 100644 index 00000000..39324bc6 --- /dev/null +++ b/src/filterwidget.cpp @@ -0,0 +1,88 @@ +#include "filterwidget.h" +#include "eventfilter.h" + +FilterWidget::FilterWidget() + : m_edit(nullptr), m_clear(nullptr), m_buddy(nullptr) +{ +} + +void FilterWidget::set(QLineEdit* edit) +{ + if (m_clear) { + delete m_clear; + m_clear = nullptr; + } + + m_edit = edit; + if (!m_edit) { + return; + } + + createClear(); + hookEvents(); +} + +void FilterWidget::clear() +{ + if (!m_edit) { + return; + } + + m_edit->clear(); +} + +void FilterWidget::createClear() +{ + m_clear = new QToolButton(m_edit); + + QPixmap pixmap(":/MO/gui/edit_clear"); + m_clear->setIcon(QIcon(pixmap)); + m_clear->setIconSize(pixmap.size()); + m_clear->setCursor(Qt::ArrowCursor); + m_clear->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + m_clear->hide(); + + QObject::connect(m_clear, &QToolButton::clicked, [&]{ clear(); }); + QObject::connect(m_edit, &QLineEdit::textChanged, [&]{ onTextChanged(); }); + + repositionClearButton(); +} + +void FilterWidget::hookEvents() +{ + auto* f = new EventFilter(m_edit, [&](auto* w, auto* e) { + if (e->type() == QEvent::Resize) { + onResized(); + } + + return false; + }); + + m_edit->installEventFilter(f); +} + +void FilterWidget::onTextChanged() +{ + m_clear->setVisible(!m_edit->text().isEmpty()); +} + +void FilterWidget::onResized() +{ + repositionClearButton(); +} + +void FilterWidget::repositionClearButton() +{ + if (!m_clear) { + return; + } + + const QSize sz = m_clear->sizeHint(); + const int frame = m_edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + const auto r = m_edit->rect(); + + const auto x = r.right() - frame - sz.width(); + const auto y = (r.bottom() + 1 - sz.height()) / 2; + + m_clear->move(x, y); +} diff --git a/src/filterwidget.h b/src/filterwidget.h new file mode 100644 index 00000000..07e216f1 --- /dev/null +++ b/src/filterwidget.h @@ -0,0 +1,27 @@ +#ifndef FILTERWIDGET_H +#define FILTERWIDGET_H + +class FilterWidget +{ +public: + FilterWidget(); + + void set(QLineEdit* edit); + void buddy(QWidget* w); + + void clear(); + +private: + QLineEdit* m_edit; + QToolButton* m_clear; + QWidget* m_buddy; + + void createClear(); + void hookEvents(); + void repositionClearButton(); + + void onTextChanged(); + void onResized(); +}; + +#endif // FILTERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9db31da3..1e933a74 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -453,6 +453,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_nonconflictExpander.set(ui->noConflictExpander, ui->noConflictTree); + m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); + // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( 0, new ElideLeftDelegate(ui->conflictsAdvancedList)); diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 69b0069e..5169a993 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include "tutorabledialog.h" #include "plugincontainer.h" #include "organizercore.h" +#include "filterwidget.h" #include #include @@ -427,6 +428,7 @@ private: std::map m_RealTabPos; ExpanderWidget m_overwriteExpander, m_overwrittenExpander, m_nonconflictExpander; + FilterWidget m_advancedConflictFilter; void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); @@ -448,6 +450,7 @@ private: const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); + void restoreTabState(const QByteArray &state); void restoreConflictExpandersState(const QByteArray &state); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e391da46..b55c0f07 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -782,7 +782,7 @@ text-align: left; 0 - + Filter @@ -1201,11 +1201,6 @@ p, li { white-space: pre-wrap; } QLineEdit modidlineedit.h - - MOBase::LineEditClear - QLineEdit - lineeditclear.h - -- cgit v1.3.1 From b7580c5ad766ebf984641674fdc0f121d754c82e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 11:31:21 -0400 Subject: restore conflicts selected tab and checkboxes --- src/modinfodialog.cpp | 28 +++++++++++++++++++++++----- src/modinfodialog.h | 4 ++-- src/modinfodialog.ui | 2 +- 3 files changed, 26 insertions(+), 8 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4852150f..1dd44112 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -553,13 +553,13 @@ int ModInfoDialog::tabIndex(const QString &tabId) void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); - s.directInterface().setValue("mod_info_conflict_expanders", saveConflictExpandersState()); + s.directInterface().setValue("mod_info_conflicts", saveConflictsState()); } void ModInfoDialog::restoreState(const Settings& s) { restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - restoreConflictExpandersState(s.directInterface().value("mod_info_conflict_expanders").toByteArray()); + restoreConflictsState(s.directInterface().value("mod_info_conflicts").toByteArray()); } void ModInfoDialog::restoreTabState(const QByteArray &state) @@ -593,7 +593,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) ui->tabWidget->blockSignals(false); } -void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state) +void ModInfoDialog::restoreConflictsState(const QByteArray &state) { QDataStream stream(state); @@ -608,6 +608,20 @@ void ModInfoDialog::restoreConflictExpandersState(const QByteArray &state) 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 @@ -622,7 +636,7 @@ QByteArray ModInfoDialog::saveTabState() const return result; } -QByteArray ModInfoDialog::saveConflictExpandersState() const +QByteArray ModInfoDialog::saveConflictsState() const { QByteArray result; QDataStream stream(&result, QIODevice::WriteOnly); @@ -630,7 +644,11 @@ QByteArray ModInfoDialog::saveConflictExpandersState() const stream << m_overwriteExpander.opened() << m_overwrittenExpander.opened() - << m_nonconflictExpander.opened(); + << m_nonconflictExpander.opened() + << ui->tabConflictsTabs->currentIndex() + << ui->conflictsAdvancedShowNoConflict->isChecked() + << ui->conflictsAdvancedShowAll->isChecked() + << ui->conflictsAdvancedShowNearest->isChecked(); return result; } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 5169a993..40e45eb4 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -452,10 +452,10 @@ private: void restoreTabState(const QByteArray &state); - void restoreConflictExpandersState(const QByteArray &state); + void restoreConflictsState(const QByteArray &state); QByteArray saveTabState() const; - QByteArray saveConflictExpandersState() const; + QByteArray saveConflictsState() const; bool canHideConflictItem(const QTreeWidgetItem* item) const; bool canUnhideConflictItem(const QTreeWidgetItem* item) const; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index b55c0f07..6c1ed36d 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -402,7 +402,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - + 1 -- cgit v1.3.1 From 65428e89ad9c2489d40cc01e026e6a73e64df258 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 11:59:19 -0400 Subject: put back changes from another PR lost because of a bad rebase --- src/modinfodialog.ui | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 6c1ed36d..8744b216 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -404,7 +404,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 1 + 0 @@ -481,24 +481,21 @@ text-align: left; Qt::ElideLeft + + true + true true - - false - 2 365 - - false - 200 @@ -554,9 +551,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true @@ -624,9 +627,15 @@ text-align: left; Qt::CustomContextMenu + + QAbstractItemView::ExtendedSelection + Qt::ElideLeft + + true + true -- cgit v1.3.1 From 0f6f0c23943116ea668ab035055cb8b0e4a6574e Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 12:49:25 -0400 Subject: replaced all the manual UserRole stuff with a few constants and helper functions setConflictItem() is now used by all conflict lists to setup the data (filename, archive, etc.) and visuals (italic for archives) merged openDataFile() and previewDataFile() into their caller as they weren't used anywhere else previewDataFile() used to do a fromNativeSeparators() before previewing, moved that to previewFileWithAlternatives() instead brought overwrittenTree double click in line with overwriteTree, there's no difference between apply() and close() because there's only a close button --- src/modinfodialog.cpp | 133 +++++++++++++++++++++++++++----------------------- src/modinfodialog.h | 10 +++- src/modinfodialog.ui | 9 ++++ src/organizercore.cpp | 2 + 4 files changed, 91 insertions(+), 63 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1dd44112..e639dcc7 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -57,6 +57,10 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const auto FILENAME_USERROLE = Qt::UserRole + 1; +const auto ORIGIN_USERROLE = Qt::UserRole + 2; +const auto ARCHIVE_USERROLE = Qt::UserRole + 3; + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -744,18 +748,10 @@ QTreeWidgetItem* ModInfoDialog::createOverwriteItem( QStringList fields(relativeName); fields.append(altString); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.back().first).getName())); - item->setData(1, Qt::UserRole + 1, alternatives.back().first); - item->setData(1, Qt::UserRole + 2, archive); + const auto origin = ToQString(m_Directory->getOriginByID(alternatives.back().first).getName()); - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + setConflictItem(item, fileName, origin, archive); return item; } @@ -764,13 +760,7 @@ QTreeWidgetItem* ModInfoDialog::createNoConflictItem( bool archive, const QString& fileName, const QString& relativeName) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList({relativeName})); - item->setData(0, Qt::UserRole, fileName); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - } + setConflictItem(item, fileName, "", archive); return item; } @@ -785,16 +775,7 @@ QTreeWidgetItem* ModInfoDialog::createOverwrittenItem( fields.append(ToQString(realOrigin.getName())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); - item->setData(1, Qt::UserRole + 2, archive); - - if (archive) { - QFont font = item->font(0); - font.setItalic(true); - item->setFont(0, font); - item->setFont(1, font); - } + setConflictItem(item, fileName, ToQString(realOrigin.getName()), archive); return item; } @@ -908,9 +889,44 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( item->setText(1, relativeName); item->setText(2, after); + setConflictItem(item, fileName, "", archive); + return item; } +void ModInfoDialog::setConflictItem( + QTreeWidgetItem* item, + const QString& fileName, const QString& origin, bool archive) const +{ + item->setData(0, FILENAME_USERROLE, fileName); + item->setData(0, ORIGIN_USERROLE, origin); + item->setData(0, ARCHIVE_USERROLE, archive); + + if (archive) { + QFont font = item->font(0); + font.setItalic(true); + + for (int i=0; icolumnCount(); ++i) { + item->setFont(i, font); + } + } +} + +QString ModInfoDialog::conflictFileName(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, FILENAME_USERROLE).toString(); +} + +QString ModInfoDialog::conflictOrigin(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ORIGIN_USERROLE).toString(); +} + +bool ModInfoDialog::conflictIsArchive(const QTreeWidgetItem* conflictItem) const +{ + return conflictItem->data(0, ARCHIVE_USERROLE).toBool(); +} + void ModInfoDialog::refreshFiles() { if (m_RootPath.length() > 0) { @@ -1860,8 +1876,12 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - this->close(); - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); + const auto origin = conflictOrigin(item); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) @@ -1907,14 +1927,14 @@ void ModInfoDialog::changeConflictItemsVisibility( qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; continue; } - result = unhideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = unhideFile(renamer, conflictFileName(item)); } else { if (!canHideConflictItem(item)) { qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; continue; } - result = hideFile(renamer, item->data(0, Qt::UserRole).toString()); + result = hideFile(renamer, conflictFileName(item)); } switch (result) { @@ -1953,7 +1973,9 @@ 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) { - openDataFile(item); + if (item) { + m_OrganizerCore->executeFileVirtualized(this, conflictFileName(item)); + } } } @@ -1962,28 +1984,10 @@ 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) { - previewDataFile(item); - } -} - -void ModInfoDialog::openDataFile(const QTreeWidgetItem* item) -{ - if (!item) { - return; - } - - QFileInfo targetInfo(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->executeFileVirtualized(this, targetInfo); -} - -void ModInfoDialog::previewDataFile(const QTreeWidgetItem* item) -{ - if (!item) { - return; + if (item) { + m_OrganizerCore->previewFileWithAlternatives(this, conflictFileName(item)); + } } - - QString fileName = QDir::fromNativeSeparators(item->data(0, Qt::UserRole).toString()); - m_OrganizerCore->previewFileWithAlternatives(this, fileName); } bool ModInfoDialog::canPreviewFile(bool isArchive, const QString& filename) const @@ -2028,19 +2032,17 @@ bool ModInfoDialog::canUnhideFile(bool isArchive, const QString& filename) const bool ModInfoDialog::canHideConflictItem(const QTreeWidgetItem* item) const { - return canHideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); + return canHideFile(conflictIsArchive(item), conflictFileName(item)); } bool ModInfoDialog::canUnhideConflictItem(const QTreeWidgetItem* item) const { - return canUnhideFile(item->data(1, Qt::UserRole + 2).toBool(), item->text(0)); + return canUnhideFile(conflictIsArchive(item), conflictFileName(item)); } bool ModInfoDialog::canPreviewConflictItem(const QTreeWidgetItem* item) const { - return canPreviewFile( - item->data(1, Qt::UserRole + 2).toBool(), - item->data(0, Qt::UserRole).toString()); + return canPreviewFile(conflictIsArchive(item), conflictFileName(item)); } void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) @@ -2058,6 +2060,11 @@ void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &p 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()); @@ -2181,8 +2188,12 @@ ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); - this->accept(); + const auto origin = conflictOrigin(item); + + if (!origin.isEmpty()) { + close(); + emit modOpen(origin, TAB_CONFLICTS); + } } void ModInfoDialog::on_refreshButton_clicked() diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 40e45eb4..59912127 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -368,6 +368,7 @@ private slots: 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(); @@ -450,6 +451,13 @@ private: const QString& fileName, const QString& relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); + void setConflictItem( + QTreeWidgetItem* item, + const QString& fileName, const QString& origin, bool archive) const; + + QString conflictFileName(const QTreeWidgetItem* conflictItem) const; + QString conflictOrigin(const QTreeWidgetItem* conflictItem) const; + bool conflictIsArchive(const QTreeWidgetItem* conflictItem) const; void restoreTabState(const QByteArray &state); void restoreConflictsState(const QByteArray &state); @@ -461,8 +469,6 @@ private: bool canUnhideConflictItem(const QTreeWidgetItem* item) const; bool canPreviewConflictItem(const QTreeWidgetItem* item) const; - void openDataFile(const QTreeWidgetItem* item); - void previewDataFile(const QTreeWidgetItem* item); void changeFiletreeVisibility(bool visible); void openConflictItems(const QList& items); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 8744b216..93291c02 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -707,6 +707,15 @@ text-align: left; + + Qt::CustomContextMenu + + + QAbstractItemView::ExtendedSelection + + + true + 3 diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 3ad4e586..892162f6 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1321,6 +1321,8 @@ bool OrganizerCore::executeFileVirtualized( bool OrganizerCore::previewFileWithAlternatives( QWidget* parent, QString fileName, int selectedOrigin) { + fileName = QDir::fromNativeSeparators(fileName); + // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory -- cgit v1.3.1 From 7421aaa33b37bc13a9c5a3f5df2f06b6bf0f09e5 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 13:59:08 -0400 Subject: conflict lists - sorting on advanced list - save state of all four lists - call refreshLists() just before showing because the state has to be loaded first, which happens after the ctor --- src/modinfodialog.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/modinfodialog.h | 2 ++ src/modinfodialog.ui | 3 +++ 3 files changed, 39 insertions(+), 2 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 97507777..b8a8ae69 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -397,8 +397,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - refreshLists(); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -501,6 +499,12 @@ ModInfoDialog::~ModInfoDialog() } +int ModInfoDialog::exec() +{ + refreshLists(); + return TutorableDialog::exec(); +} + void ModInfoDialog::initINITweaks() { int numTweaks = m_Settings->beginReadArray("INI Tweaks"); @@ -558,12 +562,40 @@ 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()); } 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()); } void ModInfoDialog::restoreTabState(const QByteArray &state) diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 35f87820..85505487 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -280,6 +280,8 @@ public: **/ void openTab(int tab); + int exec() override; + void saveState(Settings& s) const; void restoreState(const Settings& s); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 93291c02..20437e58 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -716,6 +716,9 @@ text-align: left; true + + true + 3 -- cgit v1.3.1 From a6dbb1d7668a2219811fec71a55294e1db6d14aa Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 14:09:36 -0400 Subject: advanced conflict list tooltips --- src/modinfodialog.ui | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/modinfodialog.ui') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 20437e58..e009a599 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -762,6 +762,9 @@ text-align: left; + + Whether files that have no conflicts should be visible in the list + Show files that have no conflicts @@ -772,6 +775,9 @@ text-align: left; + + Shows all mods overwriting or being overwritten by this mod + Show all conflicting mods @@ -782,6 +788,9 @@ text-align: left; + + Shows only the nearest conflicting mods, in order of priority + Show nearest conflicting mod -- cgit v1.3.1 From 724fad7cb62a0ee4913ed41bde45c7564183c7af Mon Sep 17 00:00:00 2001 From: Matte A Date: Sun, 23 Jun 2019 04:38:36 +0200 Subject: Correcting minor spelling mistakes in the UI + add contributor --- src/aboutdialog.ui | 4 ++-- src/instancemanager.cpp | 2 +- src/mainwindow.cpp | 4 ++-- src/mainwindow.ui | 4 ++-- src/moapplication.cpp | 4 ++-- src/modinfodialog.cpp | 8 ++++---- src/modinfodialog.ui | 8 ++++---- src/organizercore.cpp | 6 +++--- src/overwriteinfodialog.cpp | 8 ++++---- src/settingsdialog.ui | 4 ++-- src/spawn.cpp | 2 +- src/tutorials/tutorial_conflictresolution_main.js | 2 +- src/tutorials/tutorial_firststeps_main.js | 2 +- src/tutorials/tutorial_firststeps_modinfo.js | 2 +- 14 files changed, 30 insertions(+), 30 deletions(-) (limited to 'src/modinfodialog.ui') diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 86b95c83..1b3f0ad2 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -337,7 +337,7 @@ - Jax (Swedish) + Jax, Nubbie (Swedish) @@ -361,7 +361,7 @@ - Other Supporters && Contributors + Other Supporters & Contributors diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index 5b57827a..b5130e8b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -102,7 +102,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const SelectionDialog selection( QString("%1%2") .arg(QObject::tr("Choose Instance to Delete")) - .arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), + .arg(QObject::tr("Be Careful! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), nullptr); for (const QString &instance : instanceList) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e383006b..da7c721d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -6796,7 +6796,7 @@ void MainWindow::on_bossButton_clicked() } if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); + QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occurred"), errorMessages.c_str(), QMessageBox::Ok, this); warn->setModal(false); warn->show(); } @@ -6899,7 +6899,7 @@ void MainWindow::on_saveModsButton_clicked() m_OrganizerCore.currentProfile()->writeModlistNow(true); QDateTime now = QDateTime::currentDateTime(); if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { - MessageDialog::showMessage(tr("Backup of modlist created"), this); + MessageDialog::showMessage(tr("Backup of mod list created"), this); } } diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0ee74239..6a816262 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -221,8 +221,8 @@ <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;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<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;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> +<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;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different playthroughs.</span></p> +<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;">Please note that right now your esp load order is not kept separate for different profiles.</span></p></body></html> diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 3a791827..5652833a 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -116,12 +116,12 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) } catch (const std::exception &e) { qCritical("uncaught exception in handler (object %s, eventtype %d): %s", receiver->objectName().toUtf8().constData(), event->type(), e.what()); - reportError(tr("an error occured: %1").arg(e.what())); + reportError(tr("an error occurred: %1").arg(e.what())); return false; } catch (...) { qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", receiver->objectName().toUtf8().constData(), event->type()); - reportError(tr("an error occured")); + reportError(tr("an error occurred")); return false; } } diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 2be8e481..188ea956 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1670,13 +1670,13 @@ void ModInfoDialog::delete_activated() } 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), + 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 sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } @@ -1695,12 +1695,12 @@ void ModInfoDialog::deleteTriggered() return; } else if (m_FileSelection.count() == 1) { QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + 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 sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index e009a599..eed4e31f 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -136,7 +136,7 @@ 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 wether the tweaks are really optional. + 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. @@ -383,7 +383,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 selecteable in the esp list in the main window. + 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. @@ -897,7 +897,7 @@ text-align: left; <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 Organizer2 on Nexus. Why not go there now and endorse us?</p></body></html> +<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> @@ -1067,7 +1067,7 @@ p, li { white-space: pre-wrap; } - Web page URL (only used if invalid NexusID) : + Web page URL (only used if invalid Nexus ID) : diff --git a/src/organizercore.cpp b/src/organizercore.cpp index c724e57f..2cad9ce8 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -391,7 +391,7 @@ void OrganizerCore::storeSettings() if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to update MO settings to %1: %2") + tr("An error occurred trying to update MO settings to %1: %2") .arg(iniFile, windowsErrorString(::GetLastError()))); return; } @@ -418,7 +418,7 @@ void OrganizerCore::storeSettings() : tr("Unknown error %1").arg(result); QMessageBox::critical( qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to write back MO settings to %1: %2") + tr("An error occurred trying to write back MO settings to %1: %2") .arg(writeTarget, reason)); } } @@ -1570,7 +1570,7 @@ HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, tr("MO was denied access to the Steam process. This normally indicates that " "Steam is being run as administrator while MO is not. This can cause issues " "launching the game. It is recommended to not run Steam as administrator unless " - "absolutely neccessary.\n\n" + "absolutely necessary.\n\n" "Restart MO as administrator?"), QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); if (result == QDialogButtonBox::Yes) { diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 9b3e55df..0a884ac9 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -162,13 +162,13 @@ void OverwriteInfoDialog::delete_activated() } 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), + 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 sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } @@ -187,12 +187,12 @@ void OverwriteInfoDialog::deleteTriggered() return; } else if (m_FileSelection.count() == 1) { QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + 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 sure you want to delete the selected files?"), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; } diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 08c8d9f2..faaf1653 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -441,7 +441,7 @@ If you use pre-releases, never contact me directly by e-mail or via private mess - Important: All directories have to be writeable! + Important: All directories have to be writable! @@ -1297,7 +1297,7 @@ programs you are intentionally running. Decides the amount of data printed to "ModOrganizer.log". - "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regular use. On the "Error" level the log file usually remains empty. diff --git a/src/spawn.cpp b/src/spawn.cpp index d7d5efc2..f77da35f 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -150,7 +150,7 @@ HANDLE startBinary(const QFileInfo &binary, if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { if (QMessageBox::question(QApplication::activeModalWidget(), QObject::tr("Elevation required"), QObject::tr("This process requires elevation to run.\n" - "This is a potential security risk so I highly advice you to investigate if\n" + "This is a potential security risk so I highly advise you to investigate if\n" "\"%1\"\n" "can be installed to work without elevation.\n\n" "Restart Mod Organizer as an elevated process?\n" diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js index 3c782af2..404afe5d 100644 --- a/src/tutorials/tutorial_conflictresolution_main.js +++ b/src/tutorials/tutorial_conflictresolution_main.js @@ -56,7 +56,7 @@ function getTutorialSteps() { waitForClick() }, function() { - tutorial.text = qsTr(" indicates that the mod is completely overwrtten by another. You could as well disable it."); + tutorial.text = qsTr(" indicates that the mod is completely overwritten by another. You could as well disable it."); waitForClick() }, function() { diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index ee97766b..35d1aa11 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -137,7 +137,7 @@ function getTutorialSteps() function() { tutorial.text = qsTr("A single mod may contain zero, one or multiple esps. Some or all may be optional. " - + "If in doubt, please consult the documentation of the indiviual mod. " + + "If in doubt, please consult the documentation of the individual mod. " + "To do so, right-click the mod and select \"Information\".") highlightItem("modList", true) manager.activateTutorial("ModInfoDialog", "tutorial_firststeps_modinfo.js") diff --git a/src/tutorials/tutorial_firststeps_modinfo.js b/src/tutorials/tutorial_firststeps_modinfo.js index 50c38345..1ed0dab7 100644 --- a/src/tutorials/tutorial_firststeps_modinfo.js +++ b/src/tutorials/tutorial_firststeps_modinfo.js @@ -16,7 +16,7 @@ function getTutorialSteps() }, function() { unhighlight() - tutorial.text = qsTr("We may re-visit this screen in later tutorials.") + tutorial.text = qsTr("We may revisit this screen in later tutorials.") waitForClick() } ] -- 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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/modinfodialog.ui') 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/modinfodialog.ui') 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 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/modinfodialog.ui') 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()
Sorry, there is no description available for this mod. :(