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/modinfodialogimages.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/modinfodialogimages.cpp (limited to 'src/modinfodialogimages.cpp') 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)); +} -- 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/modinfodialogimages.cpp') 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/modinfodialogimages.cpp') 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 b5c47eb161f6e20c9275909d32202e52bf3ba4a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:24:44 -0400 Subject: pass parent widget to tabs for dialogs finished moving over conflict tab to its own set of files --- src/modinfodialog.cpp | 444 +++++++---------------------------------- src/modinfodialog.h | 62 ++---- src/modinfodialogconflicts.cpp | 441 +++++++++++++++++++++++++++++++++++++--- src/modinfodialogconflicts.h | 80 +++++++- src/modinfodialogesps.cpp | 30 +-- src/modinfodialogesps.h | 5 +- src/modinfodialogimages.cpp | 2 +- src/modinfodialogimages.h | 4 +- src/modinfodialogtextfiles.cpp | 17 +- src/modinfodialogtextfiles.h | 13 +- 10 files changed, 616 insertions(+), 482 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index c24176b3..d52103b9 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -63,20 +63,8 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; +const int max_scan_for_context_menu = 50; -std::vector> ModInfoDialogTab::createTabs( - Ui::ModInfoDialog* ui) -{ - std::vector> v; - - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - v.push_back(std::make_unique(ui)); - - return v; -} bool ModInfoDialogTab::canClose() { @@ -93,6 +81,26 @@ void ModInfoDialogTab::restoreState(const Settings& s) // no-op } +void ModInfoDialogTab::setMod(ModInfo::Ptr, MOShared::FilesOrigin*) +{ + // no-op +} + +void ModInfoDialogTab::update() +{ + // no-op +} + +void ModInfoDialogTab::emitOriginModified(int originID) +{ + emit originModified(originID); +} + +void ModInfoDialogTab::emitModOpen(QString name) +{ + emit modOpen(name); +} + class ModFileListWidget : public QListWidgetItem { friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); @@ -109,20 +117,16 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS return LHS.m_SortValue < RHS.m_SortValue; } -// if there are more than 50 selected items in the conflict tree or filetree, -// don't bother checking whether menu items apply to them, just show all of them -const int max_scan_for_context_menu = 50; - bool canPreviewFile( - PluginContainer* pluginContainer, bool isArchive, const QString& filename) + PluginContainer& pluginContainer, bool isArchive, const QString& filename) { if (isArchive) { return false; } const auto ext = QFileInfo(filename).suffix(); - return pluginContainer->previewGenerator().previewSupported(ext); + return pluginContainer.previewGenerator().previewSupported(ext); } bool canOpenFile(bool isArchive, const QString&) @@ -161,6 +165,18 @@ bool canUnhideFile(bool isArchive, const QString& filename) return true; } +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName) +{ + const QString newName = oldName + ModInfo::s_HiddenExt; + return renamer.rename(oldName, newName); +} + +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName) +{ + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + return renamer.rename(oldName, newName); +} + ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), @@ -172,7 +188,20 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo { ui->setupUi(this); - m_tabs = ModInfoDialogTab::createTabs(ui); + m_tabs = createTabs(); + + for (std::size_t i=0; i(i)); + }); + } this->setWindowTitle(modInfo->name()); this->setWindowModality(Qt::WindowModal); @@ -223,12 +252,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo } } - // refresh everything but the conflict lists, which are done in exec() because - // they depend on restoring the state to some widgets; this refresh has to be - // done here because some of the checks below depend on the ui to decide which - // tabs to enable - refreshFiles(); - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); @@ -283,8 +306,11 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo if (ui->tabWidget->currentIndex() == TAB_NEXUS) { activateNexusTab(); } -} + for (auto& tab : m_tabs) { + tab->setMod(m_ModInfo, m_Origin); + } +} ModInfoDialog::~ModInfoDialog() { @@ -301,11 +327,23 @@ ModInfoDialog::~ModInfoDialog() delete m_Settings; } +std::vector> ModInfoDialog::createTabs() +{ + std::vector> v; + + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique(this, ui)); + v.push_back(std::make_unique( + this, ui, *m_OrganizerCore, *m_PluginContainer)); + + return v; +} int ModInfoDialog::exec() { - // no need to refresh the other stuff, that was done in the constructor - refreshConflictLists(true, true); + refreshLists(); return TutorableDialog::exec(); } @@ -412,17 +450,15 @@ QByteArray ModInfoDialog::saveTabState() const void ModInfoDialog::refreshLists() { - refreshConflictLists(true, true); + for (auto& tab : m_tabs) { + tab->update(); + } + refreshFiles(); } void ModInfoDialog::refreshFiles() { - // clearing - for (auto& tab : m_tabs) { - tab->clear(); - } - if (m_RootPath.length() > 0) { QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { @@ -936,7 +972,7 @@ void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) const QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (!canPreviewFile(m_PluginContainer, false, fileName)) { + if (!canPreviewFile(*m_PluginContainer, false, fileName)) { enablePreview = false; } @@ -1054,342 +1090,6 @@ void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) } } -FileRenamer::RenameResults ModInfoDialog::hideFile(FileRenamer& renamer, const QString &oldName) -{ - const QString newName = oldName + ModInfo::s_HiddenExt; - return renamer.rename(oldName, newName); -} - -FileRenamer::RenameResults ModInfoDialog::unhideFile(FileRenamer& renamer, const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - return renamer.rename(oldName, newName); -} - -void ModInfoDialog::changeConflictItemsVisibility( - const QList& items, bool visible) -{ - bool changed = false; - bool stop = false; - - qDebug().nospace() - << (visible ? "unhiding" : "hiding") << " " - << items.size() << " conflict files"; - - QFlags flags = - (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); - - if (items.size() > 1) { - flags |= FileRenamer::MULTIPLE; - } - - FileRenamer renamer(this, flags); - - for (const auto* item : items) { - if (stop) { - break; - } - - const auto* ci = dynamic_cast(item); - if (!ci) { - continue; - } - - auto result = FileRenamer::RESULT_CANCEL; - - if (visible) { - if (!ci->canUnhide()) { - qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; - continue; - } - result = unhideFile(renamer, ci->fileName()); - - } else { - if (!ci->canHide()) { - qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; - continue; - } - result = hideFile(renamer, ci->fileName()); - } - - switch (result) { - case FileRenamer::RESULT_OK: { - // will trigger a refresh at the end - changed = true; - break; - } - - case FileRenamer::RESULT_SKIP: { - // nop - break; - } - - case FileRenamer::RESULT_CANCEL: { - // stop right now, but make sure to refresh if needed - stop = true; - break; - } - } - } - - qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; - - if (changed) { - qDebug().nospace() << "triggering refresh"; - if (m_Origin) { - emit originModified(m_Origin->getID()); - } - refreshLists(); - } -} - -void ModInfoDialog::openConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->executeFileVirtualized(this, ci->fileName()); - } - } -} - -void ModInfoDialog::previewConflictItems(const QList& items) -{ - // the menu item is only shown for a single selection, but handle all of them - // in case this changes - for (auto* item : items) { - if (auto* ci=dynamic_cast(item)) { - m_OrganizerCore->previewFileWithAlternatives(this, ci->fileName()); - } - } -} - -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwriteTree); -} - -void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->overwrittenTree); -} - -void ModInfoDialog::on_noConflictTree_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->noConflictTree); -} - -void ModInfoDialog::on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos) -{ - showConflictMenu(pos, ui->conflictsAdvancedList); -} - -void ModInfoDialog::showConflictMenu(const QPoint &pos, QTreeWidget* tree) -{ - auto actions = createConflictMenuActions(tree->selectedItems()); - - QMenu menu; - - // open - if (actions.open) { - connect(actions.open, &QAction::triggered, [&]{ - openConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.open); - } - - // preview - if (actions.preview) { - connect(actions.preview, &QAction::triggered, [&]{ - previewConflictItems(tree->selectedItems()); - }); - - menu.addAction(actions.preview); - } - - // hide - if (actions.hide) { - connect(actions.hide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), false); - }); - - menu.addAction(actions.hide); - } - - // unhide - if (actions.unhide) { - connect(actions.unhide, &QAction::triggered, [&]{ - changeConflictItemsVisibility(tree->selectedItems(), true); - }); - - menu.addAction(actions.unhide); - } - - // goto - if (actions.gotoMenu) { - menu.addMenu(actions.gotoMenu); - - for (auto* a : actions.gotoActions) { - connect(a, &QAction::triggered, [&, name=a->text()]{ - close(); - emit modOpen(name, TAB_CONFLICTS); - }); - - actions.gotoMenu->addAction(a); - } - } - - if (!menu.isEmpty()) { - menu.exec(tree->viewport()->mapToGlobal(pos)); - } -} - -ModInfoDialog::ConflictActions ModInfoDialog::createConflictMenuActions( - const QList& selection) -{ - if (selection.empty()) { - return {}; - } - - bool enableHide = true; - bool enableUnhide = true; - bool enableOpen = true; - bool enablePreview = true; - bool enableGoto = true; - - if (selection.size() == 1) { - // this is a single selection - const auto* ci = dynamic_cast(selection[0]); - if (!ci) { - return {}; - } - - enableHide = ci->canHide(); - enableUnhide = ci->canUnhide(); - enableOpen = ci->canOpen(); - enablePreview = ci->canPreview(m_PluginContainer); - enableGoto = ci->hasAlts(); - } - else { - // this is a multiple selection, don't show open/preview so users don't open - // a thousand files - enableOpen = false; - enablePreview = false; - - // don't bother with this on multiple selection, at least for now - enableGoto = false; - - if (selection.size() < max_scan_for_context_menu) { - // if the number of selected items is low, checking them to accurately - // show the menu items is worth it - enableHide = false; - enableUnhide = false; - - for (const auto* item : selection) { - if (const auto* ci=dynamic_cast(item)) { - if (ci->canHide()) { - enableHide = true; - } - - if (ci->canUnhide()) { - enableUnhide = true; - } - - if (enableHide && enableUnhide && enableGoto) { - // found all, no need to check more - break; - } - } - } - } - } - - ConflictActions actions; - - actions.hide = new QAction(tr("Hide"), this); - actions.hide->setEnabled(enableHide); - - // note that it is possible for hidden files to appear if they override other - // hidden files from another mod - actions.unhide = new QAction(tr("Unhide"), this); - actions.unhide->setEnabled(enableUnhide); - - actions.open = new QAction(tr("Open/Execute"), this); - actions.open->setEnabled(enableOpen); - - actions.preview = new QAction(tr("Preview"), this); - actions.preview->setEnabled(enablePreview); - - actions.gotoMenu = new QMenu(tr("Go to..."), this); - actions.gotoMenu->setEnabled(enableGoto); - - if (enableGoto) { - actions.gotoActions = createGotoActions(selection); - } - - return actions; -} - -std::vector ModInfoDialog::createGotoActions(const QList& selection) -{ - if (!m_Origin || selection.size() != 1) { - return {}; - } - - const auto* item = dynamic_cast(selection[0]); - if (!item) { - return {}; - } - - auto file = m_Origin->findFile(item->fileIndex()); - if (!file) { - return {}; - } - - - std::vector mods; - - // add all alternatives - for (const auto& alt : file->getAlternatives()) { - const auto& o = m_Directory->getOriginByID(alt.first); - if (o.getID() != m_Origin->getID()) { - mods.push_back(ToQString(o.getName())); - } - } - - // add the real origin if different from this mod - const FilesOrigin& realOrigin = m_Directory->getOriginByID(file->getOrigin()); - if (realOrigin.getID() != m_Origin->getID()) { - mods.push_back(ToQString(realOrigin.getName())); - } - - std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { - return (QString::localeAwareCompare(a, b) < 0); - }); - - std::vector actions; - - for (const auto& name : mods) { - actions.push_back(new QAction(name, this)); - } - - return actions; -} - -void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - if (const auto* ci=dynamic_cast(item)) { - const auto origin = ci->altOrigin(); - - if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); - } - } -} - void ModInfoDialog::on_refreshButton_clicked() { if (m_ModInfo->getNexusID() > 0) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 53b5d94a..2a3ee2d8 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -54,12 +54,11 @@ class CategoryFactory; class TextEditor; -class ModInfoDialogTab +class ModInfoDialogTab : public QObject { -public: - static std::vector> createTabs( - Ui::ModInfoDialog* ui); + Q_OBJECT; +public: ModInfoDialogTab() = default; ModInfoDialogTab(const ModInfoDialogTab&) = delete; ModInfoDialogTab& operator=(const ModInfoDialogTab&) = delete; @@ -72,6 +71,17 @@ public: virtual bool canClose(); virtual void saveState(Settings& s); virtual void restoreState(const Settings& s); + + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + virtual void update(); + +signals: + void originModified(int originID); + void modOpen(QString name); + +protected: + void emitOriginModified(int originID); + void emitModOpen(QString name); }; @@ -89,11 +99,14 @@ protected: }; -bool canPreviewFile(PluginContainer* pluginContainer, bool isArchive, const QString& filename); +bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); bool canHideFile(bool isArchive, const QString& filename); bool canUnhideFile(bool isArchive, const QString& filename); +FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); +FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); + /** * this is a larger dialog used to visualise information about the mod. @@ -185,8 +198,6 @@ private: bool recursiveDelete(const QModelIndex &index); void deleteFile(const QModelIndex &index); void saveCategories(QTreeWidgetItem *currentNode); - FileRenamer::RenameResults hideFile(FileRenamer& renamer, const QString &oldName); - FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldName); void addCheckedCategories(QTreeWidgetItem *tree); void refreshPrimaryCategoriesBox(); @@ -216,12 +227,6 @@ private slots: void on_tabWidget_currentChanged(int index); void on_primaryCategoryBox_currentIndexChanged(int index); void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); - void on_overwriteTree_customContextMenuRequested(const QPoint &pos); - void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); - void on_noConflictTree_customContextMenuRequested(const QPoint &pos); - void on_conflictsAdvancedList_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); @@ -235,22 +240,6 @@ private slots: private: using FileEntry = MOShared::FileEntry; - struct ConflictActions - { - QAction* hide; - QAction* unhide; - QAction* open; - QAction* preview; - QMenu* gotoMenu; - std::vector gotoActions; - - ConflictActions() : - hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), - gotoMenu(nullptr) - { - } - }; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; @@ -285,27 +274,14 @@ private: std::map m_RealTabPos; + std::vector> createTabs(); - void refreshConflictLists(bool refreshGeneral, bool refreshAdvanced); void refreshFiles(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void changeFiletreeVisibility(bool visible); - - void openConflictItems(const QList& items); - void previewConflictItems(const QList& items); - void changeConflictItemsVisibility( - const QList& items, bool visible); - - void showConflictMenu(const QPoint &pos, QTreeWidget* tree); - - ConflictActions createConflictMenuActions( - const QList& selection); - - std::vector createGotoActions( - const QList& selection); }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 17313436..22306d0c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -5,6 +5,11 @@ using namespace MOShared; using namespace MOBase; +// if there are more than 50 selected items in the conflict tree or filetree, +// don't bother checking whether menu items apply to them, just show all of them +const int max_scan_for_context_menu = 50; + + int naturalCompare(const QString& a, const QString& b) { static QCollator c = []{ @@ -89,7 +94,7 @@ public: return canOpenFile(isArchive(), fileName()); } - bool canPreview(PluginContainer* pluginContainer) const + bool canPreview(PluginContainer& pluginContainer) const { return canPreviewFile(pluginContainer, isArchive(), fileName()); } @@ -109,9 +114,45 @@ public: }; -ConflictsTab::ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_general(ui, oc), m_advanced(ui, oc) +ConflictsTab::ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin) : + m_parent(parent), ui(ui), m_core(oc), m_plugin(plugin), + m_origin(nullptr), m_general(this, ui, oc), m_advanced(this, ui, oc) +{ + connect( + &m_general, &GeneralConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); + + connect( + &m_advanced, &AdvancedConflictsTab::modOpen, + [&](const QString& name){ emitModOpen(name); }); +} + +void ConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +{ + m_mod = mod; + m_origin = origin; + + m_general.setMod(mod, origin); + m_advanced.setMod(mod, origin); +} + +void ConflictsTab::update() { + m_general.update(); + m_advanced.update(); +} + +void ConflictsTab::clear() +{ + m_general.clear(); + m_advanced.clear(); +} + +bool ConflictsTab::feedFile(const QString&, const QString&) +{ + return false; } void ConflictsTab::saveState(Settings& s) @@ -132,9 +173,307 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +void ConflictsTab::changeItemsVisibility( + const QList& items, bool visible) +{ + bool changed = false; + bool stop = false; + + qDebug().nospace() + << (visible ? "unhiding" : "hiding") << " " + << items.size() << " conflict files"; + + QFlags flags = + (visible ? FileRenamer::UNHIDE : FileRenamer::HIDE); + + if (items.size() > 1) { + flags |= FileRenamer::MULTIPLE; + } + + FileRenamer renamer(m_parent, flags); + + for (const auto* item : items) { + if (stop) { + break; + } + + const auto* ci = dynamic_cast(item); + if (!ci) { + continue; + } + + auto result = FileRenamer::RESULT_CANCEL; + + if (visible) { + if (!ci->canUnhide()) { + qDebug().nospace() << "cannot unhide " << item->text(0) << ", skipping"; + continue; + } + + result = unhideFile(renamer, ci->fileName()); + + } else { + if (!ci->canHide()) { + qDebug().nospace() << "cannot hide " << item->text(0) << ", skipping"; + continue; + } + + result = hideFile(renamer, ci->fileName()); + } + + switch (result) { + case FileRenamer::RESULT_OK: { + // will trigger a refresh at the end + changed = true; + break; + } + + case FileRenamer::RESULT_SKIP: { + // nop + break; + } + + case FileRenamer::RESULT_CANCEL: { + // stop right now, but make sure to refresh if needed + stop = true; + break; + } + } + } + + qDebug().nospace() << (visible ? "unhiding" : "hiding") << " conflict files done"; + + if (changed) { + qDebug().nospace() << "triggering refresh"; + + if (m_origin) { + emit originModified(m_origin->getID()); + } + + update(); + } +} + +void ConflictsTab::openItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.executeFileVirtualized(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::previewItems(const QList& items) +{ + // the menu item is only shown for a single selection, but handle all of them + // in case this changes + for (auto* item : items) { + if (auto* ci=dynamic_cast(item)) { + m_core.previewFileWithAlternatives(m_parent, ci->fileName()); + } + } +} + +void ConflictsTab::showContextMenu(const QPoint &pos, QTreeWidget* tree) +{ + auto actions = createMenuActions(tree->selectedItems()); + + QMenu menu; + + // open + if (actions.open) { + connect(actions.open, &QAction::triggered, [&]{ + openItems(tree->selectedItems()); + }); + + menu.addAction(actions.open); + } + + // preview + if (actions.preview) { + connect(actions.preview, &QAction::triggered, [&]{ + previewItems(tree->selectedItems()); + }); + + menu.addAction(actions.preview); + } + + // hide + if (actions.hide) { + connect(actions.hide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), false); + }); + + menu.addAction(actions.hide); + } + + // unhide + if (actions.unhide) { + connect(actions.unhide, &QAction::triggered, [&]{ + changeItemsVisibility(tree->selectedItems(), true); + }); + + menu.addAction(actions.unhide); + } + + // goto + if (actions.gotoMenu) { + menu.addMenu(actions.gotoMenu); + + for (auto* a : actions.gotoActions) { + connect(a, &QAction::triggered, [&, name=a->text()]{ + emitModOpen(name); + }); + + actions.gotoMenu->addAction(a); + } + } + + if (!menu.isEmpty()) { + menu.exec(tree->viewport()->mapToGlobal(pos)); + } +} + +ConflictsTab::Actions ConflictsTab::createMenuActions( + const QList& selection) +{ + if (selection.empty()) { + return {}; + } + + bool enableHide = true; + bool enableUnhide = true; + bool enableOpen = true; + bool enablePreview = true; + bool enableGoto = true; + + if (selection.size() == 1) { + // this is a single selection + const auto* ci = dynamic_cast(selection[0]); + if (!ci) { + return {}; + } + + enableHide = ci->canHide(); + enableUnhide = ci->canUnhide(); + enableOpen = ci->canOpen(); + enablePreview = ci->canPreview(m_plugin); + enableGoto = ci->hasAlts(); + } + else { + // this is a multiple selection, don't show open/preview so users don't open + // a thousand files + enableOpen = false; + enablePreview = false; + + // don't bother with this on multiple selection, at least for now + enableGoto = false; + + if (selection.size() < max_scan_for_context_menu) { + // if the number of selected items is low, checking them to accurately + // show the menu items is worth it + enableHide = false; + enableUnhide = false; + + for (const auto* item : selection) { + if (const auto* ci=dynamic_cast(item)) { + if (ci->canHide()) { + enableHide = true; + } + + if (ci->canUnhide()) { + enableUnhide = true; + } + + if (enableHide && enableUnhide && enableGoto) { + // found all, no need to check more + break; + } + } + } + } + } + + Actions actions; + + actions.hide = new QAction(tr("Hide"), m_parent); + actions.hide->setEnabled(enableHide); + + // note that it is possible for hidden files to appear if they override other + // hidden files from another mod + actions.unhide = new QAction(tr("Unhide"), m_parent); + actions.unhide->setEnabled(enableUnhide); + + actions.open = new QAction(tr("Open/Execute"), m_parent); + actions.open->setEnabled(enableOpen); + + actions.preview = new QAction(tr("Preview"), m_parent); + actions.preview->setEnabled(enablePreview); + + actions.gotoMenu = new QMenu(tr("Go to..."), m_parent); + actions.gotoMenu->setEnabled(enableGoto); + + if (enableGoto) { + actions.gotoActions = createGotoActions(selection); + } + + return actions; +} + +std::vector ConflictsTab::createGotoActions( + const QList& selection) +{ + if (!m_origin || selection.size() != 1) { + return {}; + } + + const auto* item = dynamic_cast(selection[0]); + if (!item) { + return {}; + } + + auto file = m_origin->findFile(item->fileIndex()); + if (!file) { + return {}; + } + + + std::vector mods; + const auto& ds = *m_core.directoryStructure(); + + // add all alternatives + for (const auto& alt : file->getAlternatives()) { + const auto& o = ds.getOriginByID(alt.first); + if (o.getID() != m_origin->getID()) { + mods.push_back(ToQString(o.getName())); + } + } + + // add the real origin if different from this mod + const FilesOrigin& realOrigin = ds.getOriginByID(file->getOrigin()); + if (realOrigin.getID() != m_origin->getID()) { + mods.push_back(ToQString(realOrigin.getName())); + } + + std::sort(mods.begin(), mods.end(), [](const auto& a, const auto& b) { + return (QString::localeAwareCompare(a, b) < 0); + }); + + std::vector actions; + + for (const auto& name : mods) { + actions.push_back(new QAction(name, m_parent)); + } + + return actions; +} -GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +GeneralConflictsTab::GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { m_expanders.overwrite.set(ui->overwriteExpander1, ui->overwriteTree1, true); m_expanders.overwritten.set(ui->overwrittenExpander1, ui->overwrittenTree1, true); @@ -143,6 +482,33 @@ GeneralConflictsTab::GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& o QObject::connect( ui->overwriteTree1, &QTreeWidget::itemDoubleClicked, [&](auto* item, int col){ onOverwriteActivated(item, col); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::itemDoubleClicked, + [&](auto* item, int col){ onOverwrittenActivated(item, col); }); + + QObject::connect( + ui->overwriteTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwriteTree1); }); + + QObject::connect( + ui->overwrittenTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->overwrittenTree1); }); + + QObject::connect( + ui->noConflictTree1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->noConflictTree1); }); +} + +void GeneralConflictsTab::clear() +{ + ui->overwriteTree1->clear(); + ui->overwrittenTree1->clear(); + ui->noConflictTree1->clear(); + + ui->overwriteCount1->display(0); + ui->overwrittenCount1->display(0); + ui->noConflictCount1->display(0); } void GeneralConflictsTab::saveState(Settings& s) @@ -198,24 +564,20 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::rebuild(ModInfo::Ptr mod, FilesOrigin* origin) +void GeneralConflictsTab::setMod(ModInfo::Ptr mod, FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void GeneralConflictsTab::update() { + clear(); + int numNonConflicting = 0; int numOverwrite = 0; int numOverwritten = 0; - ui->overwriteTree1->clear(); - ui->overwrittenTree1->clear(); - ui->noConflictTree1->clear(); - if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); @@ -307,15 +669,26 @@ void GeneralConflictsTab::onOverwriteActivated(QTreeWidgetItem *item, int) const auto origin = ci->altOrigin(); if (!origin.isEmpty()) { - close(); - emit modOpen(origin, TAB_CONFLICTS); + emit modOpen(origin); } } } +void GeneralConflictsTab::onOverwrittenActivated(QTreeWidgetItem *item, int) +{ + if (const auto* ci=dynamic_cast(item)) { + const auto origin = ci->altOrigin(); + + if (!origin.isEmpty()) { + emit modOpen(origin); + } + } +} -AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc) - : ui(ui), m_core(oc), m_origin(nullptr) + +AdvancedConflictsTab::AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc) + : m_tab(tab), ui(ui), m_core(oc), m_origin(nullptr) { // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList1->setItemDelegateForColumn( @@ -327,22 +700,31 @@ AdvancedConflictsTab::AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& // don't elide the overwritten by column so that the nearest are visible - QObject::connect(ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNoConflict1, &QCheckBox::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowAll1, &QRadioButton::clicked, + [&]{ update(); }); - QObject::connect(ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, [&] { - update(); - }); + QObject::connect( + ui->conflictsAdvancedShowNearest1, &QRadioButton::clicked, + [&]{ update(); }); + + QObject::connect( + ui->conflictsAdvancedList1, &QTreeWidget::customContextMenuRequested, + [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList1); }); m_filter.set(ui->conflictsAdvancedFilter1); m_filter.changed = [&]{ update(); }; } +void AdvancedConflictsTab::clear() +{ + ui->conflictsAdvancedList1->clear(); +} + void AdvancedConflictsTab::saveState(Settings& s) { s.directInterface().setValue( @@ -358,8 +740,7 @@ void AdvancedConflictsTab::saveState(Settings& s) << ui->conflictsAdvancedShowNearest1->isChecked(); s.directInterface().setValue( - "mod_info_conflicts_advanced_options", - ui->conflictsAdvancedList1->header()->saveState()); + "mod_info_conflicts_advanced_options", result); } void AdvancedConflictsTab::restoreState(const Settings& s) @@ -383,17 +764,15 @@ void AdvancedConflictsTab::restoreState(const Settings& s) } } -void AdvancedConflictsTab::rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) +void AdvancedConflictsTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; m_origin = origin; - - update(); } void AdvancedConflictsTab::update() { - ui->conflictsAdvancedList1->clear(); + clear(); if (m_origin != nullptr) { const auto rootPath = m_mod->absolutePath(); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 894f9dd4..5843884a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -4,23 +4,33 @@ #include "modinfodialog.h" #include "expanderwidget.h" -class GeneralConflictsTab +class ConflictsTab; + +class GeneralConflictsTab : public QObject { + Q_OBJECT; + public: - GeneralConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + GeneralConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: struct Expanders { ExpanderWidget overwrite, overwritten, nonconflict; }; + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -41,21 +51,34 @@ private: const QString& fileName, const QString& relativeName); void onOverwriteActivated(QTreeWidgetItem* item, int); + void onOverwrittenActivated(QTreeWidgetItem *item, int); + + void onOverwriteTreeContext(const QPoint &pos); + void onOverwrittenTreeContext(const QPoint &pos); + void onNoConflictTreeContext(const QPoint &pos); }; -class AdvancedConflictsTab +class AdvancedConflictsTab : public QObject { + Q_OBJECT; + public: - AdvancedConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + AdvancedConflictsTab( + ConflictsTab* tab, Ui::ModInfoDialog* ui, OrganizerCore& oc); + void clear(); void saveState(Settings& s); void restoreState(const Settings& s); - void rebuild(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); void update(); +signals: + void modOpen(QString name); + private: + ConflictsTab* m_tab; Ui::ModInfoDialog* ui; OrganizerCore& m_core; ModInfo::Ptr m_mod; @@ -71,16 +94,59 @@ private: class ConflictsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ConflictsTab(Ui::ModInfoDialog* ui, OrganizerCore& oc); + ConflictsTab( + QWidget* parent, Ui::ModInfoDialog* ui, + OrganizerCore& oc, PluginContainer& plugin); + + void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) override; + void update() override; + void clear() override; + bool feedFile(const QString& rootPath, const QString& filename) override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + void openItems(const QList& items); + void previewItems(const QList& items); + + void changeItemsVisibility( + const QList& items, bool visible); + + void showContextMenu(const QPoint &pos, QTreeWidget* tree); + private: + struct Actions + { + QAction* hide; + QAction* unhide; + QAction* open; + QAction* preview; + QMenu* gotoMenu; + std::vector gotoActions; + + Actions() : + hide(nullptr), unhide(nullptr), open(nullptr), preview(nullptr), + gotoMenu(nullptr) + { + } + }; + + QWidget* m_parent; Ui::ModInfoDialog* ui; + OrganizerCore& m_core; + PluginContainer& m_plugin; + ModInfo::Ptr m_mod; + MOShared::FilesOrigin* m_origin; GeneralConflictsTab m_general; AdvancedConflictsTab m_advanced; + + Actions createMenuActions(const QList& selection); + + std::vector createGotoActions( + const QList& selection); }; #endif // MODINFODIALOGCONFLICTS_H diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 32dcdb35..6c87b10d 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -123,20 +123,20 @@ private: }; -ESPsTab::ESPsTab(Ui::ModInfoDialog* ui) - : ui(ui) +ESPsTab::ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui) + : m_parent(parent), ui(ui) { QObject::connect( - ui->activateESP1, &QToolButton::clicked, [&]{ onActivate(); }); + ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); QObject::connect( - ui->deactivateESP1, &QToolButton::clicked, [&]{ onDeactivate(); }); + ui->deactivateESP, &QToolButton::clicked, [&]{ onDeactivate(); }); } void ESPsTab::clear() { - ui->inactiveESPList1->clear(); - ui->activeESPList1->clear(); + ui->inactiveESPList->clear(); + ui->activeESPList->clear(); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -152,9 +152,9 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) auto* item = new ESPItem({rootPath, relativePath}); if (item->esp().isActive()) { - ui->activeESPList1->addItem(item); + ui->activeESPList->addItem(item); } else { - ui->inactiveESPList1->addItem(item); + ui->inactiveESPList->addItem(item); } return true; @@ -188,7 +188,7 @@ void ESPsTab::onActivate() bool okClicked = false; newName = QInputDialog::getText( - ui->tabESPs, + m_parent, QObject::tr("File Exists"), QObject::tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, file.fileName(), &okClicked); @@ -203,8 +203,8 @@ void ESPsTab::onActivate() } if (esp.activate(newName)) { - ui->inactiveESPList1->takeItem(ui->inactiveESPList1->row(item)); - ui->activeESPList1->addItem(item); + ui->inactiveESPList->takeItem(ui->inactiveESPList->row(item)); + ui->activeESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -246,8 +246,8 @@ void ESPsTab::onDeactivate() } if (esp.deactivate(newName)) { - ui->activeESPList1->takeItem(ui->activeESPList1->row(item)); - ui->inactiveESPList1->addItem(item); + ui->activeESPList->takeItem(ui->activeESPList->row(item)); + ui->inactiveESPList->addItem(item); item->setESP(esp); } else { reportError(QObject::tr("Failed to move file")); @@ -256,7 +256,7 @@ void ESPsTab::onDeactivate() ESPItem* ESPsTab::selectedInactive() { - auto* item = ui->inactiveESPList1->currentItem(); + auto* item = ui->inactiveESPList->currentItem(); if (!item) { return nullptr; } @@ -272,7 +272,7 @@ ESPItem* ESPsTab::selectedInactive() ESPItem* ESPsTab::selectedActive() { - auto* item = ui->activeESPList1->currentItem(); + auto* item = ui->activeESPList->currentItem(); if (!item) { return nullptr; } diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index a46677cb..8da70377 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -7,13 +7,16 @@ class ESPItem; class ESPsTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ESPsTab(Ui::ModInfoDialog* ui); + ESPsTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; private: + QWidget* m_parent; Ui::ModInfoDialog* ui; void onActivate(); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1659bed3..752c8d0c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -80,7 +80,7 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) } -ImagesTab::ImagesTab(Ui::ModInfoDialog* ui) +ImagesTab::ImagesTab(QWidget*, Ui::ModInfoDialog* ui) : ui(ui), m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 73fb5936..a40904f7 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -31,8 +31,10 @@ private: class ImagesTab : public ModInfoDialogTab { + Q_OBJECT; + public: - ImagesTab(Ui::ModInfoDialog* ui); + ImagesTab(QWidget* parent, Ui::ModInfoDialog* ui); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index cf800aa6..0dd6f06e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -21,8 +21,9 @@ private: }; -GenericFilesTab::GenericFilesTab(QListWidget* list, QSplitter* sp, TextEditor* e) - : m_list(list), m_editor(e) +GenericFilesTab::GenericFilesTab( + QWidget* parent, QListWidget* list, QSplitter* sp, TextEditor* e) + : m_parent(parent), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -48,7 +49,7 @@ bool GenericFilesTab::canClose() } const int res = QMessageBox::question( - m_list, + m_parent, QObject::tr("Save changes?"), QObject::tr("Save changes to \"%1\"?").arg(m_editor->filename()), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); @@ -108,8 +109,9 @@ void GenericFilesTab::select(FileListItem* item) } -TextFilesTab::TextFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) +TextFilesTab::TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -128,8 +130,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab(Ui::ModInfoDialog* ui) - : GenericFilesTab(ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) +IniFilesTab::IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui) + : GenericFilesTab( + parent, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 8725ea09..d30e20d7 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -10,17 +10,22 @@ class TextEditor; class GenericFilesTab : public ModInfoDialogTab { -public: - GenericFilesTab(QListWidget* list, QSplitter* splitter, TextEditor* editor); + Q_OBJECT; +public: void clear() override; bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; protected: + QWidget* m_parent; QListWidget* m_list; TextEditor* m_editor; + GenericFilesTab( + QWidget* parent, + QListWidget* list, QSplitter* splitter, TextEditor* editor); + virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; private: @@ -32,7 +37,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab(Ui::ModInfoDialog* ui); + TextFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -42,7 +47,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab(Ui::ModInfoDialog* ui); + IniFilesTab(QWidget* parent, Ui::ModInfoDialog* ui); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From 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/modinfodialogimages.cpp') 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 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/modinfodialogimages.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 71e514b2..be7d4aa4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,24 +19,8 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" -#include "mainwindow.h" - -#include "modidlineedit.h" -#include "iplugingame.h" -#include "nexusinterface.h" -#include "report.h" -#include "utility.h" -#include "messagedialog.h" -#include "bbcode.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "categories.h" +#include "plugincontainer.h" #include "organizercore.h" -#include "pluginlistsortproxy.h" -#include "previewgenerator.h" -#include "previewdialog.h" -#include "texteditor.h" - #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -45,23 +29,6 @@ along with Mod Organizer. If not, see . #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - - using namespace MOBase; using namespace MOShared; @@ -144,13 +111,32 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) - : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_Origin(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) +ModInfoDialog::ModInfoDialog( + ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, + PluginContainer *pluginContainer, QWidget *parent) : + TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), + m_ModInfo(modInfo), m_RootPath(modInfo->absolutePath()), + m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), + m_Origin(nullptr) { ui->setupUi(this); + auto* ds = m_OrganizerCore->directoryStructure(); + if (ds->originExists(ToWString(m_ModInfo->name()))) { + m_Origin = &ds->getOriginByName(ToWString(m_ModInfo->name())); + if (m_Origin->isDisabled()) { + m_Origin = nullptr; + } + } + + this->setWindowTitle(m_ModInfo->name()); + this->setWindowModality(Qt::WindowModal); + + auto* sc = new QShortcut(QKeySequence::Delete, this); + connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + m_tabs = createTabs(); + bool tabSelected = false; for (std::size_t i=0; i(i)); }); - } - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); + bool enabled = true; - m_RootPath = modInfo->absolutePath(); - - auto* sc = new QShortcut(QKeySequence::Delete, this); - connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); - - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(modInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; + if (unmanaged) { + enabled = m_tabs[i]->canHandleUnmanaged(); + } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { + enabled = m_tabs[i]->canHandleSeparators(); } - } - if (modInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, false); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } - else if (unmanaged) - { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - } else { - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, true); - ui->tabWidget->setTabEnabled(TAB_INIFILES, true); - ui->tabWidget->setTabEnabled(TAB_IMAGES, true); - ui->tabWidget->setTabEnabled(TAB_ESPS, true); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, true); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, true); - ui->tabWidget->setTabEnabled(TAB_NEXUS, true); - ui->tabWidget->setTabEnabled(TAB_NOTES, true); - ui->tabWidget->setTabEnabled(TAB_FILETREE, true); - } + ui->tabWidget->setTabEnabled(static_cast(i), enabled); - // activate first enabled tab - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->isTabEnabled(i)) { - ui->tabWidget->setCurrentIndex(i); - break; + if (!tabSelected && enabled) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + tabSelected = true; } } @@ -234,23 +176,21 @@ ModInfoDialog::~ModInfoDialog() delete ui; } -template -std::vector> createTabsImpl( - OrganizerCore& oc, PluginContainer& plugin, - ModInfoDialog* self, Ui::ModInfoDialog* ui) +std::vector> ModInfoDialog::createTabs() { std::vector> v; - (v.push_back(std::make_unique(oc, plugin, self, ui)), ...); - return v; -} + v.push_back(createTab(TAB_TEXTFILES)); + v.push_back(createTab(TAB_INIFILES)); + v.push_back(createTab(TAB_IMAGES)); + v.push_back(createTab(TAB_ESPS)); + v.push_back(createTab(TAB_CONFLICTS)); + v.push_back(createTab(TAB_CATEGORIES)); + v.push_back(createTab(TAB_NEXUS)); + v.push_back(createTab(TAB_NOTES)); + v.push_back(createTab(TAB_FILETREE)); -std::vector> ModInfoDialog::createTabs() -{ - return createTabsImpl< - TextFilesTab, IniFilesTab, ImagesTab, ESPsTab, - ConflictsTab, CategoriesTab, NexusTab, NotesTab, FileTreeTab>( - *m_OrganizerCore, *m_PluginContainer, this, ui); + return v; } int ModInfoDialog::exec() @@ -259,16 +199,6 @@ int ModInfoDialog::exec() return TutorableDialog::exec(); } -int ModInfoDialog::tabIndex(const QString &tabId) -{ - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; -} - void ModInfoDialog::saveState(Settings& s) const { s.directInterface().setValue("mod_info_tabs", saveTabState()); @@ -309,7 +239,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) } // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad + QTabBar *tabBar = ui->tabWidget->tabBar(); ui->tabWidget->blockSignals(true); for (int newPos = 0; newPos < count; ++newPos) { QString tabId = tabIds.at(newPos); @@ -331,6 +261,16 @@ QByteArray ModInfoDialog::saveTabState() const return result; } +int ModInfoDialog::tabIndex(const QString& tabId) +{ + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->widget(i)->objectName() == tabId) { + return i; + } + } + return -1; +} + void ModInfoDialog::onDeleteShortcut() { for (auto& t : m_tabs) { @@ -373,9 +313,8 @@ void ModInfoDialog::on_closeButton_clicked() void ModInfoDialog::openTab(int tab) { - QTabWidget *tabWidget = findChild("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); + if (ui->tabWidget->isTabEnabled(tab)) { + ui->tabWidget->setCurrentIndex(tab); } } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 49007c87..020e7958 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -23,36 +23,15 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" -#include "plugincontainer.h" -#include "organizercore.h" -#include "filterwidget.h" #include "filerenamer.h" -#include "expanderwidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Ui { - class ModInfoDialog; -} - -class QFileSystemModel; -class QTreeView; -class CategoryFactory; -class TextEditor; -class ModInfoDialogTab; +namespace Ui { class ModInfoDialog; } +namespace MOShared { class FilesOrigin; } + +class PluginContainer; +class OrganizerCore; +class Settings; +class ModInfoDialogTab; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); @@ -131,9 +110,6 @@ signals: void modOpenPrev(int tab=-1); void originModified(int originID); -private: - int tabIndex(const QString &tabId); - private slots: void on_closeButton_clicked(); void on_tabWidget_currentChanged(int index); @@ -141,8 +117,6 @@ private slots: void on_prevButton_clicked(); private: - using FileEntry = MOShared::FileEntry; - Ui::ModInfoDialog *ui; ModInfo::Ptr m_ModInfo; std::vector> m_tabs; @@ -157,6 +131,14 @@ private: void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; void onDeleteShortcut(); + int tabIndex(const QString &tabId); + + template + std::unique_ptr createTab(int index) + { + return std::make_unique( + *m_OrganizerCore, *m_PluginContainer, this, ui, index); + } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 65b89621..93550de3 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 8 + 0 true @@ -1148,6 +1148,9 @@ p, li { white-space: pre-wrap; } QAbstractItemView::ExtendedSelection + + true +
diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 69c7c6b5..321c22b8 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -5,8 +5,8 @@ CategoriesTab::CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -35,6 +35,11 @@ void CategoriesTab::update() updatePrimary(); } +bool CategoriesTab::canHandleSeparators() const +{ + return true; +} + void CategoriesTab::add( const CategoryFactory &factory, const std::set& enabledCategories, QTreeWidgetItem* root, int rootLevel) diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 76426a5d..29d0b2a5 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -7,10 +7,11 @@ class CategoriesTab : public ModInfoDialogTab public: CategoriesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void add( diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index adde27ca..15bb7ed4 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -2,6 +2,8 @@ #include "ui_modinfodialog.h" #include "modinfodialog.h" #include "utility.h" +#include "settings.h" +#include "organizercore.h" using namespace MOShared; using namespace MOBase; @@ -131,8 +133,8 @@ public: ConflictsTab::ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) : - ModInfoDialogTab(oc, plugin, parent, ui), + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), m_general(this, ui, oc), m_advanced(this, ui, oc) { connect( @@ -174,6 +176,11 @@ void ConflictsTab::restoreState(const Settings& s) m_advanced.restoreState(s); } +bool ConflictsTab::canHandleUnmanaged() const +{ + return true; +} + void ConflictsTab::changeItemsVisibility( const QList& items, bool visible) { diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 9c011163..a05682ba 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -5,6 +5,7 @@ #include "expanderwidget.h" #include "filterwidget.h" #include "directoryentry.h" +#include class ConflictsTab; class OrganizerCore; @@ -96,13 +97,14 @@ class ConflictsTab : public ModInfoDialogTab public: ConflictsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; + bool canHandleUnmanaged() const override; void openItems(const QList& items); void previewItems(const QList& items); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index ea7eb3b0..dd4fff0b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -124,8 +124,8 @@ private: ESPsTab::ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { QObject::connect( ui->activateESP, &QToolButton::clicked, [&]{ onActivate(); }); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index e1a7a4f7..d8c8997e 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -12,7 +12,7 @@ class ESPsTab : public ModInfoDialogTab public: ESPsTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index b73a9e24..3e233ccc 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -15,8 +15,8 @@ const int max_scan_for_context_menu = 50; FileTreeTab::FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_fs(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index dcc096fe..d0c36edc 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -8,7 +8,7 @@ class FileTreeTab : public ModInfoDialogTab public: FileTreeTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1c7dcc1f..332a0984 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -82,8 +82,9 @@ void ScalableImage::mousePressEvent(QMouseEvent* e) ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_image(new ScalableImage) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ModInfoDialogTab(oc, plugin, parent, ui, index), + m_image(new ScalableImage) { ui->imagesImage->layout()->addWidget(m_image); ui->imagesThumbnails->setLayout(new QVBoxLayout); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 7853935a..689b8e93 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -36,7 +36,7 @@ class ImagesTab : public ModInfoDialogTab public: ImagesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 55b55439..9d51871c 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -9,8 +9,8 @@ NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_requestStarted(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 2e328c6d..7fe10171 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -35,7 +35,7 @@ class NexusTab : public ModInfoDialogTab public: NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 58745220..1b7fadbb 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -5,8 +5,9 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), m_origin(nullptr) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : + ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), + m_origin(nullptr), m_tabIndex(index) { } @@ -42,6 +43,16 @@ bool ModInfoDialogTab::deleteRequested() return false; } +bool ModInfoDialogTab::canHandleSeparators() const +{ + return false; +} + +bool ModInfoDialogTab::canHandleUnmanaged() const +{ + return false; +} + void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) { m_mod = mod; @@ -58,6 +69,11 @@ MOShared::FilesOrigin* ModInfoDialogTab::origin() const return m_origin; } +int ModInfoDialogTab::tabIndex() const +{ + return m_tabIndex; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -88,8 +104,8 @@ void ModInfoDialogTab::emitModOpen(QString name) NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) - : ModInfoDialogTab(oc, plugin, parent, ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) + : ModInfoDialogTab(oc, plugin, parent, ui, index) { connect(ui->commentsEdit, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notesEdit, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -107,6 +123,11 @@ void NotesTab::update() ui->notesEdit->setText(mod()->notes()); } +bool NotesTab::canHandleSeparators() const +{ + return true; +} + void NotesTab::onComments() { mod()->setComments(ui->commentsEdit->text()); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 0dc977a8..1f99344f 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -29,11 +29,16 @@ public: virtual void restoreState(const Settings& s); virtual bool deleteRequested(); + virtual bool canHandleSeparators() const; + virtual bool canHandleUnmanaged() const; + virtual void setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin); ModInfo::Ptr mod() const; MOShared::FilesOrigin* origin() const; + int tabIndex() const; + signals: void originModified(int originID); void modOpen(QString name); @@ -43,7 +48,7 @@ protected: ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); OrganizerCore& core(); PluginContainer& plugin(); @@ -59,6 +64,7 @@ private: QWidget* m_parent; ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; + int m_tabIndex; }; @@ -67,10 +73,11 @@ class NotesTab : public ModInfoDialogTab public: NotesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); void clear() override; void update() override; + bool canHandleSeparators() const override; private: void onComments(); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index f48557b0..fddfafba 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -23,9 +23,9 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* sp, TextEditor* e) - : ModInfoDialogTab(oc, plugin, parent, ui), m_list(list), m_editor(e) + : ModInfoDialogTab(oc, plugin, parent, ui, index), m_list(list), m_editor(e) { m_editor->setupToolbar(); @@ -113,9 +113,9 @@ void GenericFilesTab::select(FileListItem* item) TextFilesTab::TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) { } @@ -137,9 +137,9 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c IniFilesTab::IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui) + QWidget* parent, Ui::ModInfoDialog* ui, int index) : GenericFilesTab( - oc, plugin, parent, ui, + oc, plugin, parent, ui, index, ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 0dc5ec89..f618a6bb 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -23,7 +23,7 @@ protected: GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, + QWidget* parent, Ui::ModInfoDialog* ui, int index, QListWidget* list, QSplitter* splitter, TextEditor* editor); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -39,7 +39,7 @@ class TextFilesTab : public GenericFilesTab public: TextFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -51,7 +51,7 @@ class IniFilesTab : public GenericFilesTab public: IniFilesTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui); + QWidget* parent, Ui::ModInfoDialog* ui, int index); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; diff --git a/src/resources.qrc b/src/resources.qrc index 8645b27e..6fc33293 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,106 +1,108 @@ - - resources/help-browser.png - resources/list-add.png - resources/document-save.png - resources/edit-find-replace.png - resources/go-jump.png - resources/media-playback-start.png - resources/process-stop.png - resources/system-search.png - resources/view-refresh.png - resources/system-installer.png - resources/start-here.png - resources/list-remove.png - resources/document-properties.png - resources/go-up.png - resources/go-down.png - resources/switch-instance-icon.png - resources/contact-new.png - resources/preferences-system.png - resources/application-x-executable.png - resources/dialog-information.png - resources/emblem-readonly.png - resources/go-next_16.png - resources/go-previous_16.png - resources/view-refresh_16.png - resources/software-update-available.png - resources/emblem-important.png - resources/check.png - resources/dialog-warning.png - resources/symbol-backup.png - resources/applications-accessories.png - resources/emblem-unreadable.png - resources/internet-web-browser.png - resources/system-software-update.png - resources/help-browser_32.png - resources/system-installer.png - resources/function.png - resources/plugins.png - resources/edit-clear.png - resources/dynamic-blue-right.png - resources/icon-favorite.png - resources/emblem-favorite.png - resources/error.png - resources/show.png - splash.png - resources/conflict-mixed.png - resources/conflict-overwrite.png - resources/conflict-overwritten.png - resources/conflict-redundant.png - resources/conflict-mixed-blue.png - resources/conflict-overwrite-blue.png - resources/red-archive-conflict-loser.png - resources/accessories-text-editor.png - resources/x-office-calendar.png - resources/dialog-warning_16.png - resources/mail-attachment.png - resources/document-save_32.png - resources/edit-undo.png - resources/arrange-boxes.png - resources/badge_1.png - resources/badge_2.png - resources/badge_3.png - resources/badge_4.png - resources/badge_5.png - resources/badge_6.png - resources/badge_7.png - resources/badge_8.png - resources/badge_9.png - resources/badge_more.png - resources/status_active.png - resources/status_awaiting.png - resources/status_inactive.png - resources/mo_icon.png - resources/package.png - resources/switch-instance-icon.png - resources/open-Folder-Icon.png - resources/multiply-red.png - resources/archive-conflict-loser.png - resources/archive-conflict-mixed.png - resources/archive-conflict-neutral.png - resources/archive-conflict-winner.png - resources/game-warning.png - resources/game-warning-16.png - resources/tracked.png - - - resources/contents/jigsaw-piece.png - resources/contents/hand-of-god.png - resources/contents/empty-chessboard.png - resources/contents/double-quaver.png - resources/contents/lyre.png - resources/contents/usable.png - resources/contents/checkbox-tree.png - resources/contents/tinker.png - resources/contents/breastplate.png - resources/contents/conversation.png - resources/contents/locked-chest.png - resources/contents/config.png - resources/contents/feather-and-scroll.png - resources/contents/xedit.png - - - qt.conf - + + resources/save.svg + resources/word-wrap.svg + resources/help-browser.png + resources/list-add.png + resources/document-save.png + resources/edit-find-replace.png + resources/go-jump.png + resources/media-playback-start.png + resources/process-stop.png + resources/system-search.png + resources/view-refresh.png + resources/system-installer.png + resources/start-here.png + resources/list-remove.png + resources/document-properties.png + resources/go-up.png + resources/go-down.png + resources/switch-instance-icon.png + resources/contact-new.png + resources/preferences-system.png + resources/application-x-executable.png + resources/dialog-information.png + resources/emblem-readonly.png + resources/go-next_16.png + resources/go-previous_16.png + resources/view-refresh_16.png + resources/software-update-available.png + resources/emblem-important.png + resources/check.png + resources/dialog-warning.png + resources/symbol-backup.png + resources/applications-accessories.png + resources/emblem-unreadable.png + resources/internet-web-browser.png + resources/system-software-update.png + resources/help-browser_32.png + resources/system-installer.png + resources/function.png + resources/plugins.png + resources/edit-clear.png + resources/dynamic-blue-right.png + resources/icon-favorite.png + resources/emblem-favorite.png + resources/error.png + resources/show.png + splash.png + resources/conflict-mixed.png + resources/conflict-overwrite.png + resources/conflict-overwritten.png + resources/conflict-redundant.png + resources/conflict-mixed-blue.png + resources/conflict-overwrite-blue.png + resources/red-archive-conflict-loser.png + resources/accessories-text-editor.png + resources/x-office-calendar.png + resources/dialog-warning_16.png + resources/mail-attachment.png + resources/document-save_32.png + resources/edit-undo.png + resources/arrange-boxes.png + resources/badge_1.png + resources/badge_2.png + resources/badge_3.png + resources/badge_4.png + resources/badge_5.png + resources/badge_6.png + resources/badge_7.png + resources/badge_8.png + resources/badge_9.png + resources/badge_more.png + resources/status_active.png + resources/status_awaiting.png + resources/status_inactive.png + resources/mo_icon.png + resources/package.png + resources/switch-instance-icon.png + resources/open-Folder-Icon.png + resources/multiply-red.png + resources/archive-conflict-loser.png + resources/archive-conflict-mixed.png + resources/archive-conflict-neutral.png + resources/archive-conflict-winner.png + resources/game-warning.png + resources/game-warning-16.png + resources/tracked.png + + + resources/contents/jigsaw-piece.png + resources/contents/hand-of-god.png + resources/contents/empty-chessboard.png + resources/contents/double-quaver.png + resources/contents/lyre.png + resources/contents/usable.png + resources/contents/checkbox-tree.png + resources/contents/tinker.png + resources/contents/breastplate.png + resources/contents/conversation.png + resources/contents/locked-chest.png + resources/contents/config.png + resources/contents/feather-and-scroll.png + resources/contents/xedit.png + + + qt.conf + -- cgit v1.3.1 From eb8140afadc5aa4e6d1d2611f69dc6e38f469978 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Mon, 24 Jun 2019 07:28:02 -0400 Subject: grey out tab names when they have no data remove tabs if they're can't handle the selected mod next/previous now load mods in place without reopening the dialog tab reordering is broken --- src/mainwindow.cpp | 104 +++++++----- src/mainwindow.h | 5 +- src/modinfodialog.cpp | 358 ++++++++++++++++++++++++++-------------- src/modinfodialog.h | 60 ++++--- src/modinfodialogcategories.cpp | 2 + src/modinfodialogconflicts.cpp | 7 +- src/modinfodialogconflicts.h | 3 +- src/modinfodialogesps.cpp | 2 + src/modinfodialogfiletree.cpp | 4 +- src/modinfodialogimages.cpp | 12 +- src/modinfodialogimages.h | 2 +- src/modinfodialognexus.cpp | 2 + src/modinfodialogtab.cpp | 22 ++- src/modinfodialogtab.h | 3 + src/modinfodialogtextfiles.cpp | 2 + 15 files changed, 375 insertions(+), 213 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f9bfaafb..67dc8418 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,18 +3223,14 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog( - modInfo, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), - &m_OrganizerCore, &m_PluginContainer, this); - - connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenNext(int)), this, SLOT(modOpenNext(int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenPrev(int)), this, SLOT(modOpenPrev(int)), Qt::QueuedConnection); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + dialog.setMod(modInfo); + //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { - dialog.openTab(tab); + dialog.setTab(tab); } dialog.restoreState(m_OrganizerCore.settings()); @@ -3244,16 +3240,6 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, dialog.restoreGeometry(settings.value(key).toByteArray()); } - //If no tab was specified use the first tab from the left based on the user order. - if (tab == -1) { - for (int i = 0; i < dialog.findChild("tabWidget")->count(); ++i) { - if (dialog.findChild("tabWidget")->isTabEnabled(i)) { - dialog.findChild("tabWidget")->setCurrentIndex(i); - break; - } - } - } - dialog.exec(); dialog.saveState(m_OrganizerCore.settings()); settings.setValue(key, dialog.saveGeometry()); @@ -3296,43 +3282,71 @@ void MainWindow::setWindowEnabled(bool enabled) } -void MainWindow::modOpenNext(int tab) +ModInfo::Ptr MainWindow::nextModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } + + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenNext(tab); - } else { - displayModInformation(m_ContextRow,tab); + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } -void MainWindow::modOpenPrev(int tab) +ModInfo::Ptr MainWindow::previousModInList() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } + const QModelIndex start = m_ModListSortProxy->mapFromSource( + m_OrganizerCore.modList()->index(m_ContextRow, 0)); + + auto index = start; + + for (;;) { + int row = index.row() - 1; + if (row == -1) { + row = m_ModListSortProxy->rowCount() - 1; + } + + index = m_ModListSortProxy->index(row, 0); + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + + if (index == start || !index.isValid()) { + // wrapped around, give up + break; + } - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end())) { // skip overwrite and backups and separators - modOpenPrev(tab); - } else { - displayModInformation(m_ContextRow,tab); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + + if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || + mod->hasFlag(ModInfo::FLAG_BACKUP) || + mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + continue; + } + + return mod; } + + return {}; } void MainWindow::displayModInformation(const QString &modName, int tab) diff --git a/src/mainwindow.h b/src/mainwindow.h index b6283a26..f204211e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -160,6 +160,9 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; } + ModInfo::Ptr nextModInList(); + ModInfo::Ptr previousModInList(); + public slots: void displayColumnSelection(const QPoint &pos); @@ -549,8 +552,6 @@ private slots: void deselectFilters(); void displayModInformation(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void modRenamed(const QString &oldName, const QString &newName); void modRemoved(const QString &fileName); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index be7d4aa4..ad704ce8 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "ui_modinfodialog.h" #include "plugincontainer.h" #include "organizercore.h" +#include "mainwindow.h" #include "modinfodialogtextfiles.h" #include "modinfodialogimages.h" #include "modinfodialogesps.h" @@ -34,23 +35,6 @@ using namespace MOShared; const int max_scan_for_context_menu = 50; - -class ModFileListWidget : public QListWidgetItem { - friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); -public: - ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) - : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} -private: - int m_SortValue; -}; - - -static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) -{ - return LHS.m_SortValue < RHS.m_SortValue; -} - - bool canPreviewFile( PluginContainer& pluginContainer, bool isArchive, const QString& filename) { @@ -111,74 +95,54 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa } +ModInfoDialog::TabInfo::TabInfo(std::unique_ptr tab) + : tab(std::move(tab)), realPos(-1), widget(nullptr) +{ +} + ModInfoDialog::ModInfoDialog( - ModInfo::Ptr modInfo, bool unmanaged, OrganizerCore *organizerCore, - PluginContainer *pluginContainer, QWidget *parent) : - TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), - m_ModInfo(modInfo), m_RootPath(modInfo->absolutePath()), - m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), - m_Origin(nullptr) + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + TutorableDialog("ModInfoDialog", mw), + ui(new Ui::ModInfoDialog), m_mainWindow(mw), + m_core(core), m_plugin(plugin), m_initialTab(-1) { ui->setupUi(this); - auto* ds = m_OrganizerCore->directoryStructure(); - if (ds->originExists(ToWString(m_ModInfo->name()))) { - m_Origin = &ds->getOriginByName(ToWString(m_ModInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; - } - } - - this->setWindowTitle(m_ModInfo->name()); - this->setWindowModality(Qt::WindowModal); - auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); m_tabs = createTabs(); - bool tabSelected = false; - for (std::size_t i=0; itabWidget->count(); ++i) { + if (static_cast(i) >= m_tabs.size()) { + qCritical() << "mod info dialog has more tabs than expected"; + break; + } + + auto& tabInfo = m_tabs[static_cast(i)]; + tabInfo.widget = ui->tabWidget->widget(i); + tabInfo.caption = ui->tabWidget->tabText(i); + tabInfo.icon = ui->tabWidget->tabIcon(i); + tabInfo.realPos = i; + connect( - m_tabs[i].get(), &ModInfoDialogTab::originModified, + tabInfo.tab.get(), &ModInfoDialogTab::originModified, [&](int originID){ emit originModified(originID); }); connect( - m_tabs[i].get(), &ModInfoDialogTab::modOpen, + tabInfo.tab.get(), &ModInfoDialogTab::modOpen, [&](const QString& name){ - close(); - emit modOpen(name, static_cast(i)); + setMod(name); + update(); }); - - bool enabled = true; - - if (unmanaged) { - enabled = m_tabs[i]->canHandleUnmanaged(); - } else if (m_ModInfo->hasFlag(ModInfo::FLAG_SEPARATOR)) { - enabled = m_tabs[i]->canHandleSeparators(); - } - - ui->tabWidget->setTabEnabled(static_cast(i), enabled); - - if (!tabSelected && enabled) { - ui->tabWidget->setCurrentIndex(static_cast(i)); - tabSelected = true; - } - } - - for (auto& tab : m_tabs) { - tab->setMod(m_ModInfo, m_Origin); } } -ModInfoDialog::~ModInfoDialog() -{ - delete ui; -} +ModInfoDialog::~ModInfoDialog() = default; -std::vector> ModInfoDialog::createTabs() +std::vector ModInfoDialog::createTabs() { - std::vector> v; + std::vector v; v.push_back(createTab(TAB_TEXTFILES)); v.push_back(createTab(TAB_INIFILES)); @@ -195,31 +159,206 @@ std::vector> ModInfoDialog::createTabs() int ModInfoDialog::exec() { - refreshLists(); + update(); return TutorableDialog::exec(); } +void ModInfoDialog::setMod(ModInfo::Ptr mod) +{ + m_mod = mod; +} + +void ModInfoDialog::setMod(const QString& name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + qCritical() << "failed to resolve mod name " << name; + return; + } + + auto mod = ModInfo::getByIndex(index); + if (!mod) { + qCritical() << "mod by index " << index << " is null"; + return; + } + + setMod(mod); +} + +void ModInfoDialog::setTab(int index) +{ + if (!isVisible()) { + m_initialTab = index; + return; + } + + switchToTab(index); +} + +void ModInfoDialog::update() +{ + setWindowTitle(m_mod->name()); + setTabsVisibility(); + updateTabs(); + feedFiles(); + setTabsColors(); + + if (m_initialTab >= 0) { + switchToTab(m_initialTab); + m_initialTab = -1; + } +} + +void ModInfoDialog::setTabsVisibility() +{ + std::vector visibility(m_tabs.size()); + bool changed = false; + + for (std::size_t i=0; ihasFlag(ModInfo::FLAG_FOREIGN)) { + visible = tabInfo.tab->canHandleUnmanaged(); + } else if (m_mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + visible = tabInfo.tab->canHandleSeparators(); + } + + const auto currentlyVisible = (ui->tabWidget->indexOf(tabInfo.widget) != -1); + + if (visible != currentlyVisible) { + changed = true; + } + + visibility[i] = visible; + } + + if (!changed) { + return; + } + + // remember selection + const int sel = ui->tabWidget->currentIndex(); + + // remove all tabs + ui->tabWidget->clear(); + + // add visible tabs + for (std::size_t i=0; itabWidget->addTab(m_tabs[i].widget, m_tabs[i].icon, m_tabs[i].caption); + + if (static_cast(i) == sel) { + ui->tabWidget->setCurrentIndex(static_cast(i)); + } + } + } +} + +void ModInfoDialog::updateTabs() +{ + auto* origin = getOrigin(); + + for (auto& tabInfo : m_tabs) { + tabInfo.tab->setMod(m_mod, origin); + tabInfo.tab->clear(); + tabInfo.tab->update(); + } +} + +void ModInfoDialog::feedFiles() +{ + const auto rootPath = m_mod->absolutePath(); + + if (rootPath.length() > 0) { + QDirIterator dirIterator(rootPath, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->feedFile(rootPath, fileName)) { + break; + } + } + } + } +} + +void ModInfoDialog::setTabsColors() +{ + for (const auto& tabInfo : m_tabs) { + const auto c = tabInfo.tab->hasData() ? + QColor::Invalid : + ui->tabWidget->palette().color(QPalette::Disabled, QPalette::WindowText); + + ui->tabWidget->tabBar()->setTabTextColor(tabInfo.realPos, c); + } +} + +void ModInfoDialog::switchToTab(std::size_t index) +{ + if (index >= m_tabs.size()) { + qCritical() << "tab index " << index << "out of range"; + return; + } + + if (ui->tabWidget->indexOf(m_tabs[index].widget) == -1) { + qCritical() << "can't switch to tab " << index << ", not available"; + return; + } + + ui->tabWidget->setCurrentIndex(m_tabs[index].realPos); +} + +MOShared::FilesOrigin* ModInfoDialog::getOrigin() +{ + MOShared::FilesOrigin* origin = nullptr; + + auto* ds = m_core->directoryStructure(); + if (ds->originExists(ToWString(m_mod->name()))) { + auto* origin = &ds->getOriginByName(ToWString(m_mod->name())); + if (!origin->isDisabled()) { + return origin; + } + } + + return nullptr; +} + void ModInfoDialog::saveState(Settings& s) const { - s.directInterface().setValue("mod_info_tabs", saveTabState()); + //s.directInterface().setValue("mod_info_tabs", saveTabState()); - for (const auto& tab : m_tabs) { - tab->saveState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->saveState(s); } } void ModInfoDialog::restoreState(const Settings& s) { - restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); + //restoreTabState(s.directInterface().value("mod_info_tabs").toByteArray()); - for (const auto& tab : m_tabs) { - tab->restoreState(s); + for (const auto& tabInfo : m_tabs) { + tabInfo.tab->restoreState(s); } } +QByteArray ModInfoDialog::saveTabState() const +{ + QByteArray result; + /*QDataStream stream(&result, QIODevice::WriteOnly); + stream << ui->tabWidget->count(); + for (int i = 0; i < ui->tabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName(); + }*/ + + return result; +} + void ModInfoDialog::restoreTabState(const QByteArray &state) { - QDataStream stream(state); + /*QDataStream stream(state); int count = 0; stream >> count; @@ -232,9 +371,9 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) tabIds.append(tabId); int oldPos = tabIndex(tabId); if (oldPos != -1) { - m_RealTabPos[newPos] = oldPos; + m_realTabPos[newPos] = oldPos; } else { - m_RealTabPos[newPos] = newPos; + m_realTabPos[newPos] = newPos; } } @@ -246,19 +385,7 @@ void ModInfoDialog::restoreTabState(const QByteArray &state) int oldPos = tabIndex(tabId); tabBar->moveTab(oldPos, newPos); } - ui->tabWidget->blockSignals(false); -} - -QByteArray ModInfoDialog::saveTabState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); - } - - return result; + ui->tabWidget->blockSignals(false);*/ } int ModInfoDialog::tabIndex(const QString& tabId) @@ -273,37 +400,17 @@ int ModInfoDialog::tabIndex(const QString& tabId) void ModInfoDialog::onDeleteShortcut() { - for (auto& t : m_tabs) { - if (t->deleteRequested()) { + for (auto& tabInfo : m_tabs) { + if (tabInfo.tab->deleteRequested()) { break; } } } -void ModInfoDialog::refreshLists() -{ - for (auto& tab : m_tabs) { - tab->update(); - } - - if (m_RootPath.length() > 0) { - QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - for (auto& tab : m_tabs) { - if (tab->feedFile(m_RootPath, fileName)) { - break; - } - } - } - } -} - void ModInfoDialog::on_closeButton_clicked() { - for (auto& tab : m_tabs) { - if (!tab->canClose()) { + for (auto& tabInfo : m_tabs) { + if (!tabInfo.tab->canClose()) { return; } } @@ -311,31 +418,28 @@ void ModInfoDialog::on_closeButton_clicked() close(); } -void ModInfoDialog::openTab(int tab) -{ - if (ui->tabWidget->isTabEnabled(tab)) { - ui->tabWidget->setCurrentIndex(tab); - } -} - void ModInfoDialog::on_tabWidget_currentChanged(int index) { } void ModInfoDialog::on_nextButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->nextModInList(); + if (mod == m_mod) { + return; + } - emit modOpenNext(tab); - this->accept(); + setMod(mod); + update(); } void ModInfoDialog::on_prevButton_clicked() { - int currentTab = ui->tabWidget->currentIndex(); - int tab = m_RealTabPos[currentTab]; + auto mod = m_mainWindow->previousModInList(); + if (mod == m_mod) { + return; + } - emit modOpenPrev(tab); - this->accept(); + setMod(mod); + update(); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 020e7958..1cefc71a 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -32,7 +32,7 @@ class PluginContainer; class OrganizerCore; class Settings; class ModInfoDialogTab; - +class MainWindow; bool canPreviewFile(PluginContainer& pluginContainer, bool isArchive, const QString& filename); bool canOpenFile(bool isArchive, const QString& filename); @@ -71,10 +71,7 @@ public: * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog( - ModInfo::Ptr modInfo, - bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, - QWidget *parent = 0); + ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); ~ModInfoDialog(); @@ -92,12 +89,9 @@ public: **/ const int getModID() const; - /** - * @brief open the specified tab in the dialog if it's enabled - * - * @param tab the tab to activate - **/ - void openTab(int tab); + void setMod(ModInfo::Ptr mod); + void setMod(const QString& name); + void setTab(int index); int exec() override; @@ -105,9 +99,6 @@ public: void restoreState(const Settings& s); signals: - void modOpen(const QString &modName, int tab); - void modOpenNext(int tab=-1); - void modOpenPrev(int tab=-1); void originModified(int originID); private slots: @@ -117,27 +108,42 @@ private slots: void on_prevButton_clicked(); private: - Ui::ModInfoDialog *ui; - ModInfo::Ptr m_ModInfo; - std::vector> m_tabs; - QString m_RootPath; - OrganizerCore *m_OrganizerCore; - PluginContainer *m_PluginContainer; - MOShared::FilesOrigin *m_Origin; - std::map m_RealTabPos; - - std::vector> createTabs(); - void refreshLists(); + struct TabInfo + { + std::unique_ptr tab; + int realPos; + QWidget* widget; + QString caption; + QIcon icon; + + TabInfo(std::unique_ptr tab); + }; + + std::unique_ptr ui; + MainWindow* m_mainWindow; + ModInfo::Ptr m_mod; + OrganizerCore* m_core; + PluginContainer* m_plugin; + std::vector m_tabs; + int m_initialTab; + + std::vector createTabs(); void restoreTabState(const QByteArray &state); QByteArray saveTabState() const; + void update(); void onDeleteShortcut(); int tabIndex(const QString &tabId); + MOShared::FilesOrigin* getOrigin(); + void setTabsVisibility(); + void updateTabs(); + void feedFiles(); + void setTabsColors(); + void switchToTab(std::size_t index); template std::unique_ptr createTab(int index) { - return std::make_unique( - *m_OrganizerCore, *m_PluginContainer, this, ui, index); + return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); } }; diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 321c22b8..bce1162b 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -22,6 +22,7 @@ void CategoriesTab::clear() { ui->categories->clear(); ui->primaryCategories->clear(); + setHasData(false); } void CategoriesTab::update() @@ -33,6 +34,7 @@ void CategoriesTab::update() ui->categories->invisibleRootItem(), 0); updatePrimary(); + setHasData(ui->primaryCategories->count() > 0); } bool CategoriesTab::canHandleSeparators() const diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 15bb7ed4..dde00354 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -148,7 +148,7 @@ ConflictsTab::ConflictsTab( void ConflictsTab::update() { - m_general.update(); + setHasData(m_general.update()); m_advanced.update(); } @@ -156,6 +156,7 @@ void ConflictsTab::clear() { m_general.clear(); m_advanced.clear(); + setHasData(false); } void ConflictsTab::saveState(Settings& s) @@ -572,7 +573,7 @@ void GeneralConflictsTab::restoreState(const Settings& s) .value("mod_info_conflicts_general_overwritten").toByteArray()); } -void GeneralConflictsTab::update() +bool GeneralConflictsTab::update() { clear(); @@ -616,6 +617,8 @@ void GeneralConflictsTab::update() ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); ui->noConflictCount->display(numNonConflicting); + + return (numOverwrite > 0 || numOverwritten > 0); } QTreeWidgetItem* GeneralConflictsTab::createOverwriteItem( diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index a05682ba..38fa6a74 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -22,7 +22,7 @@ public: void saveState(Settings& s); void restoreState(const Settings& s); - void update(); + bool update(); signals: void modOpen(QString name); @@ -100,7 +100,6 @@ public: QWidget* parent, Ui::ModInfoDialog* ui, int index); void update() override; - void clear() override; void saveState(Settings& s) override; void restoreState(const Settings& s) override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index dd4fff0b..d0dcaf2b 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -138,6 +138,7 @@ void ESPsTab::clear() { ui->inactiveESPList->clear(); ui->activeESPList->clear(); + setHasData(false); } bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -158,6 +159,7 @@ bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) ui->inactiveESPList->addItem(item); } + setHasData(true); return true; } } diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 3e233ccc..dae37f25 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -49,7 +49,9 @@ FileTreeTab::FileTreeTab( void FileTreeTab::clear() { m_fs->setRootPath({}); - //ui->filetree-> + + // always has data; even if the mod is empty, it still has a meta.ini + setHasData(true); } void FileTreeTab::update() diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 332a0984..9a60fc8e 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -105,6 +105,7 @@ void ImagesTab::clear() } static_cast(ui->imagesThumbnails->layout())->addStretch(1); + setHasData(false); } bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) @@ -115,7 +116,10 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - add(fullPath); + if (add(fullPath)) { + setHasData(true); + } + return true; } } @@ -123,13 +127,13 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) return false; } -void ImagesTab::add(const QString& fullPath) +bool ImagesTab::add(const QString& fullPath) { QImage image = QImage(fullPath); if (image.isNull()) { qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return; + return false; } auto* thumbnail = new ScalableImage(std::move(image)); @@ -140,6 +144,8 @@ void ImagesTab::add(const QString& fullPath) static_cast(ui->imagesThumbnails->layout())->insertWidget( ui->imagesThumbnails->layout()->count() - 1, thumbnail); + + return true; } void ImagesTab::onClicked(const QImage& original) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 689b8e93..60271da0 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -44,7 +44,7 @@ public: private: ScalableImage* m_image; - void add(const QString& fullPath); + bool add(const QString& fullPath); void onClicked(const QImage& image); }; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 9d51871c..61b868d1 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -48,6 +48,7 @@ void NexusTab::clear() ui->version->clear(); ui->browser->setPage(new NexusTabWebpage(ui->browser)); ui->url->clear(); + setHasData(false); } void NexusTab::update() @@ -88,6 +89,7 @@ void NexusTab::update() (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); updateWebpage(); + setHasData(mod()->getNexusID() >= 0); } void NexusTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 1b7fadbb..e50aec29 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -7,7 +7,7 @@ ModInfoDialogTab::ModInfoDialogTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int index) : ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabIndex(index) + m_origin(nullptr), m_tabIndex(index), m_hasData(false) { } @@ -74,6 +74,11 @@ int ModInfoDialogTab::tabIndex() const return m_tabIndex; } +bool ModInfoDialogTab::hasData() const +{ + return m_hasData; +} + OrganizerCore& ModInfoDialogTab::core() { return m_core; @@ -101,6 +106,11 @@ void ModInfoDialogTab::emitModOpen(QString name) emit modOpen(name); } +void ModInfoDialogTab::setHasData(bool b) +{ + m_hasData = b; +} + NotesTab::NotesTab( OrganizerCore& oc, PluginContainer& plugin, @@ -115,12 +125,18 @@ void NotesTab::clear() { ui->commentsEdit->clear(); ui->notesEdit->clear(); + setHasData(false); } void NotesTab::update() { - ui->commentsEdit->setText(mod()->comments()); - ui->notesEdit->setText(mod()->notes()); + const auto comments = mod()->comments(); + const auto notes = mod()->notes(); + + ui->commentsEdit->setText(comments); + ui->notesEdit->setText(notes); + + setHasData(!comments.isEmpty() || !notes.isEmpty()); } bool NotesTab::canHandleSeparators() const diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 1f99344f..8fe7d2d4 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -38,6 +38,7 @@ public: MOShared::FilesOrigin* origin() const; int tabIndex() const; + bool hasData() const; signals: void originModified(int originID); @@ -57,6 +58,7 @@ protected: void emitOriginModified(); void emitModOpen(QString name); + void setHasData(bool b); private: OrganizerCore& m_core; @@ -65,6 +67,7 @@ private: ModInfo::Ptr m_mod; MOShared::FilesOrigin* m_origin; int m_tabIndex; + bool m_hasData; }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index fddfafba..bd175c24 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -42,6 +42,7 @@ void GenericFilesTab::clear() { m_list->clear(); select(nullptr); + setHasData(false); } bool GenericFilesTab::canClose() @@ -76,6 +77,7 @@ bool GenericFilesTab::feedFile(const QString& rootPath, const QString& fullPath) for (const auto* e : extensions) { if (wantsFile(rootPath, fullPath)) { m_list->addItem(new FileListItem(rootPath, fullPath)); + setHasData(true); return true; } } -- cgit v1.3.1 From 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/modinfodialogimages.cpp') 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 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/modinfodialogimages.cpp') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index cdf4c80f..bcdf1007 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -177,11 +177,8 @@ 0 - - - true - - + + 0 @@ -1163,6 +1160,18 @@ p, li { white-space: pre-wrap; } QTextEdit
texteditor.h
+ + ImagesScrollArea + QScrollArea +
modinfodialogimages.h
+ 1 +
+ + ImagesThumbnails + QWidget +
modinfodialogimages.h
+ 1 +
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index d1384d7a..05b69f05 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -887,7 +887,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; for (const auto& alt : alternatives) { @@ -898,9 +897,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem( altString += ToQString(ds.getOriginByID(alt.first).getName()); } - QStringList fields(relativeName); - fields.append(altString); - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); return {altString, relativeName, "", index, fileName, true, origin, archive}; @@ -918,12 +914,8 @@ ConflictItem GeneralConflictsTab::createOverwrittenItem( const QString& fileName, const QString& relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - return { "", relativeName, ToQString(realOrigin.getName()), index, fileName, true, ToQString(realOrigin.getName()), archive}; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index f4cdade8..1f5b258c 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,25 +1,68 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" -ScalableImage::ScalableImage(QImage original) - : m_original(std::move(original)), m_border(1) +void ImagesScrollArea::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollArea::resizeEvent(QResizeEvent* e) +{ + if (m_tab) { + m_tab->scrollAreaResized(e->size()); + } +} + + +void ImagesThumbnails::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesThumbnails::paintEvent(QPaintEvent* e) +{ + if (m_tab) { + m_tab->paintThumbnails(e); + } +} + +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +{ + if (m_tab) { + m_tab->thumbnailsMouseEvent(e); + } +} + + +ScalableImage::ScalableImage(QString path) + : m_path(std::move(path)), m_border(1) { auto sp = sizePolicy(); sp.setHeightForWidth(true); setSizePolicy(sp); } +void ScalableImage::setImage(const QString& path) +{ + m_path = path; + m_original = {}; + m_scaled = {}; + + update(); +} + void ScalableImage::setImage(QImage image) { + m_path.clear(); m_original = std::move(image); m_scaled = {}; update(); } -const QImage& ScalableImage::image() const +void ScalableImage::clear() { - return m_original; + setImage(QImage()); } bool ScalableImage::hasHeightForWidth() const @@ -35,7 +78,15 @@ int ScalableImage::heightForWidth(int w) const void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { - return; + if (m_path.isNull()) { + return; + } + + m_original.load(m_path); + + if (m_original.isNull()) { + return; + } } const QRect widgetRect = rect(); @@ -72,83 +123,177 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawImage(drawImageRect, m_scaled); } -void ScalableImage::mousePressEvent(QMouseEvent* e) -{ - if (e->button() == Qt::LeftButton) { - emit clicked(m_original); - } -} - ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) { ui->imagesImage->layout()->addWidget(m_image); - ui->imagesThumbnails->setLayout(new QVBoxLayout); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); } void ImagesTab::clear() { - m_image->setImage({}); + m_image->clear(); + m_files.clear(); + setHasData(false); +} - while (ui->imagesThumbnails->layout()->count() > 0) { - auto* item = ui->imagesThumbnails->layout()->takeAt(0); - delete item->widget(); - delete item; +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + QImageReader reader(fullPath); + if (reader.canRead()) { + m_files.push_back({fullPath}); + return true; } - static_cast(ui->imagesThumbnails->layout())->addStretch(1); - setHasData(false); + return false; } -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +void ImagesTab::update() +{ + setHasData(!m_files.empty()); +} + +void ImagesTab::scrollAreaResized(const QSize& s) { - static constexpr const char* extensions[] = { - ".png", ".jpg" - }; + const int availableWidth = s.width(); - for (const auto* e : extensions) { - if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (add(fullPath)) { - setHasData(true); + const int thumbSize = availableWidth - (m_margins * 2); + + int height = 0; + if (!m_files.empty()) { + height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); + } + + height += (m_margins * 2); + + qDebug() << "new size: " << availableWidth << "x" << height; + + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t begin = e->rect().top() / (thumbSize + m_padding); + const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; + + QPainter painter(ui->imagesThumbnails); + + for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, thumbSize); + + painter.setPen(QColor(Qt::black)); + painter.drawRect(borderRect); + + auto& file = m_files[i]; + if (file.failed) { + continue; + } + + const QRect thumbRect = borderRect.adjusted( + m_border, m_border, -m_border, -m_border); + + if (needsReload(file, thumbRect.size())) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + continue; } - return true; + file.thumbnail = file.original.scaled( + scaledImageSize(file.original.size(), thumbRect.size()), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - } - return false; + if (file.thumbnail.isNull()) { + continue; + } + + const QRect scaledThumbRect( + (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, + (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, + file.thumbnail.width(), file.thumbnail.height()); + + painter.drawImage(scaledThumbRect, file.thumbnail); + } } -bool ImagesTab::add(const QString& fullPath) +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) { - QImage image = QImage(fullPath); + if (e->button() != Qt::LeftButton) { + return; + } - if (image.isNull()) { - qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image"; - return false; + const auto availableRect = ui->imagesThumbnails->rect(); + const int thumbSize = availableRect.width() - (m_margins * 2); + + const QRect topRect( + availableRect.left() + m_margins, + availableRect.top() + m_margins, + thumbSize, thumbSize); + + const std::size_t i = e->y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return; } - auto* thumbnail = new ScalableImage(std::move(image)); + if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { + return; + } - QObject::connect( - thumbnail, &ScalableImage::clicked, - [&](const QImage& image){ onClicked(image); }); + m_image->setImage(m_files[i].original); +} - static_cast(ui->imagesThumbnails->layout())->insertWidget( - ui->imagesThumbnails->layout()->count() - 1, thumbnail); +bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const +{ + if (file.failed) { + return false; + } - return true; + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { + return true; + } + + return false; } -void ImagesTab::onClicked(const QImage& original) +QSize ImagesTab::scaledImageSize( + const QSize& originalSize, const QSize& thumbSize) const { - m_image->setImage(original); + const auto ratio = std::min({ + 1.0, + static_cast(thumbSize.width()) / originalSize.width(), + static_cast(thumbSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 6603660a..1c65d8bd 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -2,28 +2,62 @@ #define MODINFODIALOGIMAGES_H #include "modinfodialogtab.h" +#include + +class ImagesTab; + +class ImagesScrollArea : public QScrollArea +{ + Q_OBJECT; + +public: + using QScrollArea::QScrollArea; + void setTab(ImagesTab* tab); + +protected: + void resizeEvent(QResizeEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + + +class ImagesThumbnails : public QWidget +{ + Q_OBJECT; + +public: + using QWidget::QWidget; + void setTab(ImagesTab* tab); + +protected: + void paintEvent(QPaintEvent* e) override; + void mousePressEvent(QMouseEvent* e) override; + +private: + ImagesTab* m_tab = nullptr; +}; + class ScalableImage : public QWidget { Q_OBJECT; public: - ScalableImage(QImage image={}); + ScalableImage(QString path={}); + void setImage(const QString& path); void setImage(QImage image); - const QImage& image() const; + void clear(); bool hasHeightForWidth() const; int heightForWidth(int w) const; -signals: - void clicked(const QImage& image); - protected: void paintEvent(QPaintEvent* e) override; - void mousePressEvent(QMouseEvent* e) override; private: + QString m_path; QImage m_original, m_scaled; int m_border; }; @@ -32,6 +66,8 @@ private: class ImagesTab : public ModInfoDialogTab { Q_OBJECT; + friend class ImagesScrollArea; + friend class ImagesThumbnails; public: ImagesTab( @@ -40,12 +76,31 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; + void update() override; private: + struct File + { + QString path; + QImage original, thumbnail; + bool failed = false; + + File(QString path) + : path(std::move(path)) + { + } + }; + ScalableImage* m_image; + std::vector m_files; + int m_margins, m_padding, m_border; + + void scrollAreaResized(const QSize& s); + void paintThumbnails(QPaintEvent* e); + void thumbnailsMouseEvent(QMouseEvent* e); - bool add(const QString& fullPath); - void onClicked(const QImage& image); + bool needsReload(const File& file, const QSize& thumbSize) const; + QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From 24b9f050c10d039be13a266d41088ae0fcc068f4 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 08:30:11 -0400 Subject: check supported extensions instead of using QImageReader, less accurate but much faster --- src/modinfodialogimages.cpp | 21 +++++++++++++++++---- src/modinfodialogimages.h | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 1f5b258c..4acc7838 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -140,6 +140,18 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setWidgetResizable(false); ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + + const auto list = QImageReader::supportedImageFormats(); + for (const auto& entry : list) { + QString s(entry); + if (!s.isEmpty()) { + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } + } } void ImagesTab::clear() @@ -151,10 +163,11 @@ void ImagesTab::clear() bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { - QImageReader reader(fullPath); - if (reader.canRead()) { - m_files.push_back({fullPath}); - return true; + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } } return false; diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 1c65d8bd..88e22a6a 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -93,6 +93,7 @@ private: ScalableImage* m_image; std::vector m_files; + std::vector m_supportedFormats; int m_margins, m_padding, m_border; void scrollAreaResized(const QSize& s); -- cgit v1.3.1 From 9e3214a73a5cc38a9b4f0313d8a48c0374cb57a3 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 09:13:38 -0400 Subject: refactored calculations --- src/modinfodialogimages.cpp | 467 ++++++++++++++++++++++++++------------------ src/modinfodialogimages.h | 36 +++- 2 files changed, 313 insertions(+), 190 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4acc7838..5b35a9b1 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,6 +1,285 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +ImagesTab::ImagesTab( + OrganizerCore& oc, PluginContainer& plugin, + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) +{ + ui->imagesImage->layout()->addWidget(m_image); + delete ui->imagesThumbnails->layout(); + + ui->tabImagesSplitter->setSizes({128, 1}); + ui->tabImagesSplitter->setStretchFactor(0, 0); + ui->tabImagesSplitter->setStretchFactor(1, 1); + + ui->imagesScrollArea->setWidgetResizable(false); + ui->imagesScrollArea->setTab(this); + ui->imagesThumbnails->setTab(this); + + getSupportedFormats(); +} + +void ImagesTab::clear() +{ + m_image->clear(); + m_files.clear(); + setHasData(false); +} + +bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) +{ + for (const auto& ext : m_supportedFormats) { + if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { + m_files.push_back({fullPath}); + return true; + } + } + + return false; +} + +void ImagesTab::update() +{ + setHasData(!m_files.empty()); + resizeWidget(); + ui->imagesThumbnails->update(); +} + +void ImagesTab::getSupportedFormats() +{ + for (const auto& entry : QImageReader::supportedImageFormats()) { + QString s(entry); + if (s.isNull() || s.isEmpty()) { + continue; + } + + // make sure it starts with a dot + if (s[0] != ".") { + s = "." + s; + } + + m_supportedFormats.emplace_back(std::move(s)); + } +} + +int ImagesTab::calcThumbSize(int availableWidth) const +{ + return availableWidth - (m_margins * 2); +} + +int ImagesTab::calcWidgetHeight(int availableWidth) const +{ + if (m_files.empty()) { + return 0; + } + + const auto thumbSize = calcThumbSize(availableWidth); + + int h = 0; + + // first thumb + h = thumbSize; + + // subsequent thumbs with padding before each + const auto thumbWithPadding = m_padding + thumbSize; + h += static_cast(thumbWithPadding * (m_files.size() - 1)); + + // margin top and bottom + h += m_margins * 2; + + return h; +} + +QRect ImagesTab::calcTopThumbRect(int thumbSize) const +{ + return {m_margins, m_margins, thumbSize, thumbSize}; +} + +std::pair ImagesTab::calcVisibleRange( + int top, int bottom, int thumbSize) const +{ + const std::size_t begin = top / (thumbSize + m_padding); + const std::size_t end = bottom / (thumbSize + m_padding) + 1; + + return {begin, end}; +} + +QRect ImagesTab::calcBorderRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return { + topRect.left(), + static_cast(topRect.top() + (i * (thumbSize + m_padding))), + thumbSize, + thumbSize + }; +} + +QRect ImagesTab::calcImageRect( + const QRect& topRect, int thumbSize, std::size_t i) const +{ + return calcBorderRect(topRect, thumbSize, i).adjusted( + m_border, m_border, -m_border, -m_border); +} + +QSize ImagesTab::calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const +{ + const auto ratio = std::min({ + 1.0, + static_cast(imageSize.width()) / originalSize.width(), + static_cast(imageSize.height()) / originalSize.height()}); + + const QSize scaledSize( + static_cast(std::round(originalSize.width() * ratio)), + static_cast(std::round(originalSize.height() * ratio))); + + return scaledSize; +} + +void ImagesTab::paintThumbnails(QPaintEvent* e) +{ + PaintContext cx(ui->imagesThumbnails); + cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + cx.topRect = calcTopThumbRect(cx.thumbSize); + + const auto [begin, end] = calcVisibleRange( + e->rect().top(), e->rect().bottom(), cx.thumbSize); + + for (std::size_t i=begin; iimagesThumbnails->width()); + + // calculate index purely based on y position + const std::size_t i = p.y() / (thumbSize + m_padding); + if (i >= m_files.size()) { + return nullptr; + } + + // get actual rect + const auto topRect = calcTopThumbRect(thumbSize); + const auto rect = calcBorderRect(topRect, thumbSize, i); + + if (!rect.contains(p)) { + return nullptr; + } + + return &m_files[i]; +} + +void ImagesTab::scrollAreaResized(const QSize&) +{ + resizeWidget(); +} + +void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +{ + if (e->button() != Qt::LeftButton) { + return; + } + + if (const auto* file=fileAtPos(e->pos())) { + m_image->setImage(file->original); + } +} + +bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +{ + if (file.failed) { + return false; + } + + if (file.original.isNull() || file.thumbnail.isNull()) { + return true; + } + + const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); + if (file.thumbnail.size() != scaledSize) { + return true; + } + + return false; +} + +void ImagesTab::reload(File& file, const QSize& scaledSize) +{ + file.original = {}; + file.thumbnail = {}; + file.failed = false; + + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } + + file.thumbnail = file.original.scaled( + calcScaledImageSize(file.original.size(), scaledSize), + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +} + +void ImagesTab::resizeWidget() +{ + if (m_files.empty()) { + ui->imagesThumbnails->setGeometry(QRect()); + return; + } + + const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + + const int widgetHeight = calcWidgetHeight(availableWidth); + ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); +} + + void ImagesScrollArea::setTab(ImagesTab* tab) { m_tab = tab; @@ -122,191 +401,3 @@ void ScalableImage::paintEvent(QPaintEvent* e) painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } - - -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) -{ - ui->imagesImage->layout()->addWidget(m_image); - delete ui->imagesThumbnails->layout(); - - ui->tabImagesSplitter->setSizes({128, 1}); - ui->tabImagesSplitter->setStretchFactor(0, 0); - ui->tabImagesSplitter->setStretchFactor(1, 1); - - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); - ui->imagesThumbnails->setTab(this); - - const auto list = QImageReader::supportedImageFormats(); - for (const auto& entry : list) { - QString s(entry); - if (!s.isEmpty()) { - if (s[0] != ".") { - s = "." + s; - } - - m_supportedFormats.emplace_back(std::move(s)); - } - } -} - -void ImagesTab::clear() -{ - m_image->clear(); - m_files.clear(); - setHasData(false); -} - -bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) -{ - for (const auto& ext : m_supportedFormats) { - if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); - return true; - } - } - - return false; -} - -void ImagesTab::update() -{ - setHasData(!m_files.empty()); -} - -void ImagesTab::scrollAreaResized(const QSize& s) -{ - const int availableWidth = s.width(); - - const int thumbSize = availableWidth - (m_margins * 2); - - int height = 0; - if (!m_files.empty()) { - height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1)); - } - - height += (m_margins * 2); - - qDebug() << "new size: " << availableWidth << "x" << height; - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height)); -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t begin = e->rect().top() / (thumbSize + m_padding); - const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1; - - QPainter painter(ui->imagesThumbnails); - - for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, thumbSize); - - painter.setPen(QColor(Qt::black)); - painter.drawRect(borderRect); - - auto& file = m_files[i]; - if (file.failed) { - continue; - } - - const QRect thumbRect = borderRect.adjusted( - m_border, m_border, -m_border, -m_border); - - if (needsReload(file, thumbRect.size())) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - continue; - } - - file.thumbnail = file.original.scaled( - scaledImageSize(file.original.size(), thumbRect.size()), - Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } - - if (file.thumbnail.isNull()) { - continue; - } - - const QRect scaledThumbRect( - (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2, - (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2, - file.thumbnail.width(), file.thumbnail.height()); - - painter.drawImage(scaledThumbRect, file.thumbnail); - } -} - -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) -{ - if (e->button() != Qt::LeftButton) { - return; - } - - const auto availableRect = ui->imagesThumbnails->rect(); - const int thumbSize = availableRect.width() - (m_margins * 2); - - const QRect topRect( - availableRect.left() + m_margins, - availableRect.top() + m_margins, - thumbSize, thumbSize); - - const std::size_t i = e->y() / (thumbSize + m_padding); - if (i >= m_files.size()) { - return; - } - - if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) { - return; - } - - m_image->setImage(m_files[i].original); -} - -bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const -{ - if (file.failed) { - return false; - } - - if (file.original.isNull() || file.thumbnail.isNull()) { - return true; - } - - if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) { - return true; - } - - return false; -} - -QSize ImagesTab::scaledImageSize( - const QSize& originalSize, const QSize& thumbSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(thumbSize.width()) / originalSize.width(), - static_cast(thumbSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 88e22a6a..9c3e5746 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -91,17 +91,49 @@ private: } }; + struct PaintContext + { + QPainter painter; + int thumbSize; + QRect topRect; + + PaintContext(QWidget* w) + : painter(w), thumbSize(0) + { + } + }; + ScalableImage* m_image; std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + void getSupportedFormats(); + void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); - bool needsReload(const File& file, const QSize& thumbSize) const; - QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const; + int calcThumbSize(int availableWidth) const; + int calcWidgetHeight(int availableWidth) const; + QRect calcTopThumbRect(int thumbSize) const; + std::pair calcVisibleRange( + int top, int bottom, int thumbSize) const; + + QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; + QSize calcScaledImageSize( + const QSize& originalSize, const QSize& imageSize) const; + + void paintThumbnail(PaintContext& cx, std::size_t i); + void paintThumbnailBorder(PaintContext& cx, std::size_t i); + void paintThumbnailImage(PaintContext& cx, std::size_t i); + + const File* fileAtPos(const QPoint& p) const; + + bool needsReload(const File& file, const QSize& imageSize) const; + void reload(File& file, const QSize& imageSize); + void resizeWidget(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From 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/modinfodialogimages.cpp') diff --git a/src/modinfodialog.h b/src/modinfodialog.h index e814dcf4..8ddaf86c 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -45,6 +45,20 @@ FileRenamer::RenameResults unhideFile(FileRenamer& renamer, const QString &oldNa int naturalCompare(const QString& a, const QString& b); +class ElideLeftDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + +protected: + void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const + { + QStyledItemDelegate::initStyleOption(o, i); + o->textElideMode = Qt::ElideLeft; + } +}; + + /** * this is a larger dialog used to visualise information about the mod. * @todo this would probably a good place for a plugin-system diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a152c292..9f4ae873 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -192,8 +192,11 @@
- - + + + + 3 + 0 @@ -206,6 +209,41 @@ 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Open in Explorer + + + + + + + true + + + + + + + + + @@ -403,7 +441,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e - 0 + 1 diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 52d25954..c053e64c 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -13,20 +13,6 @@ using namespace MOBase; const std::size_t max_small_selection = 50; -class ElideLeftDelegate : public QStyledItemDelegate -{ -public: - using QStyledItemDelegate::QStyledItemDelegate; - -protected: - void initStyleOption(QStyleOptionViewItem* o, const QModelIndex& i) const - { - QStyledItemDelegate::initStyleOption(o, i); - o->textElideMode = Qt::ElideLeft; - } -}; - - class ConflictItem { public: @@ -263,6 +249,10 @@ private: void doSort() { + if (m_items.empty()) { + return; + } + if (m_sortColumn < 0) { return; } diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5b35a9b1..22c004e6 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -1,13 +1,18 @@ #include "modinfodialogimages.h" #include "ui_modinfodialog.h" +#include "utility.h" ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1) + m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_selection(nullptr) { - ui->imagesImage->layout()->addWidget(m_image); + auto* ly = new QVBoxLayout(ui->imagesImage); + ly->setContentsMargins({0, 0, 0, 0}); + ly->addWidget(m_image); + delete ui->imagesThumbnails->layout(); ui->tabImagesSplitter->setSizes({128, 1}); @@ -18,13 +23,15 @@ ImagesTab::ImagesTab( ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + connect(ui->imagesExplore, &QAbstractButton::clicked, [&]{ onExplore(); }); + getSupportedFormats(); } void ImagesTab::clear() { - m_image->clear(); m_files.clear(); + select(nullptr); setHasData(false); } @@ -64,6 +71,21 @@ void ImagesTab::getSupportedFormats() } } +void ImagesTab::select(const File* f) +{ + if (f) { + ui->imagesPath->setText(f->path); + ui->imagesExplore->setEnabled(true); + m_image->setImage(f->original); + } else { + ui->imagesPath->clear(); + ui->imagesExplore->setEnabled(false); + m_image->clear(); + } + + m_selection = f; +} + int ImagesTab::calcThumbSize(int availableWidth) const { return availableWidth - (m_margins * 2); @@ -227,10 +249,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) } if (const auto* file=fileAtPos(e->pos())) { - m_image->setImage(file->original); + select(file); } } +void ImagesTab::onExplore() +{ + if (!m_selection) { + return; + } + + MOBase::shell::ExploreFile(m_selection->path); +} + bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const { if (file.failed) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 9c3e5746..cb9a2d67 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -107,12 +107,15 @@ private: std::vector m_files; std::vector m_supportedFormats; int m_margins, m_padding, m_border; + const File* m_selection; void getSupportedFormats(); + void select(const File* file); void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void onExplore(); int calcThumbSize(int availableWidth) const; int calcWidgetHeight(int availableWidth) const; -- cgit v1.3.1 From fb93d9ff2d1c158fb546471f6e18e4dc9b965c2f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 13:02:11 -0400 Subject: nexus tab: fixed to only make one request, changed css to match nexus more closely bbcode now supports [img width=x,height=x] images tab: don't reload the original image --- src/bbcode.cpp | 2 +- src/modinfodialogimages.cpp | 12 +++++------ src/modinfodialognexus.cpp | 52 +++++++++++++++++++++++++++++++++------------ src/modinfodialognexus.h | 1 + 4 files changed, 46 insertions(+), 21 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 3475f1b2..9f064106 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -174,7 +174,7 @@ private: "\\1"); m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + m_TagMap["img"] = std::make_pair(QRegExp("\\[img(?:\\s*width=\\d+\\s*,?\\s*height=\\d+)?\\](.*)\\[/img\\]"), ""); m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "\"\\1\""); diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 22c004e6..9b0e98c5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -282,14 +282,14 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const void ImagesTab::reload(File& file, const QSize& scaledSize) { - file.original = {}; - file.thumbnail = {}; file.failed = false; - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; + if (file.original.isNull()) { + if (!file.original.load(file.path)) { + qCritical() << "failed to load image from " << file.path; + file.failed = true; + return; + } } file.thumbnail = file.original.scaled( diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index d296e000..797f7923 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -7,10 +7,16 @@ #include #include +bool isValidModID(int id) +{ + return (id > 0); +} + NexusTab::NexusTab( OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false) + QWidget* parent, Ui::ModInfoDialog* ui, int id) : + ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), + m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -53,6 +59,8 @@ void NexusTab::clear() void NexusTab::update() { + QScopedValueRollback loading(m_loading, true); + clear(); ui->modID->setText(QString("%1").arg(mod()->getNexusID())); @@ -123,7 +131,7 @@ void NexusTab::updateWebpage() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -147,16 +155,19 @@ void NexusTab::onModChanged() QString descriptionAsHTML = R"( - @@ -181,6 +192,10 @@ void NexusTab::onModChanged() void NexusTab::onModIDChanged() { + if (m_loading) { + return; + } + const int oldID = mod()->getNexusID(); const int newID = ui->modID->text().toInt(); @@ -190,7 +205,7 @@ void NexusTab::onModIDChanged() ui->browser->page()->setHtml(""); - if (newID != 0) { + if (isValidModID(newID)) { refreshData(newID); } } @@ -198,6 +213,10 @@ void NexusTab::onModIDChanged() void NexusTab::onSourceGameChanged() { + if (m_loading) { + return; + } + for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { mod()->setGameName(game->gameShortName()); @@ -210,6 +229,10 @@ void NexusTab::onSourceGameChanged() void NexusTab::onVersionChanged() { + if (m_loading) { + return; + } + MOBase::VersionInfo version(ui->version->text()); mod()->setVersion(version); updateVersionColor(); @@ -217,6 +240,10 @@ void NexusTab::onVersionChanged() void NexusTab::onUrlChanged() { + if (m_loading) { + return; + } + mod()->setURL(ui->url->text()); mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); } @@ -225,7 +252,7 @@ void NexusTab::onOpenLink() { const int modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) ->getModURL(modID, mod()->getGameName()); @@ -237,7 +264,7 @@ void NexusTab::onRefreshBrowser() { const auto modID = mod()->getNexusID(); - if (modID > 0) { + if (isValidModID(modID)) { refreshData(modID); } else qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -259,18 +286,15 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { - if (modID <= 0) { - qDebug() << "NexusTab: can't refresh, no mod id"; + if (!isValidModID(modID)) { return false; } if (m_requestStarted) { - qDebug() << "NexusTab: a refresh request is already running"; return false; } if (!mod()->updateNXMInfo()) { - qDebug() << "NexusTab: nexus description does not need an update"; return false; } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index a09f4316..86d87c30 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -47,6 +47,7 @@ public: private: QMetaObject::Connection m_modConnection; bool m_requestStarted; + bool m_loading; void cleanup(); void updateVersionColor(); -- cgit v1.3.1 From 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/modinfodialogimages.cpp') 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 fc3ed80d0de220a1aa9d2b459785d47beea5861b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:58:34 -0400 Subject: tooltip on images --- src/modinfodialogimages.cpp | 24 ++++++++++++++++++++++++ src/modinfodialogimages.h | 2 ++ 2 files changed, 26 insertions(+) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index deaacd14..08fd5c5b 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -314,6 +314,20 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::showTooltip(QHelpEvent* e) +{ + const auto* f = fileAtPos(e->pos()); + if (!f) { + QToolTip::hideText(); + e->ignore(); + return; + } + + QToolTip::showText( + e->globalPos(), QDir::toNativeSeparators(f->path), + ui->imagesThumbnails); +} + void ImagesTab::onExplore() { if (!m_selection) { @@ -412,6 +426,16 @@ void ImagesThumbnails::mousePressEvent(QMouseEvent* e) } } +bool ImagesThumbnails::event(QEvent* e) +{ + if (e->type() == QEvent::ToolTip) { + m_tab->showTooltip(static_cast(e)); + return true; + } + + return QWidget::event(e); +} + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d90f2bfd..bd404801 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -34,6 +34,7 @@ public: protected: void paintEvent(QPaintEvent* e) override; void mousePressEvent(QMouseEvent* e) override; + bool event(QEvent* e) override; private: ImagesTab* m_tab = nullptr; @@ -118,6 +119,7 @@ private: void scrollAreaResized(const QSize& s); void paintThumbnails(QPaintEvent* e); void thumbnailsMouseEvent(QMouseEvent* e); + void showTooltip(QHelpEvent* e); void onExplore(); void onFilterChanged(); -- cgit v1.3.1 From 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/modinfodialogimages.cpp') 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/modinfodialogimages.cpp') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 3ea0a27b..a9af2593 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -172,7 +172,7 @@ false - + 3 @@ -189,20 +189,34 @@ 0 - - - Qt::ScrollBarAlwaysOn - - - - - 0 - 0 - 686 - 436 - + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 - + + + + + + + Qt::Vertical + + + + @@ -1221,16 +1235,15 @@ p, li { white-space: pre-wrap; }
texteditor.h
- ImagesScrollArea - QScrollArea + ImagesThumbnails + QWidget
modinfodialogimages.h
1
- ImagesThumbnails - QWidget + ImagesScrollbar + QScrollBar
modinfodialogimages.h
- 1
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index e25fd838..5c93e3d4 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -3,11 +3,36 @@ #include "settings.h" #include "utility.h" +QSize resizeWithAspectRatio(const QSize& original, const QSize& available) +{ + const auto ratio = std::min({ + 1.0, + static_cast(available.width()) / original.width(), + static_cast(available.height()) / original.height()}); + + const QSize scaledSize( + static_cast(std::round(original.width() * ratio)), + static_cast(std::round(original.height() * ratio))); + + return scaledSize; +} + +QRect centeredRect(const QRect& rect, const QSize& size) +{ + return QRect( + (rect.left()+rect.width()/2) - size.width()/2, + (rect.top()+rect.height()/2) - size.height()/2, + size.width(), + size.height()); +} + + ImagesTab::ImagesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1), + m_image(new ScalableImage), + m_margins(3), m_border(1), m_padding(0), m_spacing(5), m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -22,10 +47,12 @@ ImagesTab::ImagesTab( ui->tabImagesSplitter->setStretchFactor(0, 0); ui->tabImagesSplitter->setStretchFactor(1, 1); - ui->imagesScrollArea->setWidgetResizable(false); - ui->imagesScrollArea->setTab(this); ui->imagesThumbnails->setTab(this); + ui->imagesScrollerVBar->setTab(this); + ui->imagesScrollerVBar->setSingleStep(1); + connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); + ui->imagesShowDDS->setEnabled(m_ddsAvailable); m_filter.setEdit(ui->imagesFilter); @@ -35,12 +62,16 @@ ImagesTab::ImagesTab( connect(ui->imagesShowDDS, &QCheckBox::toggled, [&]{ onShowDDS(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); + + ui->imagesThumbnails->setAutoFillBackground(false); + ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); } void ImagesTab::clear() { m_files.clear(); m_filteredFiles.clear(); + ui->imagesScrollerVBar->setValue(0); select(nullptr); setHasData(false); @@ -202,157 +233,98 @@ void ImagesTab::select(const File* f) m_selection = f; } -int ImagesTab::calcThumbSize(int availableWidth) const -{ - return availableWidth - (m_margins * 2); -} - -int ImagesTab::calcWidgetHeight(int availableWidth) const +ImagesGeometry ImagesTab::makeGeometry() const { - if (fileCount() == 0) { - return 0; - } - - const auto thumbSize = calcThumbSize(availableWidth); - - int h = 0; - - // first thumb - h = thumbSize; - - // subsequent thumbs with padding before each - const auto thumbWithPadding = m_padding + thumbSize; - h += static_cast(thumbWithPadding * (fileCount() - 1)); - - // margin top and bottom - h += m_margins * 2; - - return h; -} - -QRect ImagesTab::calcTopThumbRect(int thumbSize) const -{ - return {m_margins, m_margins, thumbSize, thumbSize}; + return ImagesGeometry( + ui->imagesThumbnails->size(), + m_margins, m_border, m_padding, m_spacing); } -std::pair ImagesTab::calcVisibleRange( - int top, int bottom, int thumbSize) const +void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const std::size_t begin = top / (thumbSize + m_padding); - const std::size_t end = bottom / (thumbSize + m_padding) + 1; + const auto geo = makeGeometry(); - return {begin, end}; -} + QPainter painter(ui->imagesThumbnails); -QRect ImagesTab::calcBorderRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return { - topRect.left(), - static_cast(topRect.top() + (i * (thumbSize + m_padding))), - thumbSize, - thumbSize - }; -} + painter.fillRect( + ui->imagesThumbnails->rect(), + ui->imagesThumbnails->palette().color(QPalette::Window)); -QRect ImagesTab::calcImageRect( - const QRect& topRect, int thumbSize, std::size_t i) const -{ - return calcBorderRect(topRect, thumbSize, i).adjusted( - m_border, m_border, -m_border, -m_border); -} + const auto visible = geo.fullyVisibleCount() + 1; + const auto first = ui->imagesScrollerVBar->value(); -QSize ImagesTab::calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const -{ - const auto ratio = std::min({ - 1.0, - static_cast(imageSize.width()) / originalSize.width(), - static_cast(imageSize.height()) / originalSize.height()}); - - const QSize scaledSize( - static_cast(std::round(originalSize.width() * ratio)), - static_cast(std::round(originalSize.height() * ratio))); - - return scaledSize; -} - -void ImagesTab::paintThumbnails(QPaintEvent* e) -{ - PaintContext cx(ui->imagesThumbnails); - cx.thumbSize = calcThumbSize(ui->imagesThumbnails->width()); - cx.topRect = calcTopThumbRect(cx.thumbSize); - - const auto [begin, end] = calcVisibleRange( - e->rect().top(), e->rect().bottom(), cx.thumbSize); + for (std::size_t i=0; ifailed) { + if (file.failed) { return; } - const auto imageRect = calcImageRect(cx.topRect, cx.thumbSize, i); - - if (needsReload(*file, imageRect.size())) { - reload(*file, imageRect.size()); + if (needsReload(geo, file)) { + reload(geo, file); } - if (file->thumbnail.isNull()) { + if (file.thumbnail.isNull()) { return; } - // center scaled image in rect - const QRect scaledThumbRect( - (imageRect.left()+imageRect.width()/2) - file->thumbnail.width()/2, - (imageRect.top()+imageRect.height()/2) - file->thumbnail.height()/2, - file->thumbnail.width(), - file->thumbnail.height()); + const auto imageRect = geo.imageRect(i); + const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - cx.painter.drawImage(scaledThumbRect, file->thumbnail); + painter.drawImage(scaledThumbRect, file.thumbnail); } const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const { - const auto thumbSize = calcThumbSize(ui->imagesThumbnails->width()); + const auto geo = makeGeometry(); - // calculate index purely based on y position - const std::size_t i = p.y() / (thumbSize + m_padding); - if (i >= fileCount()) { + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { return nullptr; } - // get actual rect - const auto topRect = calcTopThumbRect(thumbSize); - const auto rect = calcBorderRect(topRect, thumbSize, i); + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return nullptr; + } - if (!rect.contains(p)) { + const auto i = static_cast(first) + offset; + if (i >= fileCount()) { return nullptr; } @@ -364,7 +336,7 @@ void ImagesTab::scrollAreaResized(const QSize&) resizeWidget(); } -void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) +void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) { if (e->button() != Qt::LeftButton) { return; @@ -373,6 +345,19 @@ void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e) select(fileAtPos(e->pos())); } +void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) +{ + const auto d = (e->angleDelta() / 8).y(); + + ui->imagesScrollerVBar->setValue( + ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); +} + +void ImagesTab::onScrolled() +{ + ui->imagesThumbnails->update(); +} + void ImagesTab::showTooltip(QHelpEvent* e) { const auto* f = fileAtPos(e->pos()); @@ -410,7 +395,7 @@ void ImagesTab::onFilterChanged() update(); } -bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const +bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const { if (file.failed) { return false; @@ -420,15 +405,11 @@ bool ImagesTab::needsReload(const File& file, const QSize& imageSize) const return true; } - const auto scaledSize = calcScaledImageSize(file.original.size(), imageSize); - if (file.thumbnail.size() != scaledSize) { - return true; - } - - return false; + const auto scaledSize = geo.scaledImageSize(file.original.size()); + return (file.thumbnail.size() != scaledSize); } -void ImagesTab::reload(File& file, const QSize& scaledSize) +void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; @@ -441,56 +422,63 @@ void ImagesTab::reload(File& file, const QSize& scaledSize) } file.thumbnail = file.original.scaled( - calcScaledImageSize(file.original.size(), scaledSize), + geo.scaledImageSize(file.original.size()), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } void ImagesTab::resizeWidget() { if (fileCount() == 0) { - ui->imagesThumbnails->setGeometry(QRect()); + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); return; } - const auto availableWidth = ui->imagesScrollArea->viewport()->width(); + const auto geo = makeGeometry(); + const auto availableSize = ui->imagesThumbnails->size(); + const auto fullyVisible = geo.fullyVisibleCount(); - const int widgetHeight = std::max( - calcWidgetHeight(availableWidth), - ui->imagesScrollArea->viewport()->height()); - - ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, widgetHeight)); + if (fullyVisible >= fileCount()) { + ui->imagesScrollerVBar->setRange(0, 0); + ui->imagesScrollerVBar->setEnabled(false); + } else { + const auto d = fileCount() - fullyVisible; + ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setEnabled(true); + } } -void ImagesScrollArea::setTab(ImagesTab* tab) +void ImagesThumbnails::setTab(ImagesTab* tab) { m_tab = tab; } -void ImagesScrollArea::resizeEvent(QResizeEvent* e) +void ImagesThumbnails::paintEvent(QPaintEvent* e) { if (m_tab) { - m_tab->scrollAreaResized(e->size()); + m_tab->paintThumbnailsArea(e); } } - -void ImagesThumbnails::setTab(ImagesTab* tab) +void ImagesThumbnails::mousePressEvent(QMouseEvent* e) { - m_tab = tab; + if (m_tab) { + m_tab->thumbnailAreaMouseEvent(e); + } } -void ImagesThumbnails::paintEvent(QPaintEvent* e) +void ImagesThumbnails::wheelEvent(QWheelEvent* e) { if (m_tab) { - m_tab->paintThumbnails(e); + m_tab->thumbnailAreaWheelEvent(e); } } -void ImagesThumbnails::mousePressEvent(QMouseEvent* e) +void ImagesThumbnails::resizeEvent(QResizeEvent* e) { if (m_tab) { - m_tab->thumbnailsMouseEvent(e); + m_tab->scrollAreaResized(e->size()); } } @@ -505,6 +493,19 @@ bool ImagesThumbnails::event(QEvent* e) } +void ImagesScrollbar::setTab(ImagesTab* tab) +{ + m_tab = tab; +} + +void ImagesScrollbar::wheelEvent(QWheelEvent* e) +{ + if (m_tab) { + m_tab->thumbnailAreaWheelEvent(e); + } +} + + ScalableImage::ScalableImage(QString path) : m_path(std::move(path)), m_border(1) { @@ -564,14 +565,8 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect imageRect = widgetRect.adjusted( m_border, m_border, -m_border, -m_border); - const auto ratio = std::min({ - 1.0, - static_cast(imageRect.width()) / m_original.width(), - static_cast(imageRect.height()) / m_original.height()}); - - const QSize scaledSize( - static_cast(std::round(m_original.width() * ratio)), - static_cast(std::round(m_original.height() * ratio))); + const QSize scaledSize = resizeWithAspectRatio( + m_original.size(), imageRect.size()); if (m_scaled.isNull() || m_scaled.size() != scaledSize) { m_scaled = m_original.scaled( @@ -580,16 +575,98 @@ void ScalableImage::paintEvent(QPaintEvent* e) } const QRect drawBorderRect = widgetRect.adjusted(0, 0, -1, -1); - - const QRect drawImageRect( - (imageRect.left()+imageRect.width()/2) - m_scaled.width()/2, - (imageRect.top()+imageRect.height()/2) - m_scaled.height()/2, - m_scaled.width(), m_scaled.height()); - + const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); painter.drawRect(drawBorderRect); painter.drawImage(drawImageRect, m_scaled); } + + +ImagesGeometry::ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing) : + m_widgetSize(widgetSize), + m_margins(margins), m_padding(padding), m_border(border), + m_spacing(spacing), m_topRect(calcTopRect()) +{ +} + +QRect ImagesGeometry::calcTopRect() const +{ + const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); + const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + + return {m_margins, m_margins, thumbWidth, thumbHeight}; +} + +std::size_t ImagesGeometry::fullyVisibleCount() const +{ + const auto r = thumbRect(0); + const auto visible = (m_widgetSize.height() / (r.height() + m_spacing)); + return static_cast(visible); +} + +QRect ImagesGeometry::thumbRect(std::size_t i) const +{ + // rect for the top thumbnail + QRect r = m_topRect; + + // move down + const auto thumbWithSpacing = m_spacing + r.height(); + r.translate(0, static_cast(i * thumbWithSpacing)); + + return r; +} + +QRect ImagesGeometry::borderRect(std::size_t i) const +{ + auto r = thumbRect(i); + + // border rect is currently the same as thumb rect, but may change if + // captions are added + + return r; +} + +QRect ImagesGeometry::imageRect(std::size_t i) const +{ + auto r = borderRect(i); + + // remove border and padding + const auto m = m_border + m_padding; + r.adjust(m, m, -m, -m); + + return r; +} + +std::size_t ImagesGeometry::indexAt(const QPoint& p) const +{ + // calculate index purely based on y position + const std::size_t offset = p.y() / (m_topRect.height() + m_spacing); + + if (!borderRect(offset).contains(p)) { + return BadIndex; + } + + return offset; +} + +QSize ImagesGeometry::scaledImageSize(const QSize& originalSize) const +{ + const auto availableSize = imageRect(0).size(); + return resizeWithAspectRatio(originalSize, availableSize); +} + +void ImagesGeometry::dump() const +{ + qDebug() + << "ImagesTab geometry:\n" + << " . widget size: " << m_widgetSize << "\n" + << " . margins: " << m_margins << "\n" + << " . border: " << m_border << "\n" + << " . padding: " << m_padding << "\n" + << " . spacing: " << m_spacing << "\n" + << " . top rect: " << m_topRect; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index fa7115b0..60ce9def 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -3,26 +3,32 @@ #include "modinfodialogtab.h" #include "filterwidget.h" -#include +#include class ImagesTab; -class ImagesScrollArea : public QScrollArea +// vertical scrollbar, this is only to handle wheel events to scroll by one +// instead of the system's scroll setting +// +class ImagesScrollbar : public QScrollBar { - Q_OBJECT; - public: - using QScrollArea::QScrollArea; + using QScrollBar::QScrollBar; void setTab(ImagesTab* tab); protected: - void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* event) override; private: ImagesTab* m_tab = nullptr; }; +// widget inside the scroller, calls ImagesTab::paintThumbnailArea() when +// needed and also forwards mouse clicks and tooltip events +// class ImagesThumbnails : public QWidget { Q_OBJECT; @@ -32,8 +38,24 @@ public: void setTab(ImagesTab* tab); protected: + // forwards to ImagesTab::paintThumbnailArea() + // void paintEvent(QPaintEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaMouseEvent() + // void mousePressEvent(QMouseEvent* e) override; + + // forwards to ImagesTab::thumbnailAreaWheelEvent() + // + void wheelEvent(QWheelEvent* e); + + // forwards to ImagesTab::scrollAreaResized() + // + void resizeEvent(QResizeEvent* e) override; + + // forwards to ImagesTab::showTooltip for tooltip events + // bool event(QEvent* e) override; private: @@ -41,6 +63,8 @@ private: }; +// a widget that draws an image scaled to fit while keeping the aspect ratio +// class ScalableImage : public QWidget { Q_OBJECT; @@ -48,12 +72,16 @@ class ScalableImage : public QWidget public: ScalableImage(QString path={}); + // sets the image to draw void setImage(const QString& path); void setImage(QImage image); + + // removes the image, won't draw the border nor the image void clear(); - bool hasHeightForWidth() const; - int heightForWidth(int w) const; + // tells the QWidget's layout manager this widget is always square + bool hasHeightForWidth() const override; + int heightForWidth(int w) const override; protected: void paintEvent(QPaintEvent* e) override; @@ -65,10 +93,105 @@ private: }; +// handles all the geometry calculations by ImagesTab for painting or handling +// mouse clicks +// +// a thumbnail looks like this: +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// | +-border--------+ <------ border rect +// | | padding | | +// | | | | +// | | +-------+ <----------- image rect +// | | | | | | +// | | | image | | | +// | | | | | | +// | | +-------+ | | +// | | | | +// | +---------------+ | +// | | +// +-----------------------+ +// +// spacing +// +// +-----------------------+ <-- thumb rect +// | margins | +// | | +// .... +// +// +class ImagesGeometry +{ +public: + // returned by indexAt() if the point is outside any possible thumbnail + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + + ImagesGeometry( + const QSize& widgetSize, int margins, int border, int padding, int spacing); + + // returns the number of images fully visible in the widget + // + std::size_t fullyVisibleCount() const; + + // rectangle around the whole thumbnail + // + QRect thumbRect(std::size_t i) const; + + // rectangle of the border for the given thumbnail + // + QRect borderRect(std::size_t i) const; + + // rectangle of the image for the given thumbnail + // + QRect imageRect(std::size_t i) const; + + // returns the index of the image at the given point; this does not take into + // account any scrolling, the image at the top of widget is always 0 + // + std::size_t indexAt(const QPoint& p) const; + + // returns the size of the image that fits in imageRect() while keeping the + // same aspect ratio as the given one + // + QSize scaledImageSize(const QSize& originalSize) const; + + // dumps stuff to qDebug() + // + void dump() const; + +private: + // size of the widget containing all the thumbnails + const QSize m_widgetSize; + + // space outside the thumbnail border + const int m_margins; + + // size of the border + const int m_border; + + // space between the border and the image + const int m_padding; + + // spacing between thumbnails + const int m_spacing; + + // rectangle of the first thumbnail on top + const QRect m_topRect; + + + // calculates the top rectangle + // + QRect calcTopRect() const; +}; + + class ImagesTab : public ModInfoDialogTab { Q_OBJECT; - friend class ImagesScrollArea; + friend class ImagesScrollbar; friend class ImagesThumbnails; public: @@ -95,23 +218,11 @@ private: } }; - struct PaintContext - { - QPainter painter; - int thumbSize; - QRect topRect; - - PaintContext(QWidget* w) - : painter(w), thumbSize(0) - { - } - }; - ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; std::vector m_supportedFormats; - int m_margins, m_padding, m_border; + int m_margins, m_border, m_padding, m_spacing; const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; @@ -122,27 +233,29 @@ private: bool needsFiltering() const; void scrollAreaResized(const QSize& s); - void paintThumbnails(QPaintEvent* e); - void thumbnailsMouseEvent(QMouseEvent* e); + void paintThumbnailsArea(QPaintEvent* e); + void thumbnailAreaMouseEvent(QMouseEvent* e); + void thumbnailAreaWheelEvent(QWheelEvent* e); + void onScrolled(); + void showTooltip(QHelpEvent* e); void onExplore(); void onShowDDS(); void onFilterChanged(); - int calcThumbSize(int availableWidth) const; - int calcWidgetHeight(int availableWidth) const; - QRect calcTopThumbRect(int thumbSize) const; - std::pair calcVisibleRange( - int top, int bottom, int thumbSize) const; + ImagesGeometry makeGeometry() const; + + void paintThumbnail( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); - QRect calcBorderRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QRect calcImageRect(const QRect& topRect, int thumbSize, std::size_t i) const; - QSize calcScaledImageSize( - const QSize& originalSize, const QSize& imageSize) const; + void paintThumbnailBorder( + QPainter& painter, const ImagesGeometry& geo, + std::size_t i); - void paintThumbnail(PaintContext& cx, std::size_t i); - void paintThumbnailBorder(PaintContext& cx, std::size_t i); - void paintThumbnailImage(PaintContext& cx, std::size_t i); + void paintThumbnailImage( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); const File* fileAtPos(const QPoint& p) const; @@ -151,8 +264,8 @@ private: File* getFile(std::size_t i); void filterImages(); - bool needsReload(const File& file, const QSize& imageSize) const; - void reload(File& file, const QSize& imageSize); + bool needsReload(const ImagesGeometry& geo, const File& file) const; + void reload(const ImagesGeometry& geo, File& file); void resizeWidget(); }; -- cgit v1.3.1 From 48607afc65e2f06bbc8b4b05386c6de89a33b7bf Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 07:27:41 -0400 Subject: changed thumbnails to use margin around border selected thumbnail now has a background color --- src/modinfodialogimages.cpp | 63 +++++++++++++++++++++++++++++++++++++-------- src/modinfodialogimages.h | 16 +++++++++++- 2 files changed, 67 insertions(+), 12 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 5c93e3d4..7e021f97 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -65,6 +65,17 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setAutoFillBackground(false); ui->imagesThumbnails->setAttribute(Qt::WA_OpaquePaintEvent, true); + + { + auto list = std::make_unique(); + parentWidget()->style()->polish(list.get()); + + m_colors.border = QColor(Qt::black); + m_colors.background = QColor(Qt::black); + m_colors.selection = list->palette().color(QPalette::Highlight); + + m_image->setColors(m_colors.border, m_colors.background); + } } void ImagesTab::clear() @@ -92,7 +103,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { filterImages(); - resizeWidget(); + updateScrollbar(); ui->imagesThumbnails->update(); setHasData(fileCount() > 0); @@ -231,6 +242,7 @@ void ImagesTab::select(const File* f) } m_selection = f; + ui->imagesThumbnails->update(); } ImagesGeometry ImagesTab::makeGeometry() const @@ -268,10 +280,21 @@ void ImagesTab::paintThumbnail( QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i) { + paintThumbnailBackground(painter, geo, file, i); paintThumbnailBorder(painter, geo, i); paintThumbnailImage(painter, geo, file, i); } +void ImagesTab::paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i) +{ + if (&file == m_selection) { + const auto rect = geo.thumbRect(i); + painter.fillRect(rect, m_colors.selection); + } +} + void ImagesTab::paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i) { @@ -282,7 +305,7 @@ void ImagesTab::paintThumbnailBorder( borderRect.setRight(borderRect.right() - 1); borderRect.setBottom(borderRect.bottom() - 1); - painter.setPen(QColor(Qt::black)); + painter.setPen(m_colors.border); painter.drawRect(borderRect); } @@ -305,6 +328,7 @@ void ImagesTab::paintThumbnailImage( const auto imageRect = geo.imageRect(i); const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); + painter.fillRect(scaledThumbRect, m_colors.background); painter.drawImage(scaledThumbRect, file.thumbnail); } @@ -333,7 +357,7 @@ const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const void ImagesTab::scrollAreaResized(const QSize&) { - resizeWidget(); + updateScrollbar(); } void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) @@ -426,7 +450,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } -void ImagesTab::resizeWidget() +void ImagesTab::updateScrollbar() { if (fileCount() == 0) { ui->imagesScrollerVBar->setRange(0, 0); @@ -547,6 +571,12 @@ int ScalableImage::heightForWidth(int w) const return w; } +void ScalableImage::setColors(const QColor& border, const QColor& background) +{ + m_borderColor = border; + m_backgroundColor = background; +} + void ScalableImage::paintEvent(QPaintEvent* e) { if (m_original.isNull()) { @@ -578,8 +608,15 @@ void ScalableImage::paintEvent(QPaintEvent* e) const QRect drawImageRect = centeredRect(imageRect, m_scaled.size()); QPainter painter(this); - painter.setPen(QColor(Qt::black)); + + // background + painter.fillRect(drawBorderRect, m_backgroundColor); + + // border + painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + + // image painter.drawImage(drawImageRect, m_scaled); } @@ -594,11 +631,14 @@ ImagesGeometry::ImagesGeometry( QRect ImagesGeometry::calcTopRect() const { - const auto thumbWidth = m_widgetSize.width() - (m_margins * 2); - const auto imageSize = thumbWidth - (m_border * 2) - (m_padding * 2); - const auto thumbHeight = m_padding + m_border + imageSize + m_border + m_padding; + const auto thumbWidth = m_widgetSize.width(); + const auto imageSize = thumbWidth - (m_margins * 2) - (m_border * 2) - (m_padding * 2); + const auto thumbHeight = + m_margins + m_border + m_padding + + imageSize + + m_border + m_padding + m_margins; - return {m_margins, m_margins, thumbWidth, thumbHeight}; + return {0, 0, thumbWidth, thumbHeight}; } std::size_t ImagesGeometry::fullyVisibleCount() const @@ -624,8 +664,9 @@ QRect ImagesGeometry::borderRect(std::size_t i) const { auto r = thumbRect(i); - // border rect is currently the same as thumb rect, but may change if - // captions are added + // remove margins + const auto m = m_margins; + r.adjust(m, m, -m, -m); return r; } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 60ce9def..db541ed8 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -83,6 +83,9 @@ public: bool hasHeightForWidth() const override; int heightForWidth(int w) const override; + // sets the colors + void setColors(const QColor& border, const QColor& background); + protected: void paintEvent(QPaintEvent* e) override; @@ -90,6 +93,7 @@ private: QString m_path; QImage m_original, m_scaled; int m_border; + QColor m_borderColor, m_backgroundColor; }; @@ -218,6 +222,11 @@ private: } }; + struct Colors + { + QColor border, background, selection; + }; + ScalableImage* m_image; std::vector m_files; std::vector m_filteredFiles; @@ -226,6 +235,7 @@ private: const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; + Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); @@ -249,6 +259,10 @@ private: QPainter& painter, const ImagesGeometry& geo, File& file, std::size_t i); + void paintThumbnailBackground( + QPainter& painter, const ImagesGeometry& geo, + File& file, std::size_t i); + void paintThumbnailBorder( QPainter& painter, const ImagesGeometry& geo, std::size_t i); @@ -266,7 +280,7 @@ private: void filterImages(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); - void resizeWidget(); + void updateScrollbar(); }; #endif // MODINFODIALOGIMAGES_H -- cgit v1.3.1 From c2df11298b3be4919a660206e5100ee6dbdecd1c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 08:43:18 -0400 Subject: refactored stuff into PaintContext and Files keep selection when switching back to no filtering up/down keyboard navigation --- src/modinfodialog.ui | 6 +- src/modinfodialogimages.cpp | 445 ++++++++++++++++++++++++++++++-------------- src/modinfodialogimages.h | 98 +++++++--- 3 files changed, 387 insertions(+), 162 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a9af2593..e745967d 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -207,7 +207,11 @@ 0 - + + + Qt::StrongFocus + + diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 7e021f97..4179c62a 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -33,7 +33,7 @@ ImagesTab::ImagesTab( ModInfoDialogTab(oc, plugin, parent, ui, id), m_image(new ScalableImage), m_margins(3), m_border(1), m_padding(0), m_spacing(5), - m_selection(nullptr), m_ddsAvailable(false), m_ddsEnabled(false) + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); @@ -80,11 +80,8 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { - m_files.clear(); - m_filteredFiles.clear(); ui->imagesScrollerVBar->setValue(0); - - select(nullptr); + select(BadIndex); setHasData(false); } @@ -92,7 +89,7 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) { for (const auto& ext : m_supportedFormats) { if (fullPath.endsWith(ext, Qt::CaseInsensitive)) { - m_files.push_back({fullPath}); + m_files.add({fullPath}); return true; } } @@ -102,11 +99,11 @@ bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath) void ImagesTab::update() { - filterImages(); + checkFiltering(); updateScrollbar(); ui->imagesThumbnails->update(); - setHasData(fileCount() > 0); + setHasData(m_files.size() > 0); } void ImagesTab::saveState(Settings& s) @@ -121,88 +118,79 @@ void ImagesTab::restoreState(const Settings& s) .value("mod_info_dialog_images_show_dds", false).toBool()); } -void ImagesTab::filterImages() +void ImagesTab::checkFiltering() { - if (!needsFiltering()) { - return; + if (m_filter.empty() && m_ddsEnabled) { + // no filtering needed + + if (m_files.isFiltered()) { + // was filtered, needs switch + switchToAll(); + } + } else { + // filtering is needed + switchToFiltered(); } +} - m_filteredFiles.clear(); +void ImagesTab::switchToAll() +{ + // remember selection + const auto* oldSelection = m_files.selectedFile(); + + // switch + m_files.switchToAll(); + + // reselect old + if (oldSelection) { + m_files.select(m_files.indexOf(oldSelection)); + } else { + m_files.select(BadIndex); + } +} + +void ImagesTab::switchToFiltered() +{ + // remember old selection, will be checked when building the filtered list + // below + const auto* oldSelection = m_files.selectedFile(); + std::size_t newSelection = BadIndex; + + // switch, also clears list + m_files.switchToFiltered(); - bool hasTextFilter = !m_filter.empty(); - bool sawSelection = false; + const bool hasTextFilter = !m_filter.empty(); - for (auto& f : m_files) { + for (auto& f : m_files.allFiles()) { if (hasTextFilter) { + // check filter widget const auto m = m_filter.matches([&](auto&& what) { return f.path.contains(what, Qt::CaseInsensitive); - }); + }); if (!m) { + // no match, skip continue; } } if (!m_ddsEnabled) { + // skip .dds files if (f.path.endsWith(".dds", Qt::CaseInsensitive)) { continue; } } - if (&f == m_selection) { - sawSelection = true; - } - - m_filteredFiles.push_back(&f); - } - - if (!sawSelection) { - select(nullptr); - } -} - -std::size_t ImagesTab::fileCount() const -{ - if (needsFiltering()) { - return m_filteredFiles.size(); - } else { - return m_files.size(); - } -} - -const ImagesTab::File* ImagesTab::getFile(std::size_t i) const -{ - if (needsFiltering()) { - if (i >= m_filteredFiles.size()) { - return nullptr; - } - - return m_filteredFiles[i]; - } else { - if (i >= m_files.size()) { - return nullptr; + if (&f == oldSelection) { + // found the old selection, remember its index + newSelection = m_files.size(); } - return &m_files[i]; - } -} - -ImagesTab::File* ImagesTab::getFile(std::size_t i) -{ - return const_cast(std::as_const(*this).getFile(i)); -} - -bool ImagesTab::needsFiltering() const -{ - if (!m_filter.empty()) { - return true; + m_files.addFiltered(&f); } - if (!m_ddsEnabled) { - return true; - } - - return false; + // reselect old, or clear if it wasn't found + select(newSelection); } void ImagesTab::getSupportedFormats() @@ -229,22 +217,101 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(const File* f) +void ImagesTab::select(std::size_t i) { - if (f) { + m_files.select(i); + + if (const auto* f=m_files.selectedFile()) { ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); + ensureVisible(i); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); m_image->clear(); } - m_selection = f; ui->imagesThumbnails->update(); } +void ImagesTab::moveSelection(int direction) +{ + const auto i = m_files.selectedIndex(); + + if (i == BadIndex) { + if (m_files.size() > 0) { + select(0); + } + + return; + } + + if (direction > 0) { + // moving down + if (i < (m_files.size() - 1)) { + select(i + 1); + } + } else { + // moving up + if (i > 0) { + select(i - 1); + } + } +} + +void ImagesTab::ensureVisible(std::size_t i) +{ + const auto geo = makeGeometry(); + + const auto fullyVisible = geo.fullyVisibleCount(); + const auto first = ui->imagesScrollerVBar->value(); + + if (i < first) { + // go up + ui->imagesScrollerVBar->setValue(static_cast(i)); + } else if (i >= first + fullyVisible) { + // go down + + if (i >= fullyVisible) { + ui->imagesScrollerVBar->setValue(static_cast(i - fullyVisible + 1)); + } + } +} + +std::size_t ImagesTab::fileIndexAtPos(const QPoint& p) const +{ + const auto geo = makeGeometry(); + + // this is the index relative to the top + const auto offset = geo.indexAt(p); + if (offset == ImagesGeometry::BadIndex) { + return BadIndex; + } + + const auto first = ui->imagesScrollerVBar->value(); + if (first < 0) { + return BadIndex; + } + + const auto i = static_cast(first) + offset; + if (i >= m_files.size()) { + return BadIndex; + } + + return i; +} + +const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const +{ + const auto i = fileIndexAtPos(p); + if (i >= m_files.size()) { + return nullptr; + } + + return m_files.get(i); +} + ImagesGeometry ImagesTab::makeGeometry() const { return ImagesGeometry( @@ -254,105 +321,78 @@ ImagesGeometry ImagesTab::makeGeometry() const void ImagesTab::paintThumbnailsArea(QPaintEvent* e) { - const auto geo = makeGeometry(); - - QPainter painter(ui->imagesThumbnails); + PaintContext cx(ui->imagesThumbnails, makeGeometry()); - painter.fillRect( + cx.painter.fillRect( ui->imagesThumbnails->rect(), ui->imagesThumbnails->palette().color(QPalette::Window)); - const auto visible = geo.fullyVisibleCount() + 1; + const auto visible = cx.geo.fullyVisibleCount() + 1; const auto first = ui->imagesScrollerVBar->value(); for (std::size_t i=0; ifailed) { return; } - if (needsReload(geo, file)) { - reload(geo, file); + if (needsReload(cx.geo, *cx.file)) { + reload(cx.geo, *cx.file); } - if (file.thumbnail.isNull()) { + if (cx.file->thumbnail.isNull()) { return; } - const auto imageRect = geo.imageRect(i); - const auto scaledThumbRect = centeredRect(imageRect, file.thumbnail.size()); - - painter.fillRect(scaledThumbRect, m_colors.background); - painter.drawImage(scaledThumbRect, file.thumbnail); -} - -const ImagesTab::File* ImagesTab::fileAtPos(const QPoint& p) const -{ - const auto geo = makeGeometry(); - - // this is the index relative to the top - const auto offset = geo.indexAt(p); - if (offset == ImagesGeometry::BadIndex) { - return nullptr; - } - - const auto first = ui->imagesScrollerVBar->value(); - if (first < 0) { - return nullptr; - } - - const auto i = static_cast(first) + offset; - if (i >= fileCount()) { - return nullptr; - } + const auto imageRect = cx.geo.imageRect(cx.thumbIndex); + const auto scaledThumbRect = centeredRect( + imageRect, cx.file->thumbnail.size()); - return getFile(i); + cx.painter.fillRect(scaledThumbRect, m_colors.background); + cx.painter.drawImage(scaledThumbRect, cx.file->thumbnail); } void ImagesTab::scrollAreaResized(const QSize&) @@ -366,7 +406,7 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileAtPos(e->pos())); + select(fileIndexAtPos(e->pos())); } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -377,6 +417,19 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) ui->imagesScrollerVBar->value() + (d > 0 ? -1 : 1)); } +bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) +{ + if (e->key() == Qt::Key_Down) { + moveSelection(+1); + return true; + } else if (e->key() == Qt::Key_Up) { + moveSelection(-1); + return true; + } + + return false; +} + void ImagesTab::onScrolled() { ui->imagesThumbnails->update(); @@ -398,11 +451,9 @@ void ImagesTab::showTooltip(QHelpEvent* e) void ImagesTab::onExplore() { - if (!m_selection) { - return; + if (auto* f=m_files.selectedFile()) { + MOBase::shell::ExploreFile(f->path); } - - MOBase::shell::ExploreFile(m_selection->path); } void ImagesTab::onShowDDS() @@ -452,7 +503,7 @@ void ImagesTab::reload(const ImagesGeometry& geo, File& file) void ImagesTab::updateScrollbar() { - if (fileCount() == 0) { + if (m_files.size() == 0) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); return; @@ -462,11 +513,11 @@ void ImagesTab::updateScrollbar() const auto availableSize = ui->imagesThumbnails->size(); const auto fullyVisible = geo.fullyVisibleCount(); - if (fullyVisible >= fileCount()) { + if (fullyVisible >= m_files.size()) { ui->imagesScrollerVBar->setRange(0, 0); ui->imagesScrollerVBar->setEnabled(false); } else { - const auto d = fileCount() - fullyVisible; + const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setEnabled(true); } @@ -506,6 +557,17 @@ void ImagesThumbnails::resizeEvent(QResizeEvent* e) } } +void ImagesThumbnails::keyPressEvent(QKeyEvent* e) +{ + if (m_tab) { + if (m_tab->thumbnailAreaKeyPressEvent(e)) { + return; + } + } + + QWidget::keyPressEvent(e); +} + bool ImagesThumbnails::event(QEvent* e) { if (e->type() == QEvent::ToolTip) { @@ -711,3 +773,116 @@ void ImagesGeometry::dump() const << " . spacing: " << m_spacing << "\n" << " . top rect: " << m_topRect; } + + +ImagesTab::Files::Files() + : m_selection(BadIndex), m_filtered(false) +{ +} + +void ImagesTab::Files::clear() +{ + m_allFiles.clear(); + m_filteredFiles.clear(); + m_filtered = false; +} + +void ImagesTab::Files::add(File f) +{ + m_allFiles.emplace_back(std::move(f)); +} + +void ImagesTab::Files::addFiltered(File* f) +{ + m_filteredFiles.push_back(f); +} + +std::size_t ImagesTab::Files::size() const +{ + if (m_filtered) { + return m_filteredFiles.size(); + } else { + return m_allFiles.size(); + } +} + +void ImagesTab::Files::switchToAll() +{ + m_filtered = false; + m_filteredFiles.clear(); +} + +void ImagesTab::Files::switchToFiltered() +{ + m_filtered = true; + m_filteredFiles.clear(); +} + +const ImagesTab::File* ImagesTab::Files::get(std::size_t i) const +{ + if (m_filtered) { + if (i < m_filteredFiles.size()) { + return m_filteredFiles[i]; + } + } else { + if (i < m_allFiles.size()) { + return &m_allFiles[i]; + } + } + + return nullptr; +} + +ImagesTab::File* ImagesTab::Files::get(std::size_t i) +{ + return const_cast(std::as_const(*this).get(i)); +} + +std::size_t ImagesTab::Files::indexOf(const File* f) const +{ + if (m_filtered) { + for (std::size_t i=0; i& ImagesTab::Files::allFiles() +{ + return m_allFiles; +} + +bool ImagesTab::Files::isFiltered() const +{ + return m_filtered; +} diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index db541ed8..c7f46975 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -54,6 +54,10 @@ protected: // void resizeEvent(QResizeEvent* e) override; + // forwards to ImagesTab::thumbnailAreaKeyPressEvent() + // + void keyPressEvent(QKeyEvent* e) override; + // forwards to ImagesTab::showTooltip for tooltip events // bool event(QEvent* e) override; @@ -210,6 +214,9 @@ public: void restoreState(const Settings& s) override; private: + static constexpr std::size_t BadIndex = + std::numeric_limits::max(); + struct File { QString path; @@ -227,25 +234,72 @@ private: QColor border, background, selection; }; + struct PaintContext + { + mutable QPainter painter; + ImagesGeometry geo; + File* file; + std::size_t thumbIndex; + std::size_t fileIndex; + + PaintContext(QWidget* w, ImagesGeometry geo) + : painter(w), geo(geo), file(nullptr), thumbIndex(0), fileIndex(0) + { + } + }; + + class Files + { + public: + Files(); + + void clear(); + + void add(File f); + void addFiltered(File* f); + + std::size_t size() const; + + void switchToAll(); + void switchToFiltered(); + + const File* get(std::size_t i) const; + File* get(std::size_t i); + std::size_t indexOf(const File* f) const; + + const File* selectedFile() const; + File* selectedFile(); + std::size_t selectedIndex() const; + void select(std::size_t i); + + std::vector& allFiles(); + + bool isFiltered() const; + + private: + std::vector m_allFiles; + std::vector m_filteredFiles; + std::size_t m_selection; + bool m_filtered; + }; + + ScalableImage* m_image; - std::vector m_files; - std::vector m_filteredFiles; std::vector m_supportedFormats; + Files m_files; int m_margins, m_border, m_padding, m_spacing; - const File* m_selection; FilterWidget m_filter; bool m_ddsAvailable, m_ddsEnabled; Colors m_colors; void getSupportedFormats(); void enableDDS(bool b); - void select(const File* file); - bool needsFiltering() const; void scrollAreaResized(const QSize& s); void paintThumbnailsArea(QPaintEvent* e); void thumbnailAreaMouseEvent(QMouseEvent* e); void thumbnailAreaWheelEvent(QWheelEvent* e); + bool thumbnailAreaKeyPressEvent(QKeyEvent* e); void onScrolled(); void showTooltip(QHelpEvent* e); @@ -253,31 +307,23 @@ private: void onShowDDS(); void onFilterChanged(); - ImagesGeometry makeGeometry() const; - - void paintThumbnail( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBackground( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); - - void paintThumbnailBorder( - QPainter& painter, const ImagesGeometry& geo, - std::size_t i); - - void paintThumbnailImage( - QPainter& painter, const ImagesGeometry& geo, - File& file, std::size_t i); + void select(std::size_t i); + void moveSelection(int direction); + void ensureVisible(std::size_t i); + std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; - std::size_t fileCount() const; - const File* getFile(std::size_t i) const; - File* getFile(std::size_t i); + ImagesGeometry makeGeometry() const; + + void paintThumbnail(const PaintContext& cx); + void paintThumbnailBackground(const PaintContext& cx); + void paintThumbnailBorder(const PaintContext& cx); + void paintThumbnailImage(const PaintContext& cx); - void filterImages(); + void checkFiltering(); + void switchToAll(); + void switchToFiltered(); bool needsReload(const ImagesGeometry& geo, const File& file) const; void reload(const ImagesGeometry& geo, File& file); void updateScrollbar(); -- cgit v1.3.1 From 933a2c7d1086ebc87ee2d214deaa3752ea8c6cf1 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 09:06:41 -0400 Subject: added keyboard navigation for page down/up, end/home changed to draw the full image background only behind the image --- src/modinfodialogimages.cpp | 113 ++++++++++++++++++++++++++++++++------------ src/modinfodialogimages.h | 13 ++++- 2 files changed, 94 insertions(+), 32 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 4179c62a..7738d55f 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -50,7 +50,6 @@ ImagesTab::ImagesTab( ui->imagesThumbnails->setTab(this); ui->imagesScrollerVBar->setTab(this); - ui->imagesScrollerVBar->setSingleStep(1); connect(ui->imagesScrollerVBar, &QScrollBar::valueChanged, [&]{ onScrolled(); }); ui->imagesShowDDS->setEnabled(m_ddsAvailable); @@ -80,6 +79,7 @@ ImagesTab::ImagesTab( void ImagesTab::clear() { + m_files.clear(); ui->imagesScrollerVBar->setValue(0); select(BadIndex); setHasData(false); @@ -221,7 +221,12 @@ void ImagesTab::select(std::size_t i) { m_files.select(i); - if (const auto* f=m_files.selectedFile()) { + if (auto* f=m_files.selectedFile()) { + // when jumping elsewhere in the list, such as with page down/up, the file + // might not be visible yet, which means it hasn't been loaded and would + // pass a null image in setImage() below + f->ensureOriginalLoaded(); + ui->imagesPath->setText(QDir::toNativeSeparators(f->path)); ui->imagesExplore->setEnabled(true); m_image->setImage(f->original); @@ -235,29 +240,36 @@ void ImagesTab::select(std::size_t i) ui->imagesThumbnails->update(); } -void ImagesTab::moveSelection(int direction) +void ImagesTab::moveSelection(int by) { - const auto i = m_files.selectedIndex(); + if (m_files.empty()) { + return; + } + auto i = m_files.selectedIndex(); if (i == BadIndex) { - if (m_files.size() > 0) { - select(0); - } - - return; + i = 0; } - if (direction > 0) { + if (by > 0) { // moving down - if (i < (m_files.size() - 1)) { - select(i + 1); + i += static_cast(by); + + if (i >= m_files.size()) { + i = (m_files.size() - 1); } - } else { + } else if (by < 0) { // moving up - if (i > 0) { - select(i - 1); + const auto abs_by = static_cast(std::abs(by)); + + if (abs_by > i) { + i = 0; + } else { + i -= abs_by; } } + + select(i); } void ImagesTab::ensureVisible(std::size_t i) @@ -406,7 +418,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) return; } - select(fileIndexAtPos(e->pos())); + const auto i = fileIndexAtPos(e->pos()); + if (i != BadIndex) { + select(i); + } } void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) @@ -419,12 +434,39 @@ void ImagesTab::thumbnailAreaWheelEvent(QWheelEvent* e) bool ImagesTab::thumbnailAreaKeyPressEvent(QKeyEvent* e) { - if (e->key() == Qt::Key_Down) { - moveSelection(+1); - return true; - } else if (e->key() == Qt::Key_Up) { - moveSelection(-1); - return true; + switch (e->key()) { + case Qt::Key_Down: { + moveSelection(ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_Up: { + moveSelection(-ui->imagesScrollerVBar->singleStep()); + return true; + } + + case Qt::Key_PageDown: { + moveSelection(ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_PageUp: { + moveSelection(-ui->imagesScrollerVBar->pageStep()); + return true; + } + + case Qt::Key_Home: { + select(0); + return true; + } + + case Qt::Key_End: { + if (!m_files.empty()) { + select(m_files.size() - 1); + } + + return true; + } } return false; @@ -487,13 +529,10 @@ bool ImagesTab::needsReload(const ImagesGeometry& geo, const File& file) const void ImagesTab::reload(const ImagesGeometry& geo, File& file) { file.failed = false; + file.ensureOriginalLoaded(); - if (file.original.isNull()) { - if (!file.original.load(file.path)) { - qCritical() << "failed to load image from " << file.path; - file.failed = true; - return; - } + if (file.failed) { + return; } file.thumbnail = file.original.scaled( @@ -519,6 +558,8 @@ void ImagesTab::updateScrollbar() } else { const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); + ui->imagesScrollerVBar->setSingleStep(1); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); ui->imagesScrollerVBar->setEnabled(true); } } @@ -671,13 +712,13 @@ void ScalableImage::paintEvent(QPaintEvent* e) QPainter painter(this); - // background - painter.fillRect(drawBorderRect, m_backgroundColor); - // border painter.setPen(m_borderColor); painter.drawRect(drawBorderRect); + // background + painter.fillRect(drawImageRect, m_backgroundColor); + // image painter.drawImage(drawImageRect, m_scaled); } @@ -784,6 +825,7 @@ void ImagesTab::Files::clear() { m_allFiles.clear(); m_filteredFiles.clear(); + m_selection = BadIndex; m_filtered = false; } @@ -797,6 +839,15 @@ void ImagesTab::Files::addFiltered(File* f) m_filteredFiles.push_back(f); } +bool ImagesTab::Files::empty() const +{ + if (m_filtered) { + return m_filteredFiles.empty(); + } else { + return m_allFiles.empty(); + } +} + std::size_t ImagesTab::Files::size() const { if (m_filtered) { diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index c7f46975..5ce66d5d 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -227,6 +227,16 @@ private: : path(std::move(path)) { } + + void ensureOriginalLoaded() + { + if (original.isNull()) { + if (!original.load(path)) { + qCritical() << "failed to load image from " << path; + failed = true; + } + } + } }; struct Colors @@ -258,6 +268,7 @@ private: void add(File f); void addFiltered(File* f); + bool empty() const; std::size_t size() const; void switchToAll(); @@ -308,7 +319,7 @@ private: void onFilterChanged(); void select(std::size_t i); - void moveSelection(int direction); + void moveSelection(int by); void ensureVisible(std::size_t i); std::size_t fileIndexAtPos(const QPoint& p) const; -- cgit v1.3.1 From 1c0024b2feda8b5c152d2e211205601ee807e517 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 10:27:02 -0400 Subject: moved all images classes into a namespace with shorter names split theme and metrics into their own class added filename to thumbs --- src/modinfodialog.ui | 8 +- src/modinfodialogimages.cpp | 379 +++++++++++++++++++++++++++++--------------- src/modinfodialogimages.h | 263 +++++++++++++++++------------- 3 files changed, 405 insertions(+), 245 deletions(-) (limited to 'src/modinfodialogimages.cpp') 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/modinfodialogimages.cpp') 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 4b958d9efed3834ceaa57f6c50a1e8288eff01b0 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 11:12:22 -0400 Subject: added visibility flags reduced paging size by one, feels much more natural --- src/modinfodialogimages.cpp | 33 ++++++++++++++++++++++----------- src/modinfodialogimages.h | 11 +++++++++-- 2 files changed, 31 insertions(+), 13 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 3d50876b..02fc7c20 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -116,6 +116,14 @@ void ImagesTab::update() { checkFiltering(); updateScrollbar(); + + // visibility needs to be rechecked here because the scrollbar configuration + // may have changed in updateScrollbar(), in which case any ensureVisible() + // calls in checkFiltering() might have been incorrect + if (m_files.selectedIndex() != BadIndex) { + ensureVisible(m_files.selectedIndex(), Visibility::Partial); + } + ui->imagesThumbnails->update(); setHasData(m_files.size() > 0); @@ -232,7 +240,7 @@ void ImagesTab::getSupportedFormats() } } -void ImagesTab::select(std::size_t i, bool ev) +void ImagesTab::select(std::size_t i, Visibility v) { m_files.select(i); @@ -247,10 +255,7 @@ void ImagesTab::select(std::size_t i, bool ev) ui->imagesSize->setText(dimensionString(f->original().size())); m_image->setImage(f->original()); - - if (ev) { - ensureVisible(i); - } + ensureVisible(i, v); } else { ui->imagesPath->clear(); ui->imagesExplore->setEnabled(false); @@ -293,17 +298,25 @@ void ImagesTab::moveSelection(int by) select(i); } -void ImagesTab::ensureVisible(std::size_t i) +void ImagesTab::ensureVisible(std::size_t i, Visibility v) { + if (v == Visibility::Ignore) { + return; + } + const auto geo = makeGeometry(); const auto fullyVisible = geo.fullyVisibleCount(); + const auto partiallyVisible = fullyVisible + 1; + const auto first = ui->imagesScrollerVBar->value(); + const auto last = (v == Visibility::Full ? + first + fullyVisible : first + partiallyVisible); if (i < first) { // go up ui->imagesScrollerVBar->setValue(static_cast(i)); - } else if (i >= first + fullyVisible) { + } else if (i >= last) { // go down if (i >= fullyVisible) { @@ -456,12 +469,10 @@ void ImagesTab::thumbnailAreaMouseEvent(QMouseEvent* e) const auto i = fileIndexAtPos(e->pos()); if (i != BadIndex) { - // 'false' so the selection is not made visible - // // the only way to click on a thumbnail is if it's already visible, so the // only thing that can happen is a click on a partially visible thumbnail, // which would scroll so it is fully visible, and that's just annoying - select(i, false); + select(i, Visibility::Ignore); } } @@ -574,7 +585,7 @@ void ImagesTab::updateScrollbar() const auto d = m_files.size() - fullyVisible; ui->imagesScrollerVBar->setRange(0, static_cast(d)); ui->imagesScrollerVBar->setSingleStep(1); - ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible)); + ui->imagesScrollerVBar->setPageStep(static_cast(fullyVisible - 1)); ui->imagesScrollerVBar->setEnabled(true); } } diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index 566113bf..d22a6ab2 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -325,6 +325,13 @@ public: void restoreState(const Settings& s) override; private: + enum class Visibility + { + Ignore = 0, + Full, + Partial + }; + using ScalableImage = ImagesTabHelpers::ScalableImage; using Files = ImagesTabHelpers::Files; using File = ImagesTabHelpers::File; @@ -356,9 +363,9 @@ private: void onShowDDS(); void onFilterChanged(); - void select(std::size_t i, bool ensureVisible=true); + void select(std::size_t i, Visibility v=Visibility::Full); void moveSelection(int by); - void ensureVisible(std::size_t i); + void ensureVisible(std::size_t i, Visibility v); std::size_t fileIndexAtPos(const QPoint& p) const; const File* fileAtPos(const QPoint& p) const; -- cgit v1.3.1 From 6547197e5f685439371446b499427848ed687871 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Thu, 27 Jun 2019 12:01:38 -0400 Subject: remember state of various widgets remove obsolete settings --- src/modinfodialog.cpp | 12 ++++++++---- src/modinfodialogesps.cpp | 11 +++++++++++ src/modinfodialogesps.h | 2 ++ src/modinfodialogimages.cpp | 4 ++++ src/modinfodialogtab.h | 23 +++++++++++++++++++++++ src/modinfodialogtextfiles.cpp | 19 +++++++++++++++---- src/modinfodialogtextfiles.h | 3 +++ 7 files changed, 66 insertions(+), 8 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ca20b318..bcb979d2 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -427,10 +427,14 @@ void ModInfoDialog::saveState(Settings& s) const s.directInterface().setValue("mod_info_tab_order", tabState); } - // remove old settings - if (s.directInterface().contains("mod_info_tabs")) { - s.directInterface().remove("mod_info_tabs"); - } + // remove 2.2.0 settings + s.directInterface().remove("mod_info_tabs"); + s.directInterface().remove("mod_info_conflict_expanders"); + s.directInterface().remove("mod_info_conflicts"); + s.directInterface().remove("mod_info_advanced_conflicts"); + s.directInterface().remove("mod_info_conflicts_overwrite"); + s.directInterface().remove("mod_info_conflicts_noconflict"); + s.directInterface().remove("mod_info_conflicts_overwritten"); for (const auto& tabInfo : m_tabs) { tabInfo.tab->saveState(s); diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 7961096d..d00eccf3 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -1,6 +1,7 @@ #include "modinfodialogesps.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include using MOBase::reportError; @@ -275,6 +276,16 @@ void ESPsTab::update() setHasData(m_inactiveModel->rowCount() > 0 || m_activeModel->rowCount() > 0); } +void ESPsTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), ui->ESPsSplitter); +} + +void ESPsTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), ui->ESPsSplitter); +} + void ESPsTab::onActivate() { const auto index = ui->inactiveESPList->currentIndex(); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index c972074a..217863c6 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -18,6 +18,8 @@ public: void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update(); + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; private: ESPListModel* m_inactiveModel; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 02fc7c20..307fa8e8 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -133,12 +133,16 @@ void ImagesTab::saveState(Settings& s) { s.directInterface().setValue( "mod_info_dialog_images_show_dds", m_ddsEnabled); + + saveWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::restoreState(const Settings& s) { ui->imagesShowDDS->setChecked(s.directInterface() .value("mod_info_dialog_images_show_dds", false).toBool()); + + restoreWidgetState(s.directInterface(), ui->tabImagesSplitter); } void ImagesTab::checkFiltering() diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 058035ab..ce29c868 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -67,6 +67,23 @@ protected: void emitModOpen(QString name); void setHasData(bool b); + // this needs to be a template because saveState() and restoreState() are + // not in QWidget, but they're in various widgets + // + template + void saveWidgetState(QSettings& s, Widget* w) + { + s.setValue(settingName(w), w->saveState()); + } + + template + void restoreWidgetState(const QSettings& s, Widget* w) + { + if (s.contains(settingName(w))) { + w->restoreState(s.value(settingName(w)).toByteArray()); + } + } + private: OrganizerCore& m_core; PluginContainer& m_plugin; @@ -76,6 +93,12 @@ private: int m_tabID; bool m_hasData; bool m_firstActivation; + + + QString settingName(QWidget* w) + { + return "geometry/modinfodialog_" + w->objectName(); + } }; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 5b035c53..52891bf5 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -1,6 +1,7 @@ #include "modinfodialogtextfiles.h" #include "ui_modinfodialog.h" #include "modinfodialog.h" +#include "settings.h" #include class FileListModel : public QAbstractItemModel @@ -102,14 +103,14 @@ GenericFilesTab::GenericFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id, QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), - m_list(list), m_editor(e), m_model(new FileListModel) + m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); m_editor->setupToolbar(); - sp->setSizes({200, 1}); - sp->setStretchFactor(0, 0); - sp->setStretchFactor(1, 1); + m_splitter->setSizes({200, 1}); + m_splitter->setStretchFactor(0, 0); + m_splitter->setStretchFactor(1, 1); m_filter.setEdit(filter); m_filter.setList(m_list); @@ -171,6 +172,16 @@ void GenericFilesTab::update() setHasData(m_model->rowCount() > 0); } +void GenericFilesTab::saveState(Settings& s) +{ + saveWidgetState(s.directInterface(), m_splitter); +} + +void GenericFilesTab::restoreState(const Settings& s) +{ + restoreWidgetState(s.directInterface(), m_splitter); +} + void GenericFilesTab::onSelection( const QModelIndex& current, const QModelIndex& previous) { diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index 037c6bb3..ffe49904 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -19,10 +19,13 @@ public: bool canClose() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; void update() override; + void saveState(Settings& s) override; + void restoreState(const Settings& s) override; protected: QListView* m_list; TextEditor* m_editor; + QSplitter* m_splitter; FileListModel* m_model; FilterWidget m_filter; -- cgit v1.3.1 From 82d985064e5105ded4b20d357eaf7cd1b97fe9da Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 12:02:07 -0400 Subject: added a ModInfoDialogTabContext to avoid passing too many things to tab constructors mod is passed to ctors to make sure they can never be empty only call deleteRequest() to selected mod comments --- src/mainwindow.cpp | 4 +- src/modinfodialog.cpp | 40 +++++++++++------- src/modinfodialog.h | 36 ++++------------ src/modinfodialogcategories.cpp | 14 +++--- src/modinfodialogcategories.h | 4 +- src/modinfodialogconflicts.cpp | 12 +++--- src/modinfodialogconflicts.h | 4 +- src/modinfodialogesps.cpp | 8 ++-- src/modinfodialogesps.h | 4 +- src/modinfodialogfiletree.cpp | 14 +++--- src/modinfodialogfiletree.h | 4 +- src/modinfodialogimages.cpp | 9 ++-- src/modinfodialogimages.h | 4 +- src/modinfodialognexus.cpp | 85 +++++++++++++++++++------------------ src/modinfodialognexus.h | 4 +- src/modinfodialogtab.cpp | 33 ++++++++------- src/modinfodialogtab.h | 94 +++++++++++++++++++++++++++++++++++++---- src/modinfodialogtextfiles.cpp | 30 ++++++------- src/modinfodialogtextfiles.h | 14 +++--- 19 files changed, 226 insertions(+), 191 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d48fae4f..7ab555fa 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3223,11 +3223,9 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } else { modInfo->saveMeta(); - ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer); + ModInfoDialog dialog(this, &m_OrganizerCore, &m_PluginContainer, modInfo); connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - dialog.setMod(modInfo); - //Open the tab first if we want to use the standard indexes of the tabs. if (tab != -1) { dialog.setTab(ModInfoDialog::ETabs(tab)); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4af479c4..4ef010e4 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -127,7 +127,8 @@ bool ModInfoDialog::TabInfo::isVisible() const ModInfoDialog::ModInfoDialog( - MainWindow* mw, OrganizerCore* core, PluginContainer* plugin) : + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod) : TutorableDialog("ModInfoDialog", mw), ui(new Ui::ModInfoDialog), m_mainWindow(mw), m_core(core), m_plugin(plugin), m_initialTab(ETabs(-1)), @@ -138,6 +139,7 @@ ModInfoDialog::ModInfoDialog( auto* sc = new QShortcut(QKeySequence::Delete, this); connect(sc, &QShortcut::activated, [&]{ onDeleteShortcut(); }); + setMod(mod); m_tabs = createTabs(); for (int i=0; itabWidget->count(); ++i) { @@ -184,19 +186,26 @@ ModInfoDialog::ModInfoDialog( ModInfoDialog::~ModInfoDialog() = default; +template +std::unique_ptr createTab(ModInfoDialog& d, int index) +{ + return std::make_unique(ModInfoDialogTabContext( + *d.m_core, *d.m_plugin, &d, d.ui.get(), index, d.m_mod, d.getOrigin())); +} + std::vector ModInfoDialog::createTabs() { std::vector v; - v.push_back(createTab(TAB_TEXTFILES)); - v.push_back(createTab(TAB_INIFILES)); - v.push_back(createTab(TAB_IMAGES)); - v.push_back(createTab(TAB_ESPS)); - v.push_back(createTab(TAB_CONFLICTS)); - v.push_back(createTab(TAB_CATEGORIES)); - v.push_back(createTab(TAB_NEXUS)); - v.push_back(createTab(TAB_NOTES)); - v.push_back(createTab(TAB_FILETREE)); + v.push_back(createTab(*this, TAB_TEXTFILES)); + v.push_back(createTab(*this, TAB_INIFILES)); + v.push_back(createTab(*this, TAB_IMAGES)); + v.push_back(createTab(*this, TAB_ESPS)); + v.push_back(createTab(*this, TAB_CONFLICTS)); + v.push_back(createTab(*this, TAB_CATEGORIES)); + v.push_back(createTab(*this, TAB_NEXUS)); + v.push_back(createTab(*this, TAB_NOTES)); + v.push_back(createTab(*this, TAB_FILETREE)); return v; } @@ -218,6 +227,7 @@ int ModInfoDialog::exec() void ModInfoDialog::setMod(ModInfo::Ptr mod) { + Q_ASSERT(mod); m_mod = mod; for (auto& tabInfo : m_tabs) { @@ -584,10 +594,8 @@ void ModInfoDialog::onOriginModified(int originID) void ModInfoDialog::onDeleteShortcut() { - for (auto& tabInfo : m_tabs) { - if (tabInfo.tab->deleteRequested()) { - break; - } + if (auto* tabInfo=currentTab()) { + tabInfo->tab->deleteRequested(); } } @@ -663,7 +671,7 @@ void ModInfoDialog::onTabMoved() void ModInfoDialog::on_nextButton_clicked() { auto mod = m_mainWindow->nextModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } @@ -674,7 +682,7 @@ void ModInfoDialog::on_nextButton_clicked() void ModInfoDialog::on_prevButton_clicked() { auto mod = m_mainWindow->previousModInList(); - if (mod == m_mod) { + if (!mod || mod == m_mod) { return; } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 899a3eab..9eb00a3b 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -66,7 +66,11 @@ protected: **/ class ModInfoDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT; + + template + friend std::unique_ptr createTab( + ModInfoDialog& d, int index); public: enum ETabs { @@ -81,30 +85,12 @@ public: TAB_FILETREE }; - /** - * @brief constructor - * - * @param modInfo info structure about the mod to display - * @param parent parend widget - **/ - ModInfoDialog(MainWindow* mw, OrganizerCore* core, PluginContainer* plugin); + ModInfoDialog( + MainWindow* mw, OrganizerCore* core, PluginContainer* plugin, + ModInfo::Ptr mod); ~ModInfoDialog(); - /** - * @brief retrieve the (user-modified) version of the mod - * - * @return the (user-modified) version of the mod - **/ - QString getModVersion() const; - - /** - * @brief retrieve the (user-modified) mod id - * - * @return the (user-modified) id of the mod - **/ - const int getModID() const; - void setMod(ModInfo::Ptr mod); void setMod(const QString& name); void setTab(ETabs id); @@ -166,12 +152,6 @@ private: void onOriginModified(int originID); void onTabChanged(); void onTabMoved(); - - template - std::unique_ptr createTab(int index) - { - return std::make_unique(*m_core, *m_plugin, this, ui.get(), index); - } }; #endif // MODINFODIALOG_H diff --git a/src/modinfodialogcategories.cpp b/src/modinfodialogcategories.cpp index 8ffded59..c61e248e 100644 --- a/src/modinfodialogcategories.cpp +++ b/src/modinfodialogcategories.cpp @@ -3,10 +3,8 @@ #include "categories.h" #include "modinfo.h" -CategoriesTab::CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id) +CategoriesTab::CategoriesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect( ui->categories, &QTreeWidget::itemChanged, @@ -30,7 +28,7 @@ void CategoriesTab::update() clear(); add( - CategoryFactory::instance(), mod()->getCategories(), + CategoryFactory::instance(), mod().getCategories(), ui->categories->invisibleRootItem(), 0); updatePrimary(); @@ -81,7 +79,7 @@ void CategoriesTab::updatePrimary() { ui->primaryCategories->clear(); - int primaryCategory = mod()->getPrimaryCategory(); + int primaryCategory = mod().getPrimaryCategory(); addChecked(ui->categories->invisibleRootItem()); @@ -111,7 +109,7 @@ void CategoriesTab::save(QTreeWidgetItem* currentNode) for (int i = 0; i < currentNode->childCount(); ++i) { QTreeWidgetItem *childNode = currentNode->child(i); - mod()->setCategory( + mod().setCategory( childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); save(childNode); @@ -134,6 +132,6 @@ void CategoriesTab::onCategoryChanged(QTreeWidgetItem* item, int) void CategoriesTab::onPrimaryChanged(int index) { if (index != -1) { - mod()->setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); + mod().setPrimaryCategory(ui->primaryCategories->itemData(index).toInt()); } } diff --git a/src/modinfodialogcategories.h b/src/modinfodialogcategories.h index 392023e7..c8b52fec 100644 --- a/src/modinfodialogcategories.h +++ b/src/modinfodialogcategories.h @@ -5,9 +5,7 @@ class CategoryFactory; class CategoriesTab : public ModInfoDialogTab { public: - CategoriesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + CategoriesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index a3383a50..511d48ad 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -382,11 +382,9 @@ void for_each_in_selection(QTreeView* tree, F&& f) } -ConflictsTab::ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_general(this, ui, oc), m_advanced(this, ui, oc) +ConflictsTab::ConflictsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(cx), // don't move, cx is used again + m_general(this, cx.ui, cx.core), m_advanced(this, cx.ui, cx.core) { connect( &m_general, &GeneralConflictsTab::modOpen, @@ -872,7 +870,7 @@ bool GeneralConflictsTab::update() int numOverwritten = 0; if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { // careful: these two strings are moved into createXItem() below @@ -1085,7 +1083,7 @@ void AdvancedConflictsTab::update() clear(); if (m_tab->origin() != nullptr) { - const auto rootPath = m_tab->mod()->absolutePath(); + const auto rootPath = m_tab->mod().absolutePath(); const auto& files = m_tab->origin()->getFiles(); m_model->reserve(files.size()); diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index 3fa12231..a77c2ac9 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -103,9 +103,7 @@ class ConflictsTab : public ModInfoDialogTab Q_OBJECT; public: - ConflictsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ConflictsTab(ModInfoDialogTabContext cx); void update() override; void clear() override; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 8dceaa31..fba5d39a 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -224,11 +224,9 @@ private: -ESPsTab::ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) +ESPsTab::ESPsTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), + m_inactiveModel(new ESPListModel), m_activeModel(new ESPListModel) { ui->inactiveESPList->setModel(m_inactiveModel); ui->activeESPList->setModel(m_activeModel); diff --git a/src/modinfodialogesps.h b/src/modinfodialogesps.h index 217863c6..b128f279 100644 --- a/src/modinfodialogesps.h +++ b/src/modinfodialogesps.h @@ -11,9 +11,7 @@ class ESPsTab : public ModInfoDialogTab Q_OBJECT; public: - ESPsTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ESPsTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialogfiletree.cpp b/src/modinfodialogfiletree.cpp index 6690dd2f..35480e2c 100644 --- a/src/modinfodialogfiletree.cpp +++ b/src/modinfodialogfiletree.cpp @@ -13,10 +13,8 @@ namespace shell = MOBase::shell; // checking whether menu items apply to them, just show all of them const int max_scan_for_context_menu = 50; -FileTreeTab::FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : ModInfoDialogTab(oc, plugin, parent, ui, id), m_fs(nullptr) +FileTreeTab::FileTreeTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)), m_fs(nullptr) { m_fs = new QFileSystemModel(this); m_fs->setReadOnly(false); @@ -58,7 +56,7 @@ void FileTreeTab::clear() void FileTreeTab::update() { - const auto rootPath = mod()->absolutePath(); + const auto rootPath = mod().absolutePath(); m_fs->setRootPath(rootPath); ui->filetree->setRootIndex(m_fs->index(rootPath)); @@ -139,7 +137,7 @@ void FileTreeTab::onPreview() return; } - core().previewFile(parentWidget(), mod()->name(), m_fs->filePath(selection)); + core().previewFile(parentWidget(), mod().name(), m_fs->filePath(selection)); } void FileTreeTab::onExplore() @@ -149,7 +147,7 @@ void FileTreeTab::onExplore() if (selection.isValid()) { shell::ExploreFile(m_fs->filePath(selection)); } else { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } } @@ -205,7 +203,7 @@ void FileTreeTab::onUnhide() void FileTreeTab::onOpenInExplorer() { - shell::ExploreFile(mod()->absolutePath()); + shell::ExploreFile(mod().absolutePath()); } bool FileTreeTab::deleteFile(const QModelIndex& index) diff --git a/src/modinfodialogfiletree.h b/src/modinfodialogfiletree.h index 9f5206b9..f9fa62d4 100644 --- a/src/modinfodialogfiletree.h +++ b/src/modinfodialogfiletree.h @@ -6,9 +6,7 @@ class FileTreeTab : public ModInfoDialogTab { public: - FileTreeTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + FileTreeTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index 307fa8e8..cf33f9f5 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -35,12 +35,9 @@ QString dimensionString(const QSize& s) } -ImagesTab::ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), - m_image(new ScalableImage), - m_ddsAvailable(false), m_ddsEnabled(false) +ImagesTab::ImagesTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_image(new ScalableImage), + m_ddsAvailable(false), m_ddsEnabled(false) { getSupportedFormats(); diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h index d22a6ab2..8d9b965b 100644 --- a/src/modinfodialogimages.h +++ b/src/modinfodialogimages.h @@ -314,9 +314,7 @@ class ImagesTab : public ModInfoDialogTab friend class ImagesTabHelpers::ThumbnailsWidget; public: - ImagesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ImagesTab(ModInfoDialogTabContext cx); void clear() override; bool feedFile(const QString& rootPath, const QString& fullPath) override; diff --git a/src/modinfodialognexus.cpp b/src/modinfodialognexus.cpp index 8c8ce55a..81546f58 100644 --- a/src/modinfodialognexus.cpp +++ b/src/modinfodialognexus.cpp @@ -14,11 +14,8 @@ bool isValidModID(int id) return (id > 0); } -NexusTab::NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ModInfoDialogTab(oc, plugin, parent, ui, id), m_requestStarted(false), - m_loading(false) +NexusTab::NexusTab(ModInfoDialogTabContext cx) : + ModInfoDialogTab(std::move(cx)), m_requestStarted(false), m_loading(false) { ui->modID->setValidator(new QIntValidator(ui->modID)); ui->endorse->setVisible(core().settings().endorsementIntegration()); @@ -70,9 +67,9 @@ void NexusTab::update() clear(); - ui->modID->setText(QString("%1").arg(mod()->getNexusID())); + ui->modID->setText(QString("%1").arg(mod().getNexusID())); - QString gameName = mod()->getGameName(); + QString gameName = mod().getGameName(); ui->sourceGame->addItem( core().managedGame()->gameName(), core().managedGame()->gameShortName()); @@ -100,10 +97,10 @@ void NexusTab::update() [&](const QUrl& url){ shell::OpenLink(url); }); ui->endorse->setEnabled( - (mod()->endorsedState() == ModInfo::ENDORSED_FALSE) || - (mod()->endorsedState() == ModInfo::ENDORSED_NEVER)); + (mod().endorsedState() == ModInfo::ENDORSED_FALSE) || + (mod().endorsedState() == ModInfo::ENDORSED_NEVER)); - setHasData(mod()->getNexusID() >= 0); + setHasData(mod().getNexusID() >= 0); } void NexusTab::firstActivation() @@ -128,10 +125,10 @@ bool NexusTab::usesOriginFiles() const void NexusTab::updateVersionColor() { - if (mod()->getVersion() != mod()->getNewestVersion()) { + if (mod().getVersion() != mod().getNewestVersion()) { ui->version->setStyleSheet("color: red"); ui->version->setToolTip(tr("Current Version: %1").arg( - mod()->getNewestVersion().canonicalString())); + mod().getNewestVersion().canonicalString())); } else { ui->version->setStyleSheet("color: green"); ui->version->setToolTip(tr("No update available")); @@ -140,11 +137,11 @@ void NexusTab::updateVersionColor() void NexusTab::updateWebpage() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); ui->visitNexus->setToolTip(nexusLink); refreshData(modID); @@ -152,19 +149,19 @@ void NexusTab::updateWebpage() onModChanged(); } - ui->version->setText(mod()->getVersion().displayString()); - ui->hasCustomURL->setChecked(mod()->hasCustomURL()); - ui->customURL->setText(mod()->getCustomURL()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + ui->version->setText(mod().getVersion().displayString()); + ui->hasCustomURL->setChecked(mod().hasCustomURL()); + ui->customURL->setText(mod().getCustomURL()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); updateTracking(); } void NexusTab::updateTracking() { - if (mod()->trackedState() == ModInfo::TRACKED_TRUE) { + if (mod().trackedState() == ModInfo::TRACKED_TRUE) { ui->track->setChecked(true); ui->track->setText(tr("Tracked")); } else { @@ -185,7 +182,7 @@ void NexusTab::refreshData(int modID) bool NexusTab::tryRefreshData(int modID) { if (isValidModID(modID) && !m_requestStarted) { - if (mod()->updateNXMInfo()) { + if (mod().updateNXMInfo()) { ui->browser->setHtml(""); return true; } @@ -198,7 +195,7 @@ void NexusTab::onModChanged() { m_requestStarted = false; - const QString nexusDescription = mod()->getNexusDescription(); + const QString nexusDescription = mod().getNexusDescription(); QString descriptionAsHTML = R"( @@ -247,12 +244,12 @@ void NexusTab::onModIDChanged() return; } - const int oldID = mod()->getNexusID(); + const int oldID = mod().getNexusID(); const int newID = ui->modID->text().toInt(); if (oldID != newID){ - mod()->setNexusID(newID); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setNexusID(newID); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); ui->browser->page()->setHtml(""); @@ -270,9 +267,9 @@ void NexusTab::onSourceGameChanged() for (auto game : plugin().plugins()) { if (game->gameName() == ui->sourceGame->currentText()) { - mod()->setGameName(game->gameShortName()); - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); - refreshData(mod()->getNexusID()); + mod().setGameName(game->gameShortName()); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + refreshData(mod().getNexusID()); return; } } @@ -285,16 +282,16 @@ void NexusTab::onVersionChanged() } MOBase::VersionInfo version(ui->version->text()); - mod()->setVersion(version); + mod().setVersion(version); updateVersionColor(); } void NexusTab::onRefreshBrowser() { - const auto modID = mod()->getNexusID(); + const auto modID = mod().getNexusID(); if (isValidModID(modID)) { - mod()->setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); + mod().setLastNexusQuery(QDateTime::fromSecsSinceEpoch(0)); updateWebpage(); } else { qInfo("Mod has no valid Nexus ID, info can't be updated."); @@ -303,11 +300,11 @@ void NexusTab::onRefreshBrowser() void NexusTab::onVisitNexus() { - const int modID = mod()->getNexusID(); + const int modID = mod().getNexusID(); if (isValidModID(modID)) { const QString nexusLink = NexusInterface::instance(&plugin()) - ->getModURL(modID, mod()->getGameName()); + ->getModURL(modID, mod().getGameName()); shell::OpenLink(QUrl(nexusLink)); } @@ -315,12 +312,16 @@ void NexusTab::onVisitNexus() void NexusTab::onEndorse() { - core().loggedInAction(parentWidget(), [m=mod()]{ m->endorse(true); }); + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()]{ m->endorse(true); }); } void NexusTab::onTrack() { - core().loggedInAction(parentWidget(), [m=mod()] { + // use modPtr() instead of mod() or this because the callback may be + // executed after the dialog is closed + core().loggedInAction(parentWidget(), [m=modPtr()] { if (m->trackedState() == ModInfo::TRACKED_TRUE) { m->track(false); } else { @@ -335,9 +336,9 @@ void NexusTab::onCustomURLToggled() return; } - mod()->setHasCustomURL(ui->hasCustomURL->isChecked()); - ui->customURL->setEnabled(mod()->hasCustomURL()); - ui->visitCustomURL->setEnabled(mod()->hasCustomURL()); + mod().setHasCustomURL(ui->hasCustomURL->isChecked()); + ui->customURL->setEnabled(mod().hasCustomURL()); + ui->visitCustomURL->setEnabled(mod().hasCustomURL()); } void NexusTab::onCustomURLChanged() @@ -346,13 +347,13 @@ void NexusTab::onCustomURLChanged() return; } - mod()->setCustomURL(ui->customURL->text()); - ui->visitCustomURL->setToolTip(mod()->parseCustomURL().toString()); + mod().setCustomURL(ui->customURL->text()); + ui->visitCustomURL->setToolTip(mod().parseCustomURL().toString()); } void NexusTab::onVisitCustomURL() { - const auto url = mod()->parseCustomURL(); + const auto url = mod().parseCustomURL(); if (url.isValid()) { shell::OpenLink(url); } diff --git a/src/modinfodialognexus.h b/src/modinfodialognexus.h index 930c1ffc..6478375b 100644 --- a/src/modinfodialognexus.h +++ b/src/modinfodialognexus.h @@ -33,9 +33,7 @@ signals: class NexusTab : public ModInfoDialogTab { public: - NexusTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + NexusTab(ModInfoDialogTabContext cx); ~NexusTab(); diff --git a/src/modinfodialogtab.cpp b/src/modinfodialogtab.cpp index 0468b405..554df6df 100644 --- a/src/modinfodialogtab.cpp +++ b/src/modinfodialogtab.cpp @@ -3,11 +3,9 @@ #include "texteditor.h" #include "directoryentry.h" -ModInfoDialogTab::ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) : - ui(ui), m_core(oc), m_plugin(plugin), m_parent(parent), - m_origin(nullptr), m_tabID(id), m_hasData(false), m_firstActivation(true) +ModInfoDialogTab::ModInfoDialogTab(ModInfoDialogTabContext cx) : + ui(cx.ui), m_core(cx.core), m_plugin(cx.plugin), m_parent(cx.parent), + m_origin(cx.origin), m_tabID(cx.id), m_hasData(false), m_firstActivation(true) { } @@ -82,8 +80,15 @@ void ModInfoDialogTab::setMod(ModInfo::Ptr mod, MOShared::FilesOrigin* origin) m_origin = origin; } -ModInfo::Ptr ModInfoDialogTab::mod() const +ModInfo& ModInfoDialogTab::mod() const { + Q_ASSERT(m_mod); + return *m_mod; +} + +ModInfo::Ptr ModInfoDialogTab::modPtr() const +{ + Q_ASSERT(m_mod); return m_mod; } @@ -143,10 +148,8 @@ void ModInfoDialogTab::setFocus() } -NotesTab::NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index) - : ModInfoDialogTab(oc, plugin, parent, ui, index) +NotesTab::NotesTab(ModInfoDialogTabContext cx) + : ModInfoDialogTab(std::move(cx)) { connect(ui->comments, &QLineEdit::editingFinished, [&]{ onComments(); }); connect(ui->notes, &HTMLEditor::editingFinished, [&]{ onNotes(); }); @@ -161,8 +164,8 @@ void NotesTab::clear() void NotesTab::update() { - const auto comments = mod()->comments(); - const auto notes = mod()->notes(); + const auto comments = mod().comments(); + const auto notes = mod().notes(); ui->comments->setText(comments); ui->notes->setText(notes); @@ -176,7 +179,7 @@ bool NotesTab::canHandleSeparators() const void NotesTab::onComments() { - mod()->setComments(ui->comments->text()); + mod().setComments(ui->comments->text()); checkHasData(); } @@ -184,9 +187,9 @@ void NotesTab::onNotes() { // Avoid saving html stub if notes field is empty. if (ui->notes->toPlainText().isEmpty()) { - mod()->setNotes({}); + mod().setNotes({}); } else { - mod()->setNotes(ui->notes->toHtml()); + mod().setNotes(ui->notes->toHtml()); } checkHasData(); diff --git a/src/modinfodialogtab.h b/src/modinfodialogtab.h index 3f98314a..eb574de0 100644 --- a/src/modinfodialogtab.h +++ b/src/modinfodialogtab.h @@ -10,6 +10,33 @@ namespace Ui { class ModInfoDialog; } class Settings; class OrganizerCore; +// helper struct to avoid passing too much stuff to tab constructors +// +struct ModInfoDialogTabContext +{ + OrganizerCore& core; + PluginContainer& plugin; + QWidget* parent; + Ui::ModInfoDialog* ui; + int id; + ModInfo::Ptr mod; + MOShared::FilesOrigin* origin; + + ModInfoDialogTabContext( + OrganizerCore& core, + PluginContainer& plugin, + QWidget* parent, + Ui::ModInfoDialog* ui, + int id, + ModInfo::Ptr mod, + MOShared::FilesOrigin* origin) : + core(core), plugin(plugin), parent(parent), ui(ui), id(id), + mod(mod), origin(origin) + { + } +}; + + // base class for all tabs in the mod info dialog // // when the dialog is opened or when next/previous is clicked, the sequence is: @@ -38,7 +65,7 @@ class OrganizerCore; // tabs can call emitModOpen() to request showing a different mod // // hasDataChanged() should be called when a tab goes from having data to being -// empty or vice versa; this will update the tab text color +// empty or vice versa; this will update the tab text colour // class ModInfoDialogTab : public QObject { @@ -107,20 +134,75 @@ public: // virtual void firstActivation(); + // called when closing the dialog, can return false to stop the dialog from + // closing + // + // this is typically used by tabs that require manual saving, like text files; + // tabs that refuse to close should focus themselves before showing whatever + // confirmation they have // virtual bool canClose(); + + + // called after the dialog is closed, tabs should save whatever UI state they + // want + // virtual void saveState(Settings& s); + + // called before the is shown, tabs should restore whatever UI state they + // saved in saveState() + // virtual void restoreState(const Settings& s); + + // called on the selected tab when the Delete key is pressed on the keyboard; + // tabs _must_ check which widget currently has focus to decide whether this + // should be handled or not; do not blindly delete stuff when this is called + // + // if the delete request was handled, this should return true + // virtual bool deleteRequested(); + + // return true if this tab can handle a separator mod, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // + // if a tab can show meaningful information about a separator (like + // categories or notes), it should return true + // virtual bool canHandleSeparators() const; + + // return true if this tab can handle unmanaged mods, defaults to false; + // when this returns false, the tab is removed from the widget entirely + // virtual bool canHandleUnmanaged() const; + + // return true if this tab uses the files from the mod's origin, defaults to + // false + // + // tabs that do not care about the files inside a mod should return false, + // such as the notes or categories tab + // + // mods that return true will be updated anytime a tab calls + // emitOriginModifed() + // virtual bool usesOriginFiles() const; - ModInfo::Ptr mod() const; + + // returns the currently selected mod + // + ModInfo& mod() const; + + // returns the currently selected mod, can never be empty + // + ModInfo::Ptr modPtr() const; + + // returns the origin of the selected mod; this can be null for mods that + // don't have an origin, like deactivated mods + // MOShared::FilesOrigin* origin() const; + int tabID() const; bool hasData() const; @@ -133,9 +215,7 @@ signals: protected: Ui::ModInfoDialog* ui; - ModInfoDialogTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + ModInfoDialogTab(ModInfoDialogTabContext cx); OrganizerCore& core(); PluginContainer& plugin(); @@ -186,9 +266,7 @@ private: class NotesTab : public ModInfoDialogTab { public: - NotesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int index); + NotesTab(ModInfoDialogTabContext cx); void clear() override; void update() override; diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index bc44ee3e..7a09fa4e 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -99,10 +99,10 @@ private: GenericFilesTab::GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : - ModInfoDialogTab(oc, plugin, parent, ui, id), + ModInfoDialogTabContext cx, + QListView* list, QSplitter* sp, + TextEditor* e, QLineEdit* filter) : + ModInfoDialogTab(std::move(cx)), m_list(list), m_editor(e), m_splitter(sp), m_model(new FileListModel) { m_list->setModel(m_model); @@ -208,13 +208,10 @@ void GenericFilesTab::select(const QModelIndex& index) } -TextFilesTab::TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, - ui->textFileEditor, ui->textFileFilter) +TextFilesTab::TextFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->textFileList, cx.ui->tabTextSplitter, + cx.ui->textFileEditor, cx.ui->textFileFilter) { } @@ -231,13 +228,10 @@ bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) c return false; } -IniFilesTab::IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id) - : GenericFilesTab( - oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, - ui->iniFileEditor, ui->iniFileFilter) +IniFilesTab::IniFilesTab(ModInfoDialogTabContext cx) + : GenericFilesTab(cx, + cx.ui->iniFileList, cx.ui->tabIniSplitter, + cx.ui->iniFileEditor, cx.ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index ffe49904..725ac999 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -30,9 +30,9 @@ protected: FilterWidget m_filter; GenericFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); + ModInfoDialogTabContext cx, + QListView* list, QSplitter* splitter, + TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; @@ -45,9 +45,7 @@ private: class TextFilesTab : public GenericFilesTab { public: - TextFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + TextFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; @@ -57,9 +55,7 @@ protected: class IniFilesTab : public GenericFilesTab { public: - IniFilesTab( - OrganizerCore& oc, PluginContainer& plugin, - QWidget* parent, Ui::ModInfoDialog* ui, int id); + IniFilesTab(ModInfoDialogTabContext cx); protected: bool wantsFile(const QString& rootPath, const QString& fullPath) const override; -- cgit v1.3.1 From 7fe637ce4421e0c6d6ee6b103db5fcc4ef676c25 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 2 Jul 2019 14:57:33 -0400 Subject: display error when loading an image fails --- src/modinfodialogimages.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'src/modinfodialogimages.cpp') diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp index cf33f9f5..69866902 100644 --- a/src/modinfodialogimages.cpp +++ b/src/modinfodialogimages.cpp @@ -888,11 +888,20 @@ File::File(QString path) void File::ensureOriginalLoaded() { - if (m_original.isNull()) { - if (!m_original.load(m_path)) { - qCritical() << "failed to load image from " << m_path; - m_failed = true; - } + if (!m_original.isNull()) { + // already loaded + return; + } + + QImageReader reader(m_path); + + if (!reader.read(&m_original)) { + qCritical().noquote().nospace() + << "failed to load '" << m_path << "'\n" + << reader.errorString() << " " + << "(error " << static_cast(reader.error()) << ")"; + + m_failed = true; } } -- cgit v1.3.1