From 1ac3f4d8e8ee7461f047d34e196df40499e89557 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Wed, 6 Jan 2021 19:43:21 +0100 Subject: Display children contents in collapsed separator. --- src/modlistview.cpp | 75 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 11 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 4b285733..63999ce9 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -18,6 +18,7 @@ #include "log.h" #include "modflagicondelegate.h" #include "modconflicticondelegate.h" +#include "modcontenticondelegate.h" #include "modlistviewactions.h" #include "modlistdropinfo.h" #include "modlistcontextmenu.h" @@ -741,17 +742,9 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(header(), &QHeaderView::sectionResized, [=](int logicalIndex, int oldSize, int newSize) { m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); - GenericIconDelegate* contentDelegate = new GenericIconDelegate(this, ModList::ContentsRole, ModList::COL_CONTENT, 150); - ModFlagIconDelegate* flagDelegate = new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120); - ModConflictIconDelegate* conflictFlagDelegate = new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80); - - connect(header(), &QHeaderView::sectionResized, contentDelegate, &GenericIconDelegate::columnResized); - connect(header(), &QHeaderView::sectionResized, flagDelegate, &ModFlagIconDelegate::columnResized); - connect(header(), &QHeaderView::sectionResized, conflictFlagDelegate, &ModConflictIconDelegate::columnResized); - - setItemDelegateForColumn(ModList::COL_FLAGS, flagDelegate); - setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, conflictFlagDelegate); - setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); + setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120)); + setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80)); + setItemDelegateForColumn(ModList::COL_CONTENT, new ModContentIconDelegate(this, ModList::COL_CONTENT, 150)); if (m_core->settings().geometry().restoreState(header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that @@ -1118,6 +1111,66 @@ std::vector ModListView::conflictFlags(const QModelIndex return flags; } +std::set ModListView::contents(const QModelIndex& index, bool* includeChildren) const +{ + auto modIndex = index.data(ModList::IndexRole); + if (!modIndex.isValid()) { + return {}; + } + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + auto contents = info->getContents(); + bool children = false; + + if (info->isSeparator() + && hasCollapsibleSeparators() + && m_core->settings().interface().collapsibleSeparatorsConflicts() + && !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child contents + std::set eContents(contents.begin(), contents.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cContents = ModInfo::getByIndex(cIndex)->getContents(); + eContents.insert(cContents.begin(), cContents.end()); + } + contents = { eContents.begin(), eContents.end() }; + children = true; + } + + if (includeChildren) { + *includeChildren = children; + } + + return contents; +} + +QList ModListView::contentsIcons(const QModelIndex& index, bool* forceCompact) const +{ + auto contents = this->contents(index, forceCompact); + QList result; + m_core->modDataContents().forEachContentInOrOut( + contents, + [&result](auto const& content) { result.append(content.icon()); }, + [&result](auto const&) { result.append(QString()); }); + return result; +} + +QString ModListView::contentsTooltip(const QModelIndex& index) const +{ + auto contents = this->contents(index, nullptr); + if (contents.empty()) { + return {}; + } + QString result(""); + m_core->modDataContents().forEachContentIn(contents, [&result](auto const& content) { + result.append(QString("" + "") + .arg(content.icon()).arg(content.name())); + }); + result.append("
%2
"); + return result; +} + void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { if (hasCollapsibleSeparators()) { -- cgit v1.3.1 From 183440d6949578c2c626022d42ee412919a19552 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 19:47:13 +0100 Subject: Display mod flags on collapsed separators. --- src/modflagicondelegate.cpp | 10 ++++++---- src/modflagicondelegate.h | 7 ++++++- src/modlistview.cpp | 44 +++++++++++++++++++++++++++++++++++--------- src/modlistview.h | 6 ++++++ 4 files changed, 53 insertions(+), 14 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index c709f30d..30a3c376 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,12 +1,13 @@ #include "modflagicondelegate.h" #include "modlist.h" +#include "modlistview.h" #include #include using namespace MOBase; -ModFlagIconDelegate::ModFlagIconDelegate(QTreeView* view, int column, int compactSize) - : IconDelegate(view, column, compactSize) +ModFlagIconDelegate::ModFlagIconDelegate(ModListView* view, int column, int compactSize) + : IconDelegate(view, column, compactSize), m_view(view) { } @@ -33,8 +34,9 @@ QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const QVariant modid = index.data(ModList::IndexRole); if (modid.isValid()) { - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - return getIconsForFlags(info->getFlags(), compact()); + bool compact; + auto flags = m_view->modFlags(index, &compact); + return getIconsForFlags(flags, compact || this->compact()); } return {}; diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index 63ab8978..24dd4a0e 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -6,12 +6,14 @@ #include "icondelegate.h" #include "modinfo.h" +class ModListView; + class ModFlagIconDelegate : public IconDelegate { Q_OBJECT; public: - explicit ModFlagIconDelegate(QTreeView* view, int column = -1, int compactSize = 120); + explicit ModFlagIconDelegate(ModListView* view, int column = -1, int compactSize = 120); QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; protected: @@ -25,6 +27,9 @@ protected: // ModFlagIconDelegate() : ModFlagIconDelegate(nullptr) { } +private: + ModListView* m_view; + }; #endif // MODFLAGICONDELEGATE_H diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 63999ce9..7c67c599 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -217,10 +217,8 @@ std::optional ModListView::nextMod(unsigned int modIndex) const ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); - // skip overwrite and backups and separators - if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || - mod->hasFlag(ModInfo::FLAG_BACKUP) || - mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + // skip overwrite, backups and separators + if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { continue; } @@ -246,12 +244,9 @@ std::optional ModListView::prevMod(unsigned int modIndex) const modIndex = index.data(ModList::IndexRole).toInt(); - // skip overwrite and backups and separators + // skip overwrite, backups and separators ModInfo::Ptr mod = ModInfo::getByIndex(modIndex); - - if (mod->hasFlag(ModInfo::FLAG_OVERWRITE) || - mod->hasFlag(ModInfo::FLAG_BACKUP) || - mod->hasFlag(ModInfo::FLAG_SEPARATOR)) { + if (mod->isOverwrite() || mod->isBackup() || mod->isSeparator()) { continue; } @@ -1080,6 +1075,37 @@ QColor ModListView::markerColor(const QModelIndex& index) const return QColor(); } +std::vector ModListView::modFlags(const QModelIndex& index, bool* forceCompact) const +{ + ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); + + auto flags = info->getFlags(); + bool compact = false; + if (info->isSeparator() + && hasCollapsibleSeparators() + && m_core->settings().interface().collapsibleSeparatorsConflicts() + && !isExpanded(index.sibling(index.row(), 0))) { + + // combine the child conflicts + std::set eFlags(flags.begin(), flags.end()); + for (int i = 0; i < model()->rowCount(index); ++i) { + auto cIndex = model()->index(i, index.column(), index).data(ModList::IndexRole).toInt(); + auto cFlags = ModInfo::getByIndex(cIndex)->getFlags(); + eFlags.insert(cFlags.begin(), cFlags.end()); + } + flags = { eFlags.begin(), eFlags.end() }; + + // force compact because there can be a lots of flags here + compact = true; + } + + if (forceCompact) { + *forceCompact = true; + } + + return flags; +} + std::vector ModListView::conflictFlags(const QModelIndex& index, bool* forceCompact) const { ModInfo::Ptr info = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); diff --git a/src/modlistview.h b/src/modlistview.h index 48e003bd..af4e83c0 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -173,6 +173,7 @@ protected slots: private: friend class ModConflictIconDelegate; + friend class ModFlagIconDelegate; friend class ModContentIconDelegate; friend class ModListStyledItemDelegated; friend class ModListViewMarkingScrollBar; @@ -195,6 +196,11 @@ private: // QColor markerColor(const QModelIndex& index) const; + // retrieve the mod flags for the given index + // + std::vector modFlags( + const QModelIndex& index, bool* forceCompact = nullptr) const; + // retrieve the conflicts flags for the given index // std::vector conflictFlags( -- cgit v1.3.1 From 5765439c7cff4e314a0c5732432bdb7b20e91efd Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 19:47:31 +0100 Subject: Alt+Click select/deselect the rows below from a separator. --- src/modlistview.cpp | 24 ++++++++++++++++++++++++ src/modlistview.h | 1 + 2 files changed, 25 insertions(+) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 7c67c599..7c770709 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1311,6 +1311,30 @@ void ModListView::timerEvent(QTimerEvent* event) } } +void ModListView::mousePressEvent(QMouseEvent* event) +{ + // we call the parent class first so that we can use the actual + // selection state of the item after + QTreeView::mousePressEvent(event); + + const auto index = indexAt(event->pos()); + + if (event->isAccepted() + && hasCollapsibleSeparators() + && index.isValid() && model()->hasChildren(indexAt(event->pos())) + && (event->modifiers() & Qt::AltModifier)) { + + const auto flag = selectionModel()->isSelected(index) ? + QItemSelectionModel::Select : QItemSelectionModel::Deselect; + const bool expanded = isExpanded(index); + const QItemSelection selection( + model()->index(0, index.column(), index), + model()->index(model()->rowCount(index) - 1, index.column(), index)); + selectionModel()->select(selection, flag | QItemSelectionModel::Rows); + setExpanded(index, expanded); + } +} + bool ModListView::event(QEvent* event) { if (event->type() == QEvent::KeyPress diff --git a/src/modlistview.h b/src/modlistview.h index af4e83c0..08ecd935 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -161,6 +161,7 @@ protected: void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; bool event(QEvent* event) override; protected slots: -- cgit v1.3.1 From f919e0917344d7396f03423ef4fcd4d41fe37b07 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 20:11:26 +0100 Subject: Show conflicts and highligth plugins from collapsed separators. --- src/modinfo.cpp | 1 + src/modinfo.h | 63 ++++++++++++++----------------- src/modinfowithconflictinfo.h | 21 ++++------- src/modlistview.cpp | 87 ++++++++++++++++++++----------------------- src/modlistview.h | 15 +++++--- src/settingsdialog.ui | 6 +-- 6 files changed, 89 insertions(+), 104 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modinfo.cpp b/src/modinfo.cpp index c76cb4ad..b46a68ab 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -49,6 +49,7 @@ using namespace MOBase; using namespace MOShared; +const std::set ModInfo::s_EmptySet; std::vector ModInfo::s_Collection; ModInfo::Ptr ModInfo::s_Overwrite; std::map ModInfo::s_ModsByName; diff --git a/src/modinfo.h b/src/modinfo.h index 08ed94f8..634ea900 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -868,46 +868,35 @@ public: // Nexus stuff public: // Conflicts - /** - * @return retrieve list of mods (as mod index) that are overwritten by this one. - * Updates may be delayed. - */ - virtual std::set getModOverwrite() const { - return std::set(); } + // retrieve the list of mods (as mod index) that are overwritten by this one. + // Updates may be delayed. + // + virtual const std::set& getModOverwrite() const { return s_EmptySet; } - /** - * @return list of mods (as mod index) that overwrite this one. Updates may be delayed. - */ - virtual std::set getModOverwritten() const { - return std::set(); } + // retrieve the list of mods (as mod index) that overwrite this one. + // Updates may be delayed. + // + virtual const std::set& getModOverwritten() const { return s_EmptySet; } - /** - * @return retrieve list of mods (as mod index) with archives that are overwritten by - * this one. Updates may be delayed - */ - virtual std::set getModArchiveOverwrite() const { - return std::set(); } + // retrieve the list of mods (as mod index) with archives that are overwritten by + // this one. Updates may be delayed + // + virtual const std::set& getModArchiveOverwrite() const { return s_EmptySet; } - /** - * @return list of mods (as mod index) with archives that overwrite this one. Updates - * may be delayed. - */ - virtual std::set getModArchiveOverwritten() const { - return std::set(); } + // retrieve the list of mods (as mod index) with archives that overwrite this one. Updates + // may be delayed. + // + virtual const std::set& getModArchiveOverwritten() const { return s_EmptySet; } - /** - * @return the list of mods (as mod index) with archives that are overwritten by loose - * files of this mod. Updates may be delayed. - */ - virtual std::set getModArchiveLooseOverwrite() const { - return std::set(); } + // retrieve the list of mods (as mod index) with archives that are overwritten by loose + // files of this mod. Updates may be delayed. + // + virtual const std::set& getModArchiveLooseOverwrite() const { return s_EmptySet; } - /** - * @return the list of mods (as mod index) with loose files that overwrite this one's - * archive files. Updates may be delayed. - */ - virtual std::set getModArchiveLooseOverwritten() const { - return std::set(); } + // retrieve the list of mods (as mod index) with loose files that overwrite this one's + // archive files. Updates may be delayed. + // + virtual const std::set& getModArchiveLooseOverwritten() const { return s_EmptySet; } /** * @brief Update conflict information. @@ -960,6 +949,10 @@ protected: MOBase::VersionInfo m_Version; bool m_PluginSelected = false; + // empty set that can be returned in overwrite functions by + // default + static const std::set s_EmptySet; + protected: friend class OrganizerCore; diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 889f1246..c9cddb60 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -52,21 +52,16 @@ public: /** * @brief clear all caches held for this mod */ - virtual void clearCaches() override; + void clearCaches() override; - virtual std::set getModOverwrite() const override { return m_OverwriteList; } + const std::set& getModOverwrite() const override { return m_OverwriteList; } + const std::set& getModOverwritten() const override { return m_OverwrittenList; } + const std::set& getModArchiveOverwrite() const override { return m_ArchiveOverwriteList; } + const std::set& getModArchiveOverwritten() const override { return m_ArchiveOverwrittenList; } + const std::set& getModArchiveLooseOverwrite() const override { return m_ArchiveLooseOverwriteList; } + const std::set& getModArchiveLooseOverwritten() const override { return m_ArchiveLooseOverwrittenList; } - virtual std::set getModOverwritten() const override { return m_OverwrittenList; } - - virtual std::set getModArchiveOverwrite() const override { return m_ArchiveOverwriteList; } - - virtual std::set getModArchiveOverwritten() const override { return m_ArchiveOverwrittenList; } - - virtual std::set getModArchiveLooseOverwrite() const override { return m_ArchiveLooseOverwriteList; } - - virtual std::set getModArchiveLooseOverwritten() const override { return m_ArchiveLooseOverwrittenList; } - - virtual void doConflictCheck() const override; + void doConflictCheck() const override; public slots: diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 7c770709..2c9634bd 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -417,7 +417,7 @@ void ModListView::onModPrioritiesChanged(const QModelIndexList& indices) } // update conflict check on the moved mod modInfo->doConflictCheck(); - setOverwriteMarkers(modInfo); + setOverwriteMarkers(selectionModel()->selectedRows()); } } } @@ -682,7 +682,8 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_actions = new ModListViewActions(core, *m_filters, factory, this, mwui->espList, mw); ui = { mwui->groupCombo, mwui->activeModsCounter, mwui->modFilterEdit, - mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators + mwui->currentCategoryLabel, mwui->clearFiltersButton, mwui->filtersSeparators, + mwui->espList }; connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { onModInstalled(name); }); @@ -765,16 +766,6 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); } - // highligth plugins - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=](auto&& selected) { - std::vector modIndices; - for (auto& idx : selectionModel()->selectedRows()) { - modIndices.push_back(idx.data(ModList::IndexRole).toInt()); - } - m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); - mwui->espList->verticalScrollBar()->repaint(); - }); - // prevent the name-column from being hidden header()->setSectionHidden(ModList::COL_NAME, false); @@ -962,35 +953,23 @@ void ModListView::clearOverwriteMarkers() m_markers.archiveLooseOverwritten.clear(); } -void ModListView::setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) -{ - m_markers.overwrite = overwrite; - m_markers.overwritten = overwritten; -} - -void ModListView::setArchiveOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) -{ - m_markers.archiveOverwrite = overwrite; - m_markers.archiveOverwritten = overwritten; -} - -void ModListView::setArchiveLooseOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) -{ - m_markers.archiveLooseOverwrite = overwrite; - m_markers.archiveLooseOverwritten = overwritten; -} - -void ModListView::setOverwriteMarkers(ModInfo::Ptr mod) +void ModListView::setOverwriteMarkers(const QModelIndexList& indexes) { - if (mod) { - setOverwriteMarkers(mod->getModOverwrite(), mod->getModOverwritten()); - setArchiveOverwriteMarkers(mod->getModArchiveOverwrite(), mod->getModArchiveOverwritten()); - setArchiveLooseOverwriteMarkers(mod->getModArchiveLooseOverwrite(), mod->getModArchiveLooseOverwritten()); - } - else { - setOverwriteMarkers({}, {}); - setArchiveOverwriteMarkers({}, {}); - setArchiveLooseOverwriteMarkers({}, {}); + const auto insert = [](auto& dest, const auto& from) { + dest.insert(from.begin(), from.end()); + }; + clearOverwriteMarkers(); + for (auto& idx : indexes) { + auto mIndex = idx.data(ModList::IndexRole); + if (mIndex.isValid()) { + auto info = ModInfo::getByIndex(mIndex.toInt()); + insert(m_markers.overwrite, info->getModOverwrite()); + insert(m_markers.overwritten, info->getModOverwritten()); + insert(m_markers.archiveOverwrite, info->getModArchiveOverwrite()); + insert(m_markers.archiveOverwritten, info->getModArchiveOverwritten()); + insert(m_markers.archiveLooseOverwrite, info->getModArchiveLooseOverwrite()); + insert(m_markers.archiveLooseOverwritten, info->getModArchiveLooseOverwritten()); + } } dataChanged(model()->index(0, 0), model()->index(model()->rowCount(), model()->columnCount())); verticalScrollBar()->repaint(); @@ -1207,15 +1186,29 @@ void ModListView::onSelectionChanged(const QItemSelection& selected, const QItem } } - if (selected.count()) { - auto index = selected.indexes().last(); - ModInfo::Ptr selectedMod = ModInfo::getByIndex(index.data(ModList::IndexRole).toInt()); - setOverwriteMarkers(selectedMod); - } - else { - setOverwriteMarkers(nullptr); + QModelIndexList indexes = selectionModel()->selectedRows(); + + if (m_core->settings().interface().collapsibleSeparatorsConflicts()) { + for (auto& idx : selectionModel()->selectedRows()) { + if (hasCollapsibleSeparators() + && model()->hasChildren(idx) + && !isExpanded(idx)) { + for (int i = 0; i < model()->rowCount(idx); ++i) { + indexes.append(model()->index(i, idx.column(), idx)); + } + } + } } + setOverwriteMarkers(indexes); + + // highligth plugins + std::vector modIndices; + for (auto& idx : indexes) { + modIndices.push_back(idx.data(ModList::IndexRole).toInt()); + } + m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); + ui.pluginList->verticalScrollBar()->repaint(); } void ModListView::onFiltersCriteria(const std::vector& criteria) diff --git a/src/modlistview.h b/src/modlistview.h index 08ecd935..53be08c5 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -23,6 +23,7 @@ class MainWindow; class Profile; class ModListByPriorityProxy; class ModListViewActions; +class PluginListView; class ModListView : public QTreeView { @@ -183,15 +184,14 @@ private: void onModInstalled(const QString& modName); void onModFilterActive(bool filterActive); - // overwrite markers + // clear overwrite markers (without repainting) + // void clearOverwriteMarkers(); - void setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); - void setArchiveOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); - void setArchiveLooseOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); - // set overwrite markers from the mod and repaint (if mod is nullptr, clear overwrite and repaint) + // set overwrite markers from the mod in the given list and repaint (if the list + // is empty, clear overwrite and repaint) // - void setOverwriteMarkers(ModInfo::Ptr mod); + void setOverwriteMarkers(const QModelIndexList& indexes); // retrieve the marker color for the given index // @@ -251,6 +251,9 @@ private: QLabel* currentCategory; QPushButton* clearFilters; QComboBox* filterSeparators; + + // the plugin list (for highligths) + PluginListView* pluginList; }; OrganizerCore* m_core; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index a22beacd..527f9a91 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -318,13 +318,13 @@ If you disable this feature, MO will only display official DLCs this way. Please - Display mod conflicts on separator when collapsed. + Display mod conflicts on and from separator when collapsed, and show plugins from collapsed separators. - Display mod conflicts on separator when collapsed. + Display mod conflicts on and from separator when collapsed, and show plugins from collapsed separators. - Show conflicts on separators + Show conflicts and plugins on separators and from separators true -- cgit v1.3.1 From 80371ae2ccdb233f9aa92c8982bad6a136c54ae9 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 20:33:01 +0100 Subject: Update markers and plugins highligths when separator is expanded/collapsed. --- src/modlistview.cpp | 81 +++++++++++++++++++++++++++-------------------------- src/modlistview.h | 10 ++++++- 2 files changed, 50 insertions(+), 41 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 2c9634bd..7b1b6f0c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -138,6 +138,14 @@ ModListView::ModListView(QWidget* parent) connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); + // the timeout is pretty small because its main purpose is to avoid + // refreshing multiple times when calling expandAll() or collapseAll() + // which emit a lots of expanded/collapsed signals in a very small + // time window + m_refreshMarkersTimer.setInterval(50); + m_refreshMarkersTimer.setSingleShot(true); + connect(&m_refreshMarkersTimer, &QTimer::timeout, [=] { refreshMarkersAndPlugins(); }); + installEventFilter(new CopyEventFilter(this, [=](auto& index) { QVariant mIndex = index.data(ModList::IndexRole); QString name = index.data(Qt::DisplayRole).toString(); @@ -432,8 +440,8 @@ void ModListView::onModInstalled(const QString& modName) QModelIndex qIndex = indexModelToView(m_core->modList()->index(index, 0)); - if (hasCollapsibleSeparators()) { - setExpanded(qIndex, true); + if (hasCollapsibleSeparators() && qIndex.parent().isValid()) { + setExpanded(qIndex.parent(), true); } // focus, scroll to and select @@ -777,7 +785,9 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo }); connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModListView::onSelectionChanged); + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=] { m_refreshMarkersTimer.start(); }); + connect(this, &QTreeView::collapsed, [=] { m_refreshMarkersTimer.start(); }); + connect(this, &QTreeView::expanded, [=] { m_refreshMarkersTimer.start(); }); // filters connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); @@ -975,6 +985,34 @@ void ModListView::setOverwriteMarkers(const QModelIndexList& indexes) verticalScrollBar()->repaint(); } +void ModListView::refreshMarkersAndPlugins() +{ + QModelIndexList indexes = selectionModel()->selectedRows(); + + if (m_core->settings().interface().collapsibleSeparatorsConflicts()) { + for (auto& idx : selectionModel()->selectedRows()) { + if (hasCollapsibleSeparators() + && model()->hasChildren(idx) + && !isExpanded(idx)) { + for (int i = 0; i < model()->rowCount(idx); ++i) { + indexes.append(model()->index(i, idx.column(), idx)); + } + } + } + } + + setOverwriteMarkers(indexes); + + // highligth plugins + std::vector modIndices; + for (auto& idx : indexes) { + modIndices.push_back(idx.data(ModList::IndexRole).toInt()); + } + m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); + ui.pluginList->verticalScrollBar()->repaint(); +} + + void ModListView::setHighlightedMods(const std::vector& pluginIndices) { m_markers.highlight.clear(); @@ -1176,41 +1214,6 @@ QString ModListView::contentsTooltip(const QModelIndex& index) const return result; } -void ModListView::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) -{ - if (hasCollapsibleSeparators()) { - for (auto& idx : selected.indexes()) { - if (idx.parent().isValid() && !isExpanded(idx.parent())) { - setExpanded(idx.parent(), true); - } - } - } - - QModelIndexList indexes = selectionModel()->selectedRows(); - - if (m_core->settings().interface().collapsibleSeparatorsConflicts()) { - for (auto& idx : selectionModel()->selectedRows()) { - if (hasCollapsibleSeparators() - && model()->hasChildren(idx) - && !isExpanded(idx)) { - for (int i = 0; i < model()->rowCount(idx); ++i) { - indexes.append(model()->index(i, idx.column(), idx)); - } - } - } - } - - setOverwriteMarkers(indexes); - - // highligth plugins - std::vector modIndices; - for (auto& idx : indexes) { - modIndices.push_back(idx.data(ModList::IndexRole).toInt()); - } - m_core->pluginList()->highlightPlugins(modIndices, *m_core->directoryStructure()); - ui.pluginList->verticalScrollBar()->repaint(); -} - void ModListView::onFiltersCriteria(const std::vector& criteria) { setFilterCriteria(criteria); @@ -1319,12 +1322,10 @@ void ModListView::mousePressEvent(QMouseEvent* event) const auto flag = selectionModel()->isSelected(index) ? QItemSelectionModel::Select : QItemSelectionModel::Deselect; - const bool expanded = isExpanded(index); const QItemSelection selection( model()->index(0, index.column(), index), model()->index(model()->rowCount(index) - 1, index.column(), index)); selectionModel()->select(selection, flag | QItemSelectionModel::Rows); - setExpanded(index, expanded); } } diff --git a/src/modlistview.h b/src/modlistview.h index 53be08c5..1a174568 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -169,7 +169,6 @@ protected slots: void onCustomContextMenuRequested(const QPoint& pos); void onDoubleClicked(const QModelIndex& index); - void onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); void onFiltersCriteria(const std::vector& filters); private: @@ -184,6 +183,11 @@ private: void onModInstalled(const QString& modName); void onModFilterActive(bool filterActive); + // refresh the overwrite markers and the highligthed plugins from + // the current selection + // + void refreshMarkersAndPlugins(); + // clear overwrite markers (without repainting) // void clearOverwriteMarkers(); @@ -267,6 +271,10 @@ private: QtGroupingProxy* m_byCategoryProxy; QtGroupingProxy* m_byNexusIdProxy; + // marker used to avoid calling refreshing markers to many + // time in a row + QTimer m_refreshMarkersTimer; + // maintain collapsed items for each proxy to avoid // losing them on model reset std::map> m_collapsed; -- cgit v1.3.1 From fc60ea5b7a023493375a6fced6572156f48e36c3 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 20:56:08 +0100 Subject: Add option to have different separator collapsed/expanded states between profiles. --- src/mainwindow.cpp | 1 - src/modlistview.cpp | 22 +++++++++++++++++++--- src/modlistview.h | 5 +---- src/organizercore.cpp | 1 + src/organizercore.h | 7 +++++++ src/settings.cpp | 10 ++++++++++ src/settings.h | 5 +++++ src/settingsdialog.ui | 13 +++++++++++++ src/settingsdialoguserinterface.cpp | 3 +++ 9 files changed, 59 insertions(+), 8 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f761a5f0..ce98772a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1495,7 +1495,6 @@ void MainWindow::startExeAction() void MainWindow::activateSelectedProfile() { m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); - ui->modList->setProfile(m_OrganizerCore.currentProfile()); m_SavesTab->refreshSaveList(); m_OrganizerCore.refresh(); diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 7b1b6f0c..ec31d4dc 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -167,10 +167,24 @@ void ModListView::refresh() updateGroupByProxy(); } -void ModListView::setProfile(Profile* profile) +void ModListView::onProfileChanged(Profile* oldProfile, Profile* newProfile) { - m_sortProxy->setProfile(profile); - m_byPriorityProxy->setProfile(profile); + const auto perProfileSeparators = + m_core->settings().interface().collapsibleSeparatorsPerProfile(); + + // save expanded/collapsed state of separators + if (oldProfile && perProfileSeparators) { + auto& collapsed = m_collapsed[m_byPriorityProxy]; + oldProfile->storeSetting("UserInterface", "collapsed_separators", QStringList(collapsed.begin(), collapsed.end())); + } + + m_sortProxy->setProfile(newProfile); + m_byPriorityProxy->setProfile(newProfile); + + if (newProfile && perProfileSeparators) { + auto collapsed = newProfile->setting("UserInterface", "collapsed_separators", QStringList()).toStringList(); + m_collapsed[m_byPriorityProxy] = { collapsed.begin(), collapsed.end() }; + } } bool ModListView::hasCollapsibleSeparators() const @@ -694,7 +708,9 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo mwui->espList }; + connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { onModInstalled(name); }); + connect(m_core, &OrganizerCore::profileChanged, this, &ModListView::onProfileChanged); connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { onModPrioritiesChanged(indices); }); connect(core.modList(), &ModList::clearOverwrite, [=] { m_actions->clearOverwrite(); }); connect(core.modList(), &ModList::modStatesChanged, [=] { updateModCount(); }); diff --git a/src/modlistview.h b/src/modlistview.h index 1a174568..d8e08a99 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -57,10 +57,6 @@ public: void restoreState(const Settings& s); void saveState(Settings& s) const; - // set the current profile - // - void setProfile(Profile* profile); - // check if collapsible separators are currently used // bool hasCollapsibleSeparators() const; @@ -170,6 +166,7 @@ protected slots: void onCustomContextMenuRequested(const QPoint& pos); void onDoubleClicked(const QModelIndex& index); void onFiltersCriteria(const std::vector& filters); + void onProfileChanged(Profile* oldProfile, Profile* newProfile); private: diff --git a/src/organizercore.cpp b/src/organizercore.cpp index d952bc4c..90771c0a 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -602,6 +602,7 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->debugDump(); + emit profileChanged(oldProfile.get(), m_CurrentProfile.get()); m_ProfileChanged(oldProfile.get(), m_CurrentProfile.get()); } diff --git a/src/organizercore.h b/src/organizercore.h index 24de26ee..905fb111 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -406,6 +406,13 @@ signals: void close(); + // emitted when the profile is changed, before notifying plugins + // + // the new profile can be stored but the old one is temporary and + // should not be + // + void profileChanged(Profile* oldProfile, Profile* newProfile); + // Notify that the directory structure is ready to be used on the main thread // Use queued connections void directoryStructureReady(); diff --git a/src/settings.cpp b/src/settings.cpp index 7f943904..251e2b15 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2195,6 +2195,16 @@ void InterfaceSettings::setCollapsibleSeparatorsConflicts(bool b) set(m_Settings, "Settings", "collapsible_separators_conflicts", b); } +bool InterfaceSettings::collapsibleSeparatorsPerProfile() const +{ + return get(m_Settings, "Settings", "collapsible_separators_per_profile", false); +} + +void InterfaceSettings::setCollapsibleSeparatorsPerProfile(bool b) +{ + set(m_Settings, "Settings", "collapsible_separators_per_profile", b); +} + bool InterfaceSettings::saveFilters() const { return get(m_Settings, "Settings", "save_filters", false); diff --git a/src/settings.h b/src/settings.h index d61f3c20..efcfee57 100644 --- a/src/settings.h +++ b/src/settings.h @@ -631,6 +631,11 @@ public: bool collapsibleSeparatorsConflicts() const; void setCollapsibleSeparatorsConflicts(bool b); + // whether each profile should have its own expansion state + // + bool collapsibleSeparatorsPerProfile() const; + void setCollapsibleSeparatorsPerProfile(bool b); + // whether to save/restore filter states between runs // bool saveFilters() const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index 527f9a91..b09a26de 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -331,6 +331,19 @@ If you disable this feature, MO will only display official DLCs this way. Please + + + + Do not share the collapse/expanded state of separators between profiles. + + + Do not share the collapse/expanded state of separators between profiles. + + + Profile-specific collapse states for separators + + + diff --git a/src/settingsdialoguserinterface.cpp b/src/settingsdialoguserinterface.cpp index 550b4ddd..5d856395 100644 --- a/src/settingsdialoguserinterface.cpp +++ b/src/settingsdialoguserinterface.cpp @@ -15,6 +15,7 @@ UserInterfaceSettingsTab::UserInterfaceSettingsTab(Settings& s, SettingsDialog& // connect before setting to trigger QObject::connect(ui->collapsibleSeparatorsBox, &QGroupBox::toggled, [=](auto&& on) { ui->collapsibleSeparatorsConflictsBox->setEnabled(on); + ui->collapsibleSeparatorsPerProfileBox->setEnabled(on); }); // mod list @@ -22,6 +23,7 @@ UserInterfaceSettingsTab::UserInterfaceSettingsTab(Settings& s, SettingsDialog& ui->colorSeparatorsBox->setChecked(settings().colors().colorSeparatorScrollbar()); ui->collapsibleSeparatorsConflictsBox->setChecked(settings().interface().collapsibleSeparatorsConflicts()); ui->collapsibleSeparatorsBox->setChecked(settings().interface().collapsibleSeparators()); + ui->collapsibleSeparatorsPerProfileBox->setChecked(settings().interface().collapsibleSeparatorsPerProfile()); ui->saveFiltersBox->setChecked(settings().interface().saveFilters()); // download list @@ -42,6 +44,7 @@ void UserInterfaceSettingsTab::update() settings().interface().setDisplayForeign(ui->displayForeignBox->isChecked()); settings().interface().setCollapsibleSeparators(ui->collapsibleSeparatorsBox->isChecked()); settings().interface().setCollapsibleSeparatorsConflicts(ui->collapsibleSeparatorsConflictsBox->isChecked()); + settings().interface().setCollapsibleSeparatorsPerProfile(ui->collapsibleSeparatorsPerProfileBox->isChecked()); settings().interface().setSaveFilters(ui->saveFiltersBox->isChecked()); // download list -- cgit v1.3.1 From 7d9fa5e4f96840321ff996b4c637b7cd686c3570 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 21:46:58 +0100 Subject: Fix drop below collapsed separator. --- src/modlistbypriorityproxy.cpp | 38 ++++++++++++++++++++++++-------------- src/modlistbypriorityproxy.h | 10 +++++++++- src/modlistview.cpp | 10 +++++++++- src/modlistview.h | 8 +++++++- 4 files changed, 49 insertions(+), 17 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistbypriorityproxy.cpp b/src/modlistbypriorityproxy.cpp index 6f549d91..c0d70571 100644 --- a/src/modlistbypriorityproxy.cpp +++ b/src/modlistbypriorityproxy.cpp @@ -183,6 +183,25 @@ bool ModListByPriorityProxy::setData(const QModelIndex& index, const QVariant& v return QAbstractProxyModel::setData(index, value, role); } +std::pair ModListByPriorityProxy::dropSeparators(const ModListDropInfo& dropInfo) const +{ + bool hasSeparator = false; + unsigned int firstRowIndex = -1; + + int firstRowPriority = INT_MAX; + for (auto sourceRow : dropInfo.rows()) { + hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); + if (m_profile->getModPriority(sourceRow) < firstRowPriority) { + firstRowIndex = sourceRow; + firstRowPriority = m_profile->getModPriority(sourceRow); + } + } + + bool firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + + return { hasSeparator, firstRowSeparator }; +} + bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { ModListDropInfo dropInfo(data, m_core); @@ -193,19 +212,7 @@ bool ModListByPriorityProxy::canDropMimeData(const QMimeData* data, Qt::DropActi if (dropInfo.isModDrop()) { - bool hasSeparator = false; - unsigned int firstRowIndex = -1; - - int firstRowPriority = INT_MAX; - for (auto sourceRow : dropInfo.rows()) { - hasSeparator = hasSeparator || ModInfo::getByIndex(sourceRow)->isSeparator(); - if (m_profile->getModPriority(sourceRow) < firstRowPriority) { - firstRowIndex = sourceRow; - firstRowPriority = m_profile->getModPriority(sourceRow); - } - } - - bool firstRowSeparator = firstRowIndex != -1 && ModInfo::getByIndex(firstRowIndex)->isSeparator(); + auto [hasSeparator, firstRowSeparator] = dropSeparators(dropInfo); // row = -1 and invalid parent means we're dropping onto an item, we don't want to drop // separators onto items or items into their own separator @@ -264,6 +271,7 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction } } else { + if (row >= 0) { if (!parent.isValid()) { if (row < m_Root.children.size()) { @@ -271,6 +279,7 @@ bool ModListByPriorityProxy::dropMimeData(const QMimeData* data, Qt::DropAction if (row > 0 && m_Root.children[row - 1]->mod->isSeparator() && !m_Root.children[row - 1]->children.empty() + && m_dropExpanded && m_dropPosition == ModListView::DropPosition::BelowItem) { sourceRow = m_Root.children[row - 1]->children[0]->index; } @@ -316,7 +325,8 @@ QModelIndex ModListByPriorityProxy::index(int row, int column, const QModelIndex return createIndex(row, column, parentItem->children[row].get()); } -void ModListByPriorityProxy::onDropEnter(const QMimeData*, ModListView::DropPosition dropPosition) +void ModListByPriorityProxy::onDropEnter(const QMimeData*, bool dropExpanded, ModListView::DropPosition dropPosition) { + m_dropExpanded = dropExpanded; m_dropPosition = dropPosition; } diff --git a/src/modlistbypriorityproxy.h b/src/modlistbypriorityproxy.h index 6b48678a..c64a7973 100644 --- a/src/modlistbypriorityproxy.h +++ b/src/modlistbypriorityproxy.h @@ -16,6 +16,7 @@ #include "modlistview.h" class ModList; +class ModListDropInfo; class Profile; class ModListByPriorityProxy : public QAbstractProxyModel @@ -46,7 +47,7 @@ public: public slots: - void onDropEnter(const QMimeData* data, ModListView::DropPosition dropPosition); + void onDropEnter(const QMimeData* data, bool dropExpanded, ModListView::DropPosition dropPosition); protected slots: @@ -54,6 +55,11 @@ protected slots: private: + // returns a pair of boolean, the first one indicates if the drop info + // contains separators, the first one if the first row is a separator + // + std::pair dropSeparators(const ModListDropInfo& dropInfo) const; + void buildTree(); struct TreeItem { @@ -83,6 +89,8 @@ private: private: OrganizerCore& m_core; Profile* m_profile; + + bool m_dropExpanded = false; ModListView::DropPosition m_dropPosition = ModListView::DropPosition::OnItem; }; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index ec31d4dc..91d522a6 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1295,10 +1295,18 @@ void ModListView::dragMoveEvent(QDragMoveEvent* event) void ModListView::dropEvent(QDropEvent* event) { + // from Qt source + QModelIndex index; + if (viewport()->rect().contains(event->pos())) { + index = indexAt(event->pos()); + if (!index.isValid() || !visualRect(index).contains(event->pos())) + index = QModelIndex(); + } + // this event is used by the byPriorityProxy to know if allow // dropping mod between a separator and its first mod (there // is no way to deduce this except using dropIndicatorPosition()) - emit dropEntered(event->mimeData(), static_cast(dropIndicatorPosition())); + emit dropEntered(event->mimeData(), isExpanded(index), static_cast(dropIndicatorPosition())); // see selectedIndexes() m_inDragMoveEvent = true; diff --git a/src/modlistview.h b/src/modlistview.h index d8e08a99..6301a5da 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -90,8 +90,14 @@ public: signals: + // emitted for dragEnter events + // void dragEntered(const QMimeData* mimeData); - void dropEntered(const QMimeData* mimeData, DropPosition position); + + // emitted for dropEnter events, the boolean indicates if the drop target + // is expanded and the position of the indicator + // + void dropEntered(const QMimeData* mimeData, bool dropExpanded, DropPosition position); public slots: -- cgit v1.3.1 From abd82f88db694557a9eac0706f6831f150123c7b Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Thu, 7 Jan 2021 22:25:07 +0100 Subject: Fix Alt+Click for selecting children of separators. --- src/modlistview.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/modlistview.h | 1 + 2 files changed, 46 insertions(+) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 91d522a6..53070d08 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1333,10 +1333,19 @@ void ModListView::timerEvent(QTimerEvent* event) void ModListView::mousePressEvent(QMouseEvent* event) { + // disable edit if Alt is pressed + auto triggers = editTriggers(); + if (event->modifiers() & Qt::AltModifier) { + setEditTriggers(NoEditTriggers); + } + // we call the parent class first so that we can use the actual // selection state of the item after QTreeView::mousePressEvent(event); + // restore triggers + setEditTriggers(triggers); + const auto index = indexAt(event->pos()); if (event->isAccepted() @@ -1353,6 +1362,42 @@ void ModListView::mousePressEvent(QMouseEvent* event) } } +void ModListView::mouseReleaseEvent(QMouseEvent* event) +{ + // this is a duplicate of mousePressEvent because for some reason + // the selection is not always triggered in mousePressEvent and only + // doing it here create a small lag between the selection of the + // separator and the children + + // disable edit if Alt is pressed + auto triggers = editTriggers(); + if (event->modifiers() & Qt::AltModifier) { + setEditTriggers(NoEditTriggers); + } + + // we call the parent class first so that we can use the actual + // selection state of the item after + QTreeView::mouseReleaseEvent(event); + + const auto index = indexAt(event->pos()); + + // restore triggers + setEditTriggers(triggers); + + if (event->isAccepted() + && hasCollapsibleSeparators() + && index.isValid() && model()->hasChildren(indexAt(event->pos())) + && (event->modifiers() & Qt::AltModifier)) { + + const auto flag = selectionModel()->isSelected(index) ? + QItemSelectionModel::Select : QItemSelectionModel::Deselect; + const QItemSelection selection( + model()->index(0, index.column(), index), + model()->index(model()->rowCount(index) - 1, index.column(), index)); + selectionModel()->select(selection, flag | QItemSelectionModel::Rows); + } +} + bool ModListView::event(QEvent* event) { if (event->type() == QEvent::KeyPress diff --git a/src/modlistview.h b/src/modlistview.h index 6301a5da..3b64e01b 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -165,6 +165,7 @@ protected: void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; bool event(QEvent* event) override; protected slots: -- cgit v1.3.1 From 7b684f4d15fa8f75fe802f9dd1534cf4ee2c25ea Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 8 Jan 2021 17:41:10 +0100 Subject: Tentative fix for automatic text color when markers or separator colors are used. --- src/modlistview.cpp | 21 ++++++++++++++++----- src/settings.cpp | 8 ++++---- 2 files changed, 20 insertions(+), 9 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 53070d08..6b5ae66b 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -82,18 +82,29 @@ public: if (!color.isValid()) { color = index.data(Qt::BackgroundRole).value(); } - else { - // disable alternating row if the color is from the children - opt.features &= ~QStyleOptionViewItem::Alternate; - } opt.backgroundBrush = color; + // we need to find the background color to compute the ideal text color + // but the mod list view uses alternate color so we need to find the + // right color + auto bg = opt.palette.base().color(); + auto vrow = (opt.rect.y() - m_view->verticalOffset()) / opt.rect.height(); + if (vrow % 2 == 1) { + bg = opt.palette.alternateBase().color(); + } + // compute ideal foreground color for some rows if (color.isValid()) { if ((index.column() == ModList::COL_NAME && ModInfo::getByIndex(index.data(ModList::IndexRole).toInt())->isSeparator()) || index.column() == ModList::COL_NOTES) { - opt.palette.setBrush(QPalette::Text, ColorSettings::idealTextColor(color)); + + // combine the color with the background and then find the "ideal" text color + const auto a = color.alpha() / 255.; + int r = (1 - a) * bg.red() + a * color.red(), + g = (1 - a) * bg.green() + a * color.green(), + b = (1 - a) * bg.blue() + a * color.blue(); + opt.palette.setBrush(QPalette::Text, ColorSettings::idealTextColor(QColor(r, g, b))); } } diff --git a/src/settings.cpp b/src/settings.cpp index 251e2b15..488a9b01 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1326,12 +1326,12 @@ void ColorSettings::setColorSeparatorScrollbar(bool b) QColor ColorSettings::idealTextColor(const QColor& rBackgroundColor) { - if (rBackgroundColor.alpha() == 0) + if (rBackgroundColor.alpha() < 50) return QColor(Qt::black); - const int THRESHOLD = 106 * 255.0f / rBackgroundColor.alpha(); - int BackgroundDelta = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); - return QColor((255 - BackgroundDelta <= THRESHOLD) ? Qt::black : Qt::white); + // "inverse' of luminance of the background + int iLuminance = (rBackgroundColor.red() * 0.299) + (rBackgroundColor.green() * 0.587) + (rBackgroundColor.blue() * 0.114); + return QColor(iLuminance >= 128 ? Qt::black : Qt::white); } -- cgit v1.3.1 From 3e5d71923ffa41e2af552024cd929aca93830fd0 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 8 Jan 2021 20:14:37 +0100 Subject: Rename ModListStyledItemDelegated -> ModListStyledItemDelegate. --- src/modlistview.cpp | 8 ++++---- src/modlistview.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 6b5ae66b..f4f045c6 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -41,13 +41,13 @@ using namespace MOShared; // handling (e.g. checkbox, edit, etc.), so we also need to override // the visualRect() function from the mod list view. // -class ModListStyledItemDelegated : public QStyledItemDelegate +class ModListStyledItemDelegate : public QStyledItemDelegate { ModListView* m_view; public: - ModListStyledItemDelegated(ModListView* view) : + ModListStyledItemDelegate(ModListView* view) : QStyledItemDelegate(view), m_view(view) { } void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override @@ -144,7 +144,7 @@ ModListView::ModListView(QWidget* parent) MOBase::setCustomizableColumns(this); setAutoExpandDelay(750); - setItemDelegate(new ModListStyledItemDelegated(this)); + setItemDelegate(new ModListStyledItemDelegate(this)); connect(this, &ModListView::doubleClicked, this, &ModListView::onDoubleClicked); connect(this, &ModListView::customContextMenuRequested, this, &ModListView::onCustomContextMenuRequested); @@ -855,7 +855,7 @@ void ModListView::saveState(Settings& s) const QRect ModListView::visualRect(const QModelIndex& index) const { // this shift the visualRect() from QTreeView to match the new actual - // zone after removing indentation (see the ModListStyledItemDelegated) + // zone after removing indentation (see the ModListStyledItemDelegate) QRect rect = QTreeView::visualRect(index); if (hasCollapsibleSeparators() && index.column() == 0 && index.isValid() diff --git a/src/modlistview.h b/src/modlistview.h index 3b64e01b..c9588181 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -180,7 +180,7 @@ private: friend class ModConflictIconDelegate; friend class ModFlagIconDelegate; friend class ModContentIconDelegate; - friend class ModListStyledItemDelegated; + friend class ModListStyledItemDelegate; friend class ModListViewMarkingScrollBar; void onModPrioritiesChanged(const QModelIndexList& indices); -- cgit v1.3.1 From 2606b10c7efdaa65e56ece77bad90bd55c154111 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Fri, 8 Jan 2021 20:15:01 +0100 Subject: Display upgrade/warning for versions on collapsed separators. --- src/CMakeLists.txt | 1 + src/modlistversiondelegate.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++ src/modlistversiondelegate.h | 21 +++++++++++++++ src/modlistview.cpp | 2 ++ 4 files changed, 84 insertions(+) create mode 100644 src/modlistversiondelegate.cpp create mode 100644 src/modlistversiondelegate.h (limited to 'src/modlistview.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c274f989..d1092637 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -151,6 +151,7 @@ add_filter(NAME src/modlist/view GROUPS modflagicondelegate modcontenticondelegate modconflicticondelegate + modlistversiondelegate ) add_filter(NAME src/plugins GROUPS diff --git a/src/modlistversiondelegate.cpp b/src/modlistversiondelegate.cpp new file mode 100644 index 00000000..f60edcf8 --- /dev/null +++ b/src/modlistversiondelegate.cpp @@ -0,0 +1,60 @@ +#include "modlistversiondelegate.h" + +#include "modlistview.h" +#include "log.h" + +ModListVersionDelegate::ModListVersionDelegate(ModListView* view) : + QItemDelegate(view), m_view(view) { } + + +void ModListVersionDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + m_view->itemDelegate()->paint(painter, option, index); + + if (m_view->hasCollapsibleSeparators() + && m_view->model()->hasChildren(index) + && !m_view->isExpanded(index.sibling(index.row(), 0))) { + auto* model = m_view->model(); + + bool downgrade = false, upgrade = false; + + for (int i = 0; i < model->rowCount(index); ++i) { + const auto mIndex = model->index(i, index.column(), index).data(ModList::IndexRole); + if (mIndex.isValid()) { + auto info = ModInfo::getByIndex(mIndex.toInt()); + downgrade = downgrade || info->downgradeAvailable(); + upgrade = upgrade || info->updateAvailable(); + } + } + + const int margin = m_view->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, m_view) + 1; + + QStyleOptionViewItem opt(option); + const int sz = m_view->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, m_view); + opt.decorationSize = QSize(sz, sz); + opt.decorationAlignment = Qt::AlignCenter; + + if (upgrade) { + QIcon icon(":/MO/gui/update_available"); + QPixmap pixmap = decoration(opt, icon); + + QSize pm = icon.actualSize(opt.decorationSize); + pm.rwidth() += 2 * margin; + opt.rect.setRect(opt.rect.x(), opt.rect.y(), pm.width(), opt.rect.height()); + + drawDecoration(painter, opt, opt.rect, pixmap); + } + + if (downgrade) { + QIcon icon(":/MO/gui/warning"); + QPixmap pixmap = decoration(opt, icon); + + QSize pm = icon.actualSize(opt.decorationSize); + pm.rwidth() += 2 * margin; + opt.rect.setRect(opt.rect.x() + opt.decorationSize.width() + margin, opt.rect.y(), pm.width(), opt.rect.height()); + + drawDecoration(painter, opt, opt.rect, pixmap); + + } + } +} diff --git a/src/modlistversiondelegate.h b/src/modlistversiondelegate.h new file mode 100644 index 00000000..8a51e9b4 --- /dev/null +++ b/src/modlistversiondelegate.h @@ -0,0 +1,21 @@ +#ifndef MODLISTVERSIONDELEGATE_H +#define MODLISTVERSIONDELEGATE_H + +#include + +class ModListView; + +class ModListVersionDelegate : public QItemDelegate +{ +public: + ModListVersionDelegate(ModListView* view); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + +private: + + ModListView* m_view; +}; + +#endif diff --git a/src/modlistview.cpp b/src/modlistview.cpp index f4f045c6..c23cd34b 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -19,6 +19,7 @@ #include "modflagicondelegate.h" #include "modconflicticondelegate.h" #include "modcontenticondelegate.h" +#include "modlistversiondelegate.h" #include "modlistviewactions.h" #include "modlistdropinfo.h" #include "modlistcontextmenu.h" @@ -776,6 +777,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120)); setItemDelegateForColumn(ModList::COL_CONFLICTFLAGS, new ModConflictIconDelegate(this, ModList::COL_CONFLICTFLAGS, 80)); setItemDelegateForColumn(ModList::COL_CONTENT, new ModContentIconDelegate(this, ModList::COL_CONTENT, 150)); + setItemDelegateForColumn(ModList::COL_VERSION, new ModListVersionDelegate(this)); if (m_core->settings().geometry().restoreState(header())) { // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that -- cgit v1.3.1 From 32e441897fb5dc5d49764c8ce2cb8b3475bcdd20 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 10 Jan 2021 19:29:41 +0100 Subject: Request window focus when dropping external folder and scroll to new mod. --- src/modlistview.cpp | 22 ++++++++++++++++------ src/modlistview.h | 7 ++++++- 2 files changed, 22 insertions(+), 7 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index c23cd34b..13c34226 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -390,6 +390,15 @@ void ModListView::setSelected(const QModelIndex& current, const QModelIndexList& } } +void ModListView::scrollToAndSelect(const QModelIndex& index) +{ + // focus, scroll to and select + scrollTo(index); + setCurrentIndex(index); + selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + QTimer::singleShot(0, [=] { setFocus(); }); +} + void ModListView::refreshExpandedItems() { auto* model = m_sortProxy->sourceModel(); @@ -470,11 +479,7 @@ void ModListView::onModInstalled(const QString& modName) setExpanded(qIndex.parent(), true); } - // focus, scroll to and select - setFocus(Qt::OtherFocusReason); - scrollTo(qIndex); - setCurrentIndex(qIndex); - selectionModel()->select(qIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + scrollToAndSelect(qIndex); } void ModListView::onModFilterActive(bool filterActive) @@ -589,6 +594,8 @@ void ModListView::refreshFilters() void ModListView::onExternalFolderDropped(const QUrl& url, int priority) { + setWindowState(Qt::WindowActive); + QFileInfo fileInfo(url.toLocalFile()); GuessedValue name; @@ -624,9 +631,12 @@ void ModListView::onExternalFolderDropped(const QUrl& url, int priority) m_core->refresh(); + const auto index = ModInfo::getIndex(name); if (priority != -1) { - m_core->modList()->changeModPriority(ModInfo::getIndex(name), priority); + m_core->modList()->changeModPriority(index, priority); } + + scrollToAndSelect(indexModelToView(m_core->modList()->index(index, 0))); } bool ModListView::moveSelection(int key) diff --git a/src/modlistview.h b/src/modlistview.h index c9588181..53c71458 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -79,11 +79,16 @@ public: std::optional nextMod(unsigned int index) const; std::optional prevMod(unsigned int index) const; - // check if the given mod is visible + // check if the given mod is visible, i.e. not filtered (returns true + // for collapsed mods) // bool isModVisible(unsigned int index) const; bool isModVisible(ModInfo::Ptr mod) const; + // focus the view, select the given index and scroll to it + // + void scrollToAndSelect(const QModelIndex& index); + // refresh the view (to call when settings have been changed) // void refresh(); -- cgit v1.3.1 From 8115335c55c229b8da741dfb075e240334d2f0ab Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 10 Jan 2021 19:31:29 +0100 Subject: Request window focus when dropping external archive. --- src/modlistview.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 13c34226..2fde97f6 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -820,6 +820,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo m_core->installDownload(row, priority); }); connect(m_core->modList(), &ModList::externalArchiveDropped, [=](const QUrl& url, int priority) { + setWindowState(Qt::WindowActive); m_core->installArchive(url.toLocalFile(), priority, false, nullptr); }); connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); -- cgit v1.3.1 From a952f51ef75420426e8ed65e8f78d990c10770b0 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Sun, 10 Jan 2021 20:08:48 +0100 Subject: Change 'Create empty mod' depending on the currently selected mod. --- src/modlistcontextmenu.cpp | 9 +++++---- src/modlistview.cpp | 2 +- src/modlistviewactions.cpp | 31 ++++++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) (limited to 'src/modlistview.cpp') diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp index 78949be3..97eef0eb 100644 --- a/src/modlistcontextmenu.cpp +++ b/src/modlistcontextmenu.cpp @@ -23,13 +23,14 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV if (modIndex.isValid()) { auto info = ModInfo::getByIndex(modIndex.toInt()); if (!info->isBackup()) { - addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(index); }); - addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(index); }); + addAction(info->isSeparator() ? tr("Create empty mod inside") : tr("Create empty mod before"), + [=]() { view->actions().createEmptyMod(index); }); + addAction(tr("Create separator before"), [=]() { view->actions().createSeparator(index); }); } } else { - addAction(tr("Create empty mod"), [=]() { view->actions().createEmptyMod(); }); - addAction(tr("Create Separator"), [=]() { view->actions().createSeparator(); }); + addAction(tr("Create empty mod at the end"), [=]() { view->actions().createEmptyMod(); }); + addAction(tr("Create separator at the end"), [=]() { view->actions().createSeparator(); }); } if (view->hasCollapsibleSeparators()) { diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 2fde97f6..5e3e6b58 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -396,7 +396,7 @@ void ModListView::scrollToAndSelect(const QModelIndex& index) scrollTo(index); setCurrentIndex(index); selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); - QTimer::singleShot(0, [=] { setFocus(); }); + QTimer::singleShot(50, [=] { setFocus(); }); } void ModListView::refreshExpandedItems() diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp index 9957618a..14c4ab46 100644 --- a/src/modlistviewactions.cpp +++ b/src/modlistviewactions.cpp @@ -97,8 +97,30 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const } int newPriority = -1; - if (index.isValid() && m_view->sortColumn() == ModList::COL_PRIORITY) { - newPriority = m_core.currentProfile()->getModPriority(index.data(ModList::IndexRole).toInt()); + if (index.isValid() && index.data(ModList::IndexRole).isValid() + && m_view->sortColumn() == ModList::COL_PRIORITY) { + auto mIndex = index.data(ModList::IndexRole).toInt(); + auto info = ModInfo::getByIndex(mIndex); + newPriority = m_core.currentProfile()->getModPriority(mIndex); + if (info->isSeparator()) { + auto& ibp = m_core.currentProfile()->getAllIndexesByPriority(); + + // start right after the current priority and look for the next + // separator + auto it = ibp.find(newPriority + 1); + for (; it != ibp.end(); ++it) { + auto info = ModInfo::getByIndex(it->second); + if (info->isSeparator()) { + newPriority = it->first; + break; + } + } + + // no separator found, create at the end + if (it == ibp.end()) { + newPriority = -1; + } + } } IModInterface* newMod = m_core.createMod(name); @@ -108,9 +130,12 @@ void ModListViewActions::createEmptyMod(const QModelIndex& index) const m_core.refresh(); + const auto mIndex = ModInfo::getIndex(name); if (newPriority >= 0) { - m_core.modList()->changeModPriority(ModInfo::getIndex(name), newPriority); + m_core.modList()->changeModPriority(mIndex, newPriority); } + + m_view->scrollToAndSelect(m_view->indexModelToView(m_core.modList()->index(mIndex, 0))); } void ModListViewActions::createSeparator(const QModelIndex& index) const -- cgit v1.3.1 From 566441a7067ad0a83173b6652aa4a0c345b93b42 Mon Sep 17 00:00:00 2001 From: Mikaël Capelle Date: Mon, 11 Jan 2021 09:36:35 +0100 Subject: Add comment to mousePressEvent(). --- src/modlistview.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/modlistview.cpp') diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 5e3e6b58..8827733b 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1357,6 +1357,12 @@ void ModListView::timerEvent(QTimerEvent* event) void ModListView::mousePressEvent(QMouseEvent* event) { + // allow alt+click to select all mods inside a separator + // when using collapsible separators + // + // similar code is also present in mouseReleaseEvent to + // avoid missing events + // disable edit if Alt is pressed auto triggers = editTriggers(); if (event->modifiers() & Qt::AltModifier) { -- cgit v1.3.1