From 4d90e502266dfb886e1d089fb26780d19a38e5a7 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 May 2019 09:45:30 -0400 Subject: added new filterwidget, will eventually get the operators functionality from the mod/pluging lists --- src/filterwidget.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/filterwidget.cpp (limited to 'src/filterwidget.cpp') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp new file mode 100644 index 00000000..39324bc6 --- /dev/null +++ b/src/filterwidget.cpp @@ -0,0 +1,88 @@ +#include "filterwidget.h" +#include "eventfilter.h" + +FilterWidget::FilterWidget() + : m_edit(nullptr), m_clear(nullptr), m_buddy(nullptr) +{ +} + +void FilterWidget::set(QLineEdit* edit) +{ + if (m_clear) { + delete m_clear; + m_clear = nullptr; + } + + m_edit = edit; + if (!m_edit) { + return; + } + + createClear(); + hookEvents(); +} + +void FilterWidget::clear() +{ + if (!m_edit) { + return; + } + + m_edit->clear(); +} + +void FilterWidget::createClear() +{ + m_clear = new QToolButton(m_edit); + + QPixmap pixmap(":/MO/gui/edit_clear"); + m_clear->setIcon(QIcon(pixmap)); + m_clear->setIconSize(pixmap.size()); + m_clear->setCursor(Qt::ArrowCursor); + m_clear->setStyleSheet("QToolButton { border: none; padding: 0px; }"); + m_clear->hide(); + + QObject::connect(m_clear, &QToolButton::clicked, [&]{ clear(); }); + QObject::connect(m_edit, &QLineEdit::textChanged, [&]{ onTextChanged(); }); + + repositionClearButton(); +} + +void FilterWidget::hookEvents() +{ + auto* f = new EventFilter(m_edit, [&](auto* w, auto* e) { + if (e->type() == QEvent::Resize) { + onResized(); + } + + return false; + }); + + m_edit->installEventFilter(f); +} + +void FilterWidget::onTextChanged() +{ + m_clear->setVisible(!m_edit->text().isEmpty()); +} + +void FilterWidget::onResized() +{ + repositionClearButton(); +} + +void FilterWidget::repositionClearButton() +{ + if (!m_clear) { + return; + } + + const QSize sz = m_clear->sizeHint(); + const int frame = m_edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + const auto r = m_edit->rect(); + + const auto x = r.right() - frame - sz.width(); + const auto y = (r.bottom() + 1 - sz.height()) / 2; + + m_clear->move(x, y); +} -- cgit v1.3.1 From 98b3be3e9bbca73640842f6dadaa159dcec04f7b Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Wed, 29 May 2019 10:08:46 -0400 Subject: removed buddy from FilterWidget, the intention was to hook a ctrl+f shortcut to it, but that won't work as a general solution, it would need to be on tabs for the modinfo dialog added a simple matches() that just searches for the string FilterWidget can cleanly unhook itself --- src/filterwidget.cpp | 40 +++++++++++++++++++++++++++++++++------- src/filterwidget.h | 12 +++++++++--- src/modinfodialog.cpp | 7 +++++++ 3 files changed, 49 insertions(+), 10 deletions(-) (limited to 'src/filterwidget.cpp') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 39324bc6..7c47980e 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -2,24 +2,23 @@ #include "eventfilter.h" FilterWidget::FilterWidget() - : m_edit(nullptr), m_clear(nullptr), m_buddy(nullptr) + : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr) { } void FilterWidget::set(QLineEdit* edit) { - if (m_clear) { - delete m_clear; - m_clear = nullptr; - } + unhook(); m_edit = edit; + if (!m_edit) { return; } createClear(); hookEvents(); + clear(); } void FilterWidget::clear() @@ -31,6 +30,23 @@ void FilterWidget::clear() m_edit->clear(); } +bool FilterWidget::matches(const QString& s) const +{ + return s.contains(m_text); +} + +void FilterWidget::unhook() +{ + if (m_clear) { + delete m_clear; + m_clear = nullptr; + } + + if (m_edit) { + m_edit->removeEventFilter(m_eventFilter); + } +} + void FilterWidget::createClear() { m_clear = new QToolButton(m_edit); @@ -50,7 +66,7 @@ void FilterWidget::createClear() void FilterWidget::hookEvents() { - auto* f = new EventFilter(m_edit, [&](auto* w, auto* e) { + m_eventFilter = new EventFilter(m_edit, [&](auto* w, auto* e) { if (e->type() == QEvent::Resize) { onResized(); } @@ -58,12 +74,22 @@ void FilterWidget::hookEvents() return false; }); - m_edit->installEventFilter(f); + m_edit->installEventFilter(m_eventFilter); } void FilterWidget::onTextChanged() { m_clear->setVisible(!m_edit->text().isEmpty()); + + const auto text = m_edit->text(); + + if (text != m_text) { + m_text = text; + + if (changed) { + changed(); + } + } } void FilterWidget::onResized() diff --git a/src/filterwidget.h b/src/filterwidget.h index 07e216f1..ca731dc1 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -1,21 +1,27 @@ #ifndef FILTERWIDGET_H #define FILTERWIDGET_H +class EventFilter; + class FilterWidget { public: + std::function changed; + FilterWidget(); void set(QLineEdit* edit); - void buddy(QWidget* w); - void clear(); + bool matches(const QString& s) const; + private: QLineEdit* m_edit; + EventFilter* m_eventFilter; QToolButton* m_clear; - QWidget* m_buddy; + QString m_text; + void unhook(); void createClear(); void hookEvents(); void repositionClearButton(); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1e933a74..0f18033a 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -454,6 +454,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo m_advancedConflictFilter.set(ui->conflictsAdvancedFilter); + m_advancedConflictFilter.changed = [&]{ refreshConflictLists(false, true); }; // left-elide the overwrites column so that the nearest are visible ui->conflictsAdvancedList->setItemDelegateForColumn( @@ -873,6 +874,12 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } + if (!m_advancedConflictFilter.matches(before) && + !m_advancedConflictFilter.matches(relativeName) && + !m_advancedConflictFilter.matches(after)) { + return nullptr; + } + QTreeWidgetItem* item = new QTreeWidgetItem; item->setText(0, before); item->setText(1, relativeName); -- cgit v1.3.1 From 8efab8bf129b6f9928f478a429e20cf018e5e373 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Fri, 31 May 2019 10:54:36 -0400 Subject: FilterWidget takes a predicate to match strings copied the logic from modlistsortproxy so the basic boolean syntax in filters works --- src/filterwidget.cpp | 34 ++++++++++++++++++++++++++++++++-- src/filterwidget.h | 2 +- src/modinfodialog.cpp | 13 +++++++++---- 3 files changed, 42 insertions(+), 7 deletions(-) (limited to 'src/filterwidget.cpp') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 7c47980e..16a46b0e 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -30,9 +30,39 @@ void FilterWidget::clear() m_edit->clear(); } -bool FilterWidget::matches(const QString& s) const +bool FilterWidget::matches(std::function pred) const { - return s.contains(m_text); + const QStringList ORList = [&] { + QString filterCopy = QString(m_text); + filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); + return filterCopy.split(";", QString::SkipEmptyParts); + }(); + + if (ORList.isEmpty() || !pred) { + return true; + } + + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts); + bool segmentGood = true; + + // check each word in the segment for match, each word needs to be matched + // but it doesn't matter where. + for (auto& currentKeyword : ANDKeywords) { + if (!pred(currentKeyword)) { + segmentGood = false; + } + } + + if (segmentGood) { + // the last AND loop didn't break so the ORSegments is true so mod + // matches filter + return true; + } + } + + return false; } void FilterWidget::unhook() diff --git a/src/filterwidget.h b/src/filterwidget.h index ca731dc1..4fee5983 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -13,7 +13,7 @@ public: void set(QLineEdit* edit); void clear(); - bool matches(const QString& s) const; + bool matches(std::function pred) const; private: QLineEdit* m_edit; diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 0f18033a..4852150f 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -874,10 +874,15 @@ QTreeWidgetItem* ModInfoDialog::createAdvancedConflictItem( } } - if (!m_advancedConflictFilter.matches(before) && - !m_advancedConflictFilter.matches(relativeName) && - !m_advancedConflictFilter.matches(after)) { - return nullptr; + bool matched = m_advancedConflictFilter.matches([&](auto&& what) { + return + before.contains(what, Qt::CaseInsensitive) || + relativeName.contains(what, Qt::CaseInsensitive) || + after.contains(what, Qt::CaseInsensitive); + }); + + if (!matched) { + return nullptr; } QTreeWidgetItem* item = new QTreeWidgetItem; -- cgit v1.3.1 From 139c33ccc4f529083b0288907caad2946a3f5a8f Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 11:41:00 -0400 Subject: various optimizations and caching fixed conflict list not sorting when changing parameters switched from QDirIterator to std::filesystem, much faster FilterWidget precompiles the list --- src/filterwidget.cpp | 19 +++-- src/filterwidget.h | 3 + src/modinfodialog.cpp | 12 ++- src/modinfodialogconflicts.cpp | 186 ++++++++++++++++++++++++----------------- src/modinfodialogconflicts.h | 8 +- src/modinfodialogesps.cpp | 26 +++--- src/modinfodialogtextfiles.cpp | 15 ++-- 7 files changed, 155 insertions(+), 114 deletions(-) (limited to 'src/filterwidget.cpp') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 16a46b0e..82644658 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -30,21 +30,29 @@ void FilterWidget::clear() m_edit->clear(); } -bool FilterWidget::matches(std::function pred) const +void FilterWidget::compile() { + m_compiled.clear(); + const QStringList ORList = [&] { QString filterCopy = QString(m_text); filterCopy.replace("||", ";").replace("OR", ";").replace("|", ";"); return filterCopy.split(";", QString::SkipEmptyParts); }(); - if (ORList.isEmpty() || !pred) { + // split in ORSegments that internally use AND logic + for (auto& ORSegment : ORList) { + m_compiled.push_back(ORSegment.split(" ", QString::SkipEmptyParts)); + } +} + +bool FilterWidget::matches(std::function pred) const +{ + if (m_compiled.isEmpty() || !pred) { return true; } - // split in ORSegments that internally use AND logic - for (auto& ORSegment : ORList) { - QStringList ANDKeywords = ORSegment.split(" ", QString::SkipEmptyParts); + for (auto& ANDKeywords : m_compiled) { bool segmentGood = true; // check each word in the segment for match, each word needs to be matched @@ -115,6 +123,7 @@ void FilterWidget::onTextChanged() if (text != m_text) { m_text = text; + compile(); if (changed) { changed(); diff --git a/src/filterwidget.h b/src/filterwidget.h index 4fee5983..762d9b15 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -20,6 +20,7 @@ private: EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; + QList> m_compiled; void unhook(); void createClear(); @@ -28,6 +29,8 @@ private: void onTextChanged(); void onResized(); + + void compile(); }; #endif // FILTERWIDGET_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index a407e9b7..c6cdb961 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include "modinfodialogcategories.h" #include "modinfodialognexus.h" #include "modinfodialogfiletree.h" +#include using namespace MOBase; using namespace MOShared; @@ -310,12 +311,17 @@ void ModInfoDialog::updateTabs(bool becauseOriginChanged) void ModInfoDialog::feedFiles(bool becauseOriginChanged) { + namespace fs = std::filesystem; const auto rootPath = m_mod->absolutePath(); if (rootPath.length() > 0) { - QDirIterator dirIterator(rootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); + + for (const auto& entry : fs::recursive_directory_iterator(rootPath.toStdString())) { + if (!entry.is_regular_file()) { + continue; + } + + const auto fileName = QString::fromWCharArray(entry.path().c_str()); for (auto& tabInfo : m_tabs) { if (!tabInfo.isVisible()) { diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index 03589030..52d25954 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -126,8 +126,9 @@ public: const QString& (ConflictItem::*getText)() const; }; - ConflictListModel(QTreeView* tree, std::vector columns) - : m_tree(tree), m_columns(std::move(columns)) + ConflictListModel(QTreeView* tree, std::vector columns) : + m_tree(tree), m_columns(std::move(columns)), + m_sortColumn(-1), m_sortOrder(Qt::AscendingOrder) { m_tree->setModel(this); } @@ -226,32 +227,10 @@ public: void sort(int colIndex, Qt::SortOrder order=Qt::AscendingOrder) { - if (colIndex < 0) { - return; - } - - const auto c = static_cast(colIndex); - if (c >= m_columns.size()) { - return; - } - - const auto& col = m_columns[c]; - - // avoids branching on sort order while sorting - auto sortAsc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); - }; - - auto sortDesc = [&](const auto& a, const auto& b) { - return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); - }; - - if (order == Qt::AscendingOrder) { - std::sort(m_items.begin(), m_items.end(), sortAsc); - } else { - std::sort(m_items.begin(), m_items.end(), sortDesc); - } + m_sortColumn = colIndex; + m_sortOrder = order; + doSort(); emit layoutChanged({}, QAbstractItemModel::VerticalSortHint); } @@ -263,6 +242,7 @@ public: void finished() { endResetModel(); + doSort(); } const ConflictItem* getItem(std::size_t row) const @@ -278,6 +258,37 @@ private: QTreeView* m_tree; std::vector m_columns; std::vector m_items; + int m_sortColumn; + Qt::SortOrder m_sortOrder; + + void doSort() + { + if (m_sortColumn < 0) { + return; + } + + const auto c = static_cast(m_sortColumn); + if (c >= m_columns.size()) { + return; + } + + const auto& col = m_columns[c]; + + // avoids branching on sort order while sorting + auto sortAsc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) < 0); + }; + + auto sortDesc = [&](const auto& a, const auto& b) { + return (naturalCompare((a.*col.getText)(), (b.*col.getText)()) > 0); + }; + + if (m_sortOrder == Qt::AscendingOrder) { + std::sort(m_items.begin(), m_items.end(), sortAsc); + } else { + std::sort(m_items.begin(), m_items.end(), sortDesc); + } + } }; @@ -841,8 +852,9 @@ bool GeneralConflictsTab::update() const auto rootPath = m_tab->mod()->absolutePath(); for (const auto& file : m_tab->origin()->getFiles()) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createXItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -851,28 +863,31 @@ bool GeneralConflictsTab::update() if (fileOrigin == m_tab->origin()->getID()) { if (!alternatives.empty()) { m_overwriteModel->add(createOverwriteItem( - file->getIndex(), archive, fileName, relativeName, alternatives)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName), alternatives)); ++numOverwrite; } else { // otherwise, put the file in the noconflict tree m_noConflictModel->add(createNoConflictItem( - file->getIndex(), archive, fileName, relativeName)); + file->getIndex(), archive, + std::move(fileName), std::move(relativeName))); ++numNonConflicting; } } else { m_overwrittenModel->add(createOverwrittenItem( - file->getIndex(), fileOrigin, archive, fileName, relativeName)); + file->getIndex(), fileOrigin, archive, + std::move(fileName), std::move(relativeName))); ++numOverwritten; } } - } - m_overwriteModel->finished(); - m_overwrittenModel->finished(); - m_noConflictModel->finished(); + m_overwriteModel->finished(); + m_overwrittenModel->finished(); + m_noConflictModel->finished(); + } ui->overwriteCount->display(numOverwrite); ui->overwrittenCount->display(numOverwritten); @@ -882,43 +897,48 @@ bool GeneralConflictsTab::update() } ConflictItem GeneralConflictsTab::createOverwriteItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + FileEntry::Index index, bool archive, QString fileName, QString relativeName, const FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString altString; + std::wstring altString; for (const auto& alt : alternatives) { - if (!altString.isEmpty()) { - altString += ", "; + if (!altString.empty()) { + altString += L", "; } - altString += ToQString(ds.getOriginByID(alt.first).getName()); + altString += ds.getOriginByID(alt.first).getName(); } - const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); + auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName()); - return {altString, relativeName, "", index, fileName, true, origin, archive}; + return ConflictItem( + ToQString(altString), std::move(relativeName), QString::null, index, + std::move(fileName), true, std::move(origin), archive); } ConflictItem GeneralConflictsTab::createNoConflictItem( - FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName) + FileEntry::Index index, bool archive, QString fileName, QString relativeName) { - return {"", relativeName, "", index, fileName, false, "", archive}; + return ConflictItem( + QString::null, std::move(relativeName), QString::null, index, + std::move(fileName), false, QString::null, archive); } ConflictItem GeneralConflictsTab::createOverwrittenItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName) + QString fileName, QString relativeName) { const auto& ds = *m_core.directoryStructure(); - const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); - return { - "", relativeName, ToQString(realOrigin.getName()), - index, fileName, true, ToQString(realOrigin.getName()), archive}; + QString after = ToQString(realOrigin.getName()); + QString altOrigin = after; + + return ConflictItem( + QString::null, std::move(relativeName), std::move(after), + index, std::move(fileName), true, std::move(altOrigin), archive); } void GeneralConflictsTab::onOverwriteActivated(const QModelIndex& index) @@ -1048,8 +1068,9 @@ void AdvancedConflictsTab::update() m_model->reserve(files.size()); for (const auto& file : files) { - const QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); - const QString fileName = relativeName.mid(0).prepend(rootPath); + // careful: these two strings are moved into createItem() below + QString relativeName = QDir::fromNativeSeparators(ToQString(file->getRelativePath())); + QString fileName = rootPath + relativeName; bool archive = false; const int fileOrigin = file->getOrigin(archive); @@ -1057,7 +1078,7 @@ void AdvancedConflictsTab::update() auto item = createItem( file->getIndex(), fileOrigin, archive, - fileName, relativeName, alternatives); + std::move(fileName), std::move(relativeName), alternatives); if (item) { m_model->add(std::move(*item)); @@ -1070,39 +1091,41 @@ void AdvancedConflictsTab::update() std::optional AdvancedConflictsTab::createItem( FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives) { const auto& ds = *m_core.directoryStructure(); - QString before, after; + std::wstring before, after; if (!alternatives.empty()) { + const bool showAllAlts = ui->conflictsAdvancedShowAll->isChecked(); + int beforePrio = 0; int afterPrio = std::numeric_limits::max(); for (const auto& alt : alternatives) { - const auto altOrigin = ds.getOriginByID(alt.first); + const auto& altOrigin = ds.getOriginByID(alt.first); - if (ui->conflictsAdvancedShowAll->isChecked()) { + if (showAllAlts) { // fills 'before' and 'after' with all the alternatives that come // before and after this mod in terms of priority if (altOrigin.getPriority() < m_tab->origin()->getPriority()) { // add all the mods having a lower priority than this one - if (!before.isEmpty()) { - before += ", "; + if (!before.empty()) { + before += L", "; } - before += ToQString(altOrigin.getName()); + before += altOrigin.getName(); } else if (altOrigin.getPriority() > m_tab->origin()->getPriority()) { // add all the mods having a higher priority than this one - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(altOrigin.getName()); + after += altOrigin.getName(); } } else { // keep track of the nearest mods that come before and after this one @@ -1114,7 +1137,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() > beforePrio) { // the alternative has a higher priority and therefore is closer // to this mod, use it - before = ToQString(altOrigin.getName()); + before = altOrigin.getName(); beforePrio = altOrigin.getPriority(); } } @@ -1125,7 +1148,7 @@ std::optional AdvancedConflictsTab::createItem( if (altOrigin.getPriority() < afterPrio) { // the alternative has a lower priority and there is closer // to this mod, use it - after = ToQString(altOrigin.getName()); + after = altOrigin.getName(); afterPrio = altOrigin.getPriority(); } } @@ -1140,41 +1163,46 @@ std::optional AdvancedConflictsTab::createItem( // nearest mods, it's not worth checking for the primary origin because it // will always have a higher priority than the alternatives (or it wouldn't // be the primary) - if (after.isEmpty() || ui->conflictsAdvancedShowAll->isChecked()) { - FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin); + if (after.empty() || showAllAlts) { + const FilesOrigin& realOrigin = ds.getOriginByID(fileOrigin); // if no mods overwrite this file, the primary origin is the same as this // mod, so ignore that if (realOrigin.getID() != m_tab->origin()->getID()) { - if (!after.isEmpty()) { - after += ", "; + if (!after.empty()) { + after += L", "; } - after += ToQString(realOrigin.getName()); + after += realOrigin.getName(); } } } - bool hasAlts = !before.isEmpty() || !after.isEmpty(); + const bool hasAlts = !before.empty() || !after.empty(); - if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { + if (!hasAlts) { // if both before and after are empty, it means this file has no conflicts // at all, only display it if the user wants it - if (!hasAlts) { + if (!ui->conflictsAdvancedShowNoConflict->isChecked()) { return {}; } } - bool matched = m_filter.matches([&](auto&& what) { + auto beforeQS = QString::fromStdWString(before); + auto afterQS = QString::fromStdWString(after); + + const bool matched = m_filter.matches([&](auto&& what) { return - before.contains(what, Qt::CaseInsensitive) || + beforeQS.contains(what, Qt::CaseInsensitive) || relativeName.contains(what, Qt::CaseInsensitive) || - after.contains(what, Qt::CaseInsensitive); + afterQS.contains(what, Qt::CaseInsensitive); }); if (!matched) { return {}; } - return ConflictItem{before, relativeName, after, index, fileName, hasAlts, "", archive}; + return ConflictItem( + std::move(beforeQS), std::move(relativeName), std::move(afterQS), + index, std::move(fileName), hasAlts, QString::null, archive); } diff --git a/src/modinfodialogconflicts.h b/src/modinfodialogconflicts.h index f0c0c589..fc7dd32a 100644 --- a/src/modinfodialogconflicts.h +++ b/src/modinfodialogconflicts.h @@ -47,16 +47,16 @@ private: ConflictItem createOverwriteItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); ConflictItem createNoConflictItem( MOShared::FileEntry::Index index, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); ConflictItem createOverwrittenItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName); + QString fileName, QString relativeName); void onOverwriteActivated(const QModelIndex& index); void onOverwrittenActivated(const QModelIndex& index); @@ -93,7 +93,7 @@ private: std::optional createItem( MOShared::FileEntry::Index index, int fileOrigin, bool archive, - const QString& fileName, const QString& relativeName, + QString fileName, QString relativeName, const MOShared::FileEntry::AlternativesVector& alternatives); }; diff --git a/src/modinfodialogesps.cpp b/src/modinfodialogesps.cpp index 3f742bae..ecb341e8 100644 --- a/src/modinfodialogesps.cpp +++ b/src/modinfodialogesps.cpp @@ -18,7 +18,7 @@ public: m_active = true; } - updateFilename(); + pathChanged(); } const QString& rootPath() const @@ -50,9 +50,9 @@ public: return m_inactivePath; } - QFileInfo fileInfo() const + const QFileInfo& fileInfo() const { - return m_rootPath + QDir::separator() + relativePath(); + return m_fileInfo; } bool isActive() const @@ -73,7 +73,7 @@ public: m_inactivePath = QFileInfo(m_inactivePath).path() + QDir::separator() + newName; } - updateFilename(); + pathChanged(); return true; } @@ -88,7 +88,7 @@ public: if (root.rename(m_activePath, newName)) { m_active = false; m_inactivePath = newName; - updateFilename(); + pathChanged(); return true; } @@ -100,11 +100,13 @@ private: QString m_activePath; QString m_inactivePath; QString m_filename; + QFileInfo m_fileInfo; bool m_active; - void updateFilename() + void pathChanged() { - m_filename = fileInfo().fileName(); + m_fileInfo.setFile(m_rootPath + QDir::separator() + relativePath()); + m_filename = m_fileInfo.fileName(); } }; @@ -246,15 +248,11 @@ void ESPsTab::clear() bool ESPsTab::feedFile(const QString& rootPath, const QString& fullPath) { - static constexpr const char* extensions[] = { - ".esp", ".esm", ".esl" - }; + static const QString extensions[] = {".esp", ".esm", ".esl"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - QString relativePath = fullPath.mid(rootPath.length() + 1); - - ESPItem esp(rootPath, relativePath); + ESPItem esp(rootPath, fullPath.mid(rootPath.length() + 1)); if (esp.isActive()) { m_activeModel->add(std::move(esp)); diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 25d799b1..675a4c79 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -202,11 +202,9 @@ TextFilesTab::TextFilesTab( bool TextFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".txt" - }; + static const QString extensions[] = {".txt"}; - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { return true; } @@ -226,13 +224,12 @@ IniFilesTab::IniFilesTab( bool IniFilesTab::wantsFile(const QString& rootPath, const QString& fullPath) const { - static constexpr const char* extensions[] = { - ".ini", ".cfg" - }; + static const QString extensions[] = {".ini", ".cfg"}; + static const QString meta("meta.ini"); - for (const auto* e : extensions) { + for (const auto& e : extensions) { if (fullPath.endsWith(e, Qt::CaseInsensitive)) { - if (!fullPath.endsWith("meta.ini")) { + if (!fullPath.endsWith(meta)) { return true; } } -- cgit v1.3.1 From 19a19ead4059c600680d6e7ac193490e32e03245 Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 16:41:32 -0400 Subject: filterwidget now support automatically installing a proxy on lists filters on txt and ini tabs --- src/filterwidget.cpp | 70 +++++++++++++++++++++++++++++++++++++++--- src/filterwidget.h | 41 ++++++++++++++++++++++--- src/modinfodialog.ui | 8 ++++- src/modinfodialogconflicts.cpp | 4 +-- src/modinfodialogtextfiles.cpp | 13 +++++--- src/modinfodialogtextfiles.h | 4 ++- 6 files changed, 123 insertions(+), 17 deletions(-) (limited to 'src/filterwidget.cpp') diff --git a/src/filterwidget.cpp b/src/filterwidget.cpp index 82644658..1b9efd7c 100644 --- a/src/filterwidget.cpp +++ b/src/filterwidget.cpp @@ -1,12 +1,41 @@ #include "filterwidget.h" #include "eventfilter.h" -FilterWidget::FilterWidget() - : m_edit(nullptr), m_eventFilter(nullptr), m_clear(nullptr) +FilterWidgetProxyModel::FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent) + : QSortFilterProxyModel(parent), m_filter(fw) { + connect(&fw, &FilterWidget::changed, [&]{ invalidateFilter(); }); } -void FilterWidget::set(QLineEdit* edit) +bool FilterWidgetProxyModel::filterAcceptsRow( + int sourceRow, const QModelIndex& sourceParent) const +{ + const auto cols = sourceModel()->columnCount(); + + const auto m = m_filter.matches([&](auto&& what) { + for (int c=0; cindex(sourceRow, c, sourceParent); + const auto text = sourceModel()->data(index, Qt::DisplayRole).toString(); + + if (text.contains(what, Qt::CaseInsensitive)) { + return true; + } + } + + return false; + }); + + return m; +} + + +FilterWidget::FilterWidget() : + m_edit(nullptr), m_list(nullptr), m_proxy(nullptr), + m_eventFilter(nullptr), m_clear(nullptr) +{ +} + +void FilterWidget::setEdit(QLineEdit* edit) { unhook(); @@ -16,11 +45,22 @@ void FilterWidget::set(QLineEdit* edit) return; } + m_edit->setPlaceholderText(QObject::tr("Filter")); + createClear(); hookEvents(); clear(); } +void FilterWidget::setList(QAbstractItemView* list) +{ + m_list = list; + + m_proxy = new FilterWidgetProxyModel(*this); + m_proxy->setSourceModel(m_list->model()); + m_list->setModel(m_proxy); +} + void FilterWidget::clear() { if (!m_edit) { @@ -30,6 +70,16 @@ void FilterWidget::clear() m_edit->clear(); } +QModelIndex FilterWidget::map(const QModelIndex& index) +{ + if (m_proxy) { + return m_proxy->mapToSource(index); + } else { + qCritical() << "FilterWidget::map() called, but proxy isn't set up"; + return index; + } +} + void FilterWidget::compile() { m_compiled.clear(); @@ -83,6 +133,14 @@ void FilterWidget::unhook() if (m_edit) { m_edit->removeEventFilter(m_eventFilter); } + + if (m_proxy && m_list) { + auto* model = m_proxy->sourceModel(); + m_proxy->setSourceModel(nullptr); + delete m_proxy; + + m_list->setModel(model); + } } void FilterWidget::createClear() @@ -125,9 +183,11 @@ void FilterWidget::onTextChanged() m_text = text; compile(); - if (changed) { - changed(); + if (m_proxy) { + m_proxy->invalidateFilter(); } + + emit changed(); } } diff --git a/src/filterwidget.h b/src/filterwidget.h index 762d9b15..5b08c3ae 100644 --- a/src/filterwidget.h +++ b/src/filterwidget.h @@ -1,22 +1,55 @@ #ifndef FILTERWIDGET_H #define FILTERWIDGET_H +#include +#include +#include +#include +#include + class EventFilter; +class FilterWidget; -class FilterWidget +class FilterWidgetProxyModel : public QSortFilterProxyModel { + Q_OBJECT; + public: - std::function changed; + FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent=nullptr); + using QSortFilterProxyModel::invalidateFilter; + +protected: + bool filterAcceptsRow(int row, const QModelIndex& parent) const override; + +private: + FilterWidget& m_filter; +}; + + +class FilterWidget : public QObject +{ + Q_OBJECT; + +public: + using predFun = std::function; FilterWidget(); - void set(QLineEdit* edit); + void setEdit(QLineEdit* edit); + void setList(QAbstractItemView* list); void clear(); - bool matches(std::function pred) const; + QModelIndex map(const QModelIndex& index); + + bool matches(predFun pred) const; + +signals: + void changed(); private: QLineEdit* m_edit; + QAbstractItemView* m_list; + FilterWidgetProxyModel* m_proxy; EventFilter* m_eventFilter; QToolButton* m_clear; QString m_text; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 9f4ae873..69e73da4 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -72,6 +72,9 @@ + + + @@ -108,7 +111,7 @@ Qt::Horizontal - + 6 @@ -132,6 +135,9 @@ + + + diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp index c053e64c..e631db30 100644 --- a/src/modinfodialogconflicts.cpp +++ b/src/modinfodialogconflicts.cpp @@ -999,8 +999,8 @@ AdvancedConflictsTab::AdvancedConflictsTab( ui->conflictsAdvancedList, &QTreeView::customContextMenuRequested, [&](const QPoint& p){ m_tab->showContextMenu(p, ui->conflictsAdvancedList); }); - m_filter.set(ui->conflictsAdvancedFilter); - m_filter.changed = [&]{ update(); }; + m_filter.setEdit(ui->conflictsAdvancedFilter); + QObject::connect(&m_filter, &FilterWidget::changed, [&]{ update(); }); } void AdvancedConflictsTab::clear() diff --git a/src/modinfodialogtextfiles.cpp b/src/modinfodialogtextfiles.cpp index 675a4c79..2b5ab489 100644 --- a/src/modinfodialogtextfiles.cpp +++ b/src/modinfodialogtextfiles.cpp @@ -100,7 +100,7 @@ private: GenericFilesTab::GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* sp, TextEditor* e) : + QListView* list, QSplitter* sp, TextEditor* e, QLineEdit* filter) : ModInfoDialogTab(oc, plugin, parent, ui, id), m_list(list), m_editor(e), m_model(new FileListModel) { @@ -111,6 +111,9 @@ GenericFilesTab::GenericFilesTab( sp->setStretchFactor(0, 0); sp->setStretchFactor(1, 1); + m_filter.setEdit(filter); + m_filter.setList(m_list); + QObject::connect( m_list->selectionModel(), &QItemSelectionModel::currentRowChanged, [&](auto current, auto previous){ onSelection(current, previous); }); @@ -187,7 +190,7 @@ void GenericFilesTab::select(const QModelIndex& index) } m_editor->setEnabled(true); - m_editor->load(m_model->fullPath(index)); + m_editor->load(m_model->fullPath(m_filter.map(index))); } @@ -196,7 +199,8 @@ TextFilesTab::TextFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->textFileList, ui->tabTextSplitter, ui->textFileEditor) + ui->textFileList, ui->tabTextSplitter, + ui->textFileEditor, ui->textFileFilter) { } @@ -218,7 +222,8 @@ IniFilesTab::IniFilesTab( QWidget* parent, Ui::ModInfoDialog* ui, int id) : GenericFilesTab( oc, plugin, parent, ui, id, - ui->iniFileList, ui->tabIniSplitter, ui->iniFileEditor) + ui->iniFileList, ui->tabIniSplitter, + ui->iniFileEditor, ui->iniFileFilter) { } diff --git a/src/modinfodialogtextfiles.h b/src/modinfodialogtextfiles.h index d879c2bd..037c6bb3 100644 --- a/src/modinfodialogtextfiles.h +++ b/src/modinfodialogtextfiles.h @@ -2,6 +2,7 @@ #define MODINFODIALOGTEXTFILES_H #include "modinfodialogtab.h" +#include "filterwidget.h" #include #include @@ -23,11 +24,12 @@ protected: QListView* m_list; TextEditor* m_editor; FileListModel* m_model; + FilterWidget m_filter; GenericFilesTab( OrganizerCore& oc, PluginContainer& plugin, QWidget* parent, Ui::ModInfoDialog* ui, int id, - QListView* list, QSplitter* splitter, TextEditor* editor); + QListView* list, QSplitter* splitter, TextEditor* editor, QLineEdit* filter); virtual bool wantsFile(const QString& rootPath, const QString& fullPath) const = 0; -- cgit v1.3.1 From 65b3eb24fc5e3e1033d583a24cb51a19b0cfac2c Mon Sep 17 00:00:00 2001 From: isanae <14251494+isanae@users.noreply.github.com> Date: Tue, 25 Jun 2019 17:20:19 -0400 Subject: filterable images --- src/filterwidget.cpp | 5 ++ src/filterwidget.h | 1 + src/modinfodialog.ui | 3 ++ src/modinfodialogimages.cpp | 111 +++++++++++++++++++++++++++++++++++--------- src/modinfodialogimages.h | 9 ++++ 5 files changed, 108 insertions(+), 21 deletions(-) (limited to 'src/filterwidget.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