summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikaël Capelle <capelle.mikael@gmail.com>2020-12-31 02:29:31 +0100
committerMikaël Capelle <capelle.mikael@gmail.com>2021-01-02 15:38:17 +0100
commit6e4b1790ae4057dfafd39dadff04c8d9b5f2eaeb (patch)
treef809f8e7933dd2d23a33e16279fec42c4a522bc4
parent4ee929b68a5ba3f622fd6cecf37f61b983eb0874 (diff)
Move the separator context menu.
-rw-r--r--src/mainwindow.cpp66
-rw-r--r--src/mainwindow.h3
-rw-r--r--src/modlist.h2
-rw-r--r--src/modlistcontextmenu.cpp150
-rw-r--r--src/modlistcontextmenu.h52
-rw-r--r--src/modlistviewactions.cpp98
-rw-r--r--src/modlistviewactions.h29
7 files changed, 325 insertions, 75 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6942f83b..cd31a0bf 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -3363,45 +3363,6 @@ void MainWindow::addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, cons
refreshFilters();
}
-void MainWindow::replaceCategories_MenuHandler(QMenu* menu, int modIndex)
-{
- QList<QPersistentModelIndex> selected;
- for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) {
- selected.append(QPersistentModelIndex(idx));
- }
-
- if (selected.size() > 0) {
- QStringList selectedMods;
- int minRow = INT_MAX;
- int maxRow = -1;
- for (int i = 0; i < selected.size(); ++i) {
- QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i));
- selectedMods.append(temp.data().toString());
- replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row());
- if (temp.row() < minRow) minRow = temp.row();
- if (temp.row() > maxRow) maxRow = temp.row();
- }
-
- m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1);
-
- // find mods by their name because indices are invalidated
- QAbstractItemModel *model = ui->modList->model();
- for (const QString &mod : selectedMods) {
- QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1,
- Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive);
- if (matches.size() > 0) {
- ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows);
- }
- }
- } else {
- //For single mod selections, just do a replace
- replaceCategoriesFromMenu(menu, modIndex);
- m_OrganizerCore.modList()->notifyChange(modIndex);
- }
-
- refreshFilters();
-}
-
void MainWindow::saveArchiveList()
{
if (m_OrganizerCore.isArchivesInit()) {
@@ -3703,7 +3664,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
int modIndex = ui->modList->indexAt(pos).data(ModList::IndexRole).toInt();
int contextColumn = contextIdx.column();
- ModListContextMenu menu(m_OrganizerCore, contextIdx, ui->modList);
+ ModListContextMenu menu(contextIdx, m_OrganizerCore, m_CategoryFactory, ui->modList);
ModInfo::Ptr info = ModInfo::getByIndex(modIndex);
std::vector<ModInfo::EFlag> flags = info->getFlags();
@@ -3718,29 +3679,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
// separator
else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_SEPARATOR) != flags.end()){
- menu.addSeparator();
- QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"), &menu);
- populateMenuCategories(modIndex, addRemoveCategoriesMenu, 0);
- connect(addRemoveCategoriesMenu, &QMenu::aboutToHide, [=]() { addRemoveCategories_MenuHandler(addRemoveCategoriesMenu, modIndex, contextIdx); });
- addMenuAsPushButton(&menu, addRemoveCategoriesMenu);
- QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"), &menu);
- connect(primaryCategoryMenu, &QMenu::aboutToShow, [=]() { setPrimaryCategoryCandidates(primaryCategoryMenu, info); });
- addMenuAsPushButton(&menu, primaryCategoryMenu);
- menu.addSeparator();
- menu.addAction(tr("Rename Separator..."), [=]() { renameMod_clicked(); });
- menu.addAction(tr("Remove Separator..."), [=]() { removeMod_clicked(modIndex); });
- menu.addSeparator();
- if (ui->modList->sortColumn() == ModList::COL_PRIORITY) {
- menu.addMenu(menu.createSendToContextMenu());
- menu.addSeparator();
- }
- menu.addAction(tr("Select Color..."), [=]() { setColor_clicked(modIndex); });
-
- if (info->color().isValid()) {
- menu.addAction(tr("Reset Color"), [=]() { resetColor_clicked(modIndex); });
- }
-
- menu.addSeparator();
}
// foregin
@@ -3864,9 +3802,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
}
menu.addAction(tr("Open in Explorer"), [=]() { ui->modList->actions().openExplorer({ contextIdx }); });
- }
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) {
QAction* infoAction = menu.addAction(tr("Information..."), [=]() { information_clicked(modIndex); });
menu.setDefaultAction(infoAction);
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 49ecee32..440e39cc 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -152,6 +152,7 @@ public slots:
void directory_refreshed();
void updatePluginCount();
+ void refreshFilters();
signals:
@@ -406,7 +407,6 @@ private slots:
void setPrimaryCategoryCandidates(QMenu* menu, ModInfo::Ptr info);
void addRemoveCategories_MenuHandler(QMenu* menu, int modIndex, const QModelIndex& rowIdx);
- void replaceCategories_MenuHandler(QMenu* menu, int modIndex);
void modInstalled(const QString &modName);
@@ -426,7 +426,6 @@ private slots:
void onRequestsChanged(const APIStats& stats, const APIUserAccount& user);
void deselectFilters();
- void refreshFilters();
void onFiltersCriteria(const std::vector<ModListSortProxy::Criteria>& filters);
void onFiltersOptions(
ModListSortProxy::FilterMode mode, ModListSortProxy::SeparatorsMode sep);
diff --git a/src/modlist.h b/src/modlist.h
index cabd1f32..e4f4dfab 100644
--- a/src/modlist.h
+++ b/src/modlist.h
@@ -131,6 +131,8 @@ public:
void highlightMods(const QItemSelectionModel *selection, const MOShared::DirectoryEntry &directoryEntry);
+public:
+
/**
* @brief Notify the mod list that the given mod has been installed. This is used
* to notify the plugin that registered through onModInstalled().
diff --git a/src/modlistcontextmenu.cpp b/src/modlistcontextmenu.cpp
index df80f0d9..05c2eebf 100644
--- a/src/modlistcontextmenu.cpp
+++ b/src/modlistcontextmenu.cpp
@@ -29,22 +29,126 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, ModListV
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
view->enableAllVisible();
}
- });
+ });
addAction(tr("Disable all visible"), [=]() {
if (QMessageBox::question(view, tr("Confirm"), tr("Really disable all visible mods?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
view->disableAllVisible();
}
- });
+ });
addAction(tr("Check for updates"), [=]() { view->actions().checkModsForUpdates(); });
addAction(tr("Refresh"), &core, &OrganizerCore::profileRefresh);
addAction(tr("Export to csv..."), [=]() { view->actions().exportModListCSV(); });
}
-ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* view) :
+
+ModListChangeCategoryMenu::ModListChangeCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent)
+ : QMenu(tr("Change Categories"), parent)
+{
+ populate(this, categories, mod);
+}
+
+std::vector<std::pair<int, bool>> ModListChangeCategoryMenu::categories() const
+{
+ return categories(this);
+}
+
+std::vector<std::pair<int, bool>> ModListChangeCategoryMenu::categories(const QMenu* menu) const
+{
+ std::vector<std::pair<int, bool>> cats;
+ for (QAction* action : menu->actions()) {
+ if (action->menu() != nullptr) {
+ auto pcats = categories(action->menu());
+ cats.insert(cats.end(), pcats.begin(), pcats.end());
+ }
+ else {
+ QWidgetAction* widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != nullptr) {
+ QCheckBox* checkbox = qobject_cast<QCheckBox*>(widgetAction->defaultWidget());
+ cats.emplace_back(widgetAction->data().toInt(), checkbox->isChecked());
+ }
+ }
+ }
+ return cats;
+}
+
+bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory& factory, ModInfo::Ptr mod, int targetId)
+{
+ const std::set<int>& categories = mod->getCategories();
+
+ bool childEnabled = false;
+
+ for (unsigned int i = 1; i < factory.numCategories(); ++i) {
+ if (factory.getParentID(i) == targetId) {
+ QMenu* targetMenu = menu;
+ if (factory.hasChildren(i)) {
+ targetMenu = menu->addMenu(factory.getCategoryName(i).replace('&', "&&"));
+ }
+
+ int id = factory.getCategoryID(i);
+ QScopedPointer<QCheckBox> checkBox(new QCheckBox(targetMenu));
+ bool enabled = categories.find(id) != categories.end();
+ checkBox->setText(factory.getCategoryName(i).replace('&', "&&"));
+ if (enabled) {
+ childEnabled = true;
+ }
+ checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked);
+
+ QScopedPointer<QWidgetAction> checkableAction(new QWidgetAction(targetMenu));
+ checkableAction->setDefaultWidget(checkBox.take());
+ checkableAction->setData(id);
+ targetMenu->addAction(checkableAction.take());
+
+ if (factory.hasChildren(i)) {
+ if (populate(targetMenu, factory, mod, factory.getCategoryID(i)) || enabled) {
+ targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png"));
+ }
+ }
+ }
+ }
+ return childEnabled;
+}
+
+ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent)
+ : QMenu(tr("Primary Category"), parent)
+{
+ connect(this, &QMenu::aboutToShow, [=]() { populate(categories, mod); });
+}
+
+void ModListPrimaryCategoryMenu::populate(const CategoryFactory& factory, ModInfo::Ptr mod)
+{
+ clear();
+ const std::set<int>& categories = mod->getCategories();
+ for (int categoryID : categories) {
+ int catIdx = factory.getCategoryIndex(categoryID);
+ QWidgetAction* action = new QWidgetAction(this);
+ try {
+ QRadioButton* categoryBox = new QRadioButton(
+ factory.getCategoryName(catIdx).replace('&', "&&"),
+ this);
+ connect(categoryBox, &QRadioButton::toggled, [mod, categoryID](bool enable) {
+ if (enable) {
+ mod->setPrimaryCategory(categoryID);
+ }
+ });
+ categoryBox->setChecked(categoryID == mod->primaryCategory());
+ action->setDefaultWidget(categoryBox);
+ }
+ catch (const std::exception& e) {
+ log::error("failed to create category checkbox: {}", e.what());
+ }
+
+ action->setData(categoryID);
+ addAction(action);
+ }
+}
+
+ModListContextMenu::ModListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* view) :
QMenu(view)
, m_core(core)
- , m_index(index)
+ , m_categories(categories)
+ , m_index(index.model() == view->model() ? view->indexViewToModel(index) : index)
, m_view(view)
, m_actions(view->actions())
{
@@ -55,7 +159,6 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i
m_selected = { index };
}
-
QMenu* allMods = new ModListGlobalContextMenu(core, view, view);
allMods->setTitle(tr("All Mods"));
addMenu(allMods);
@@ -96,6 +199,15 @@ ModListContextMenu::ModListContextMenu(OrganizerCore& core, const QModelIndex& i
}
}
+void ModListContextMenu::addMenuAsPushButton(QMenu* menu)
+{
+ QPushButton* pushBtn = new QPushButton(menu->title());
+ pushBtn->setMenu(menu);
+ QWidgetAction* action = new QWidgetAction(this);
+ action->setDefaultWidget(pushBtn);
+ addAction(action);
+}
+
QMenu* ModListContextMenu::createSendToContextMenu()
{
QMenu* menu = new QMenu(m_view);
@@ -120,7 +232,35 @@ void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod)
void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod)
{
+ addSeparator();
+
+ // categories
+ ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this);
+ connect(categoriesMenu, &QMenu::aboutToHide, [=]() {
+ m_actions.setCategories(m_selected, m_index, categoriesMenu->categories());
+ });
+ addMenuAsPushButton(categoriesMenu);
+ ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this);
+ addMenuAsPushButton(primaryCategoryMenu);
+ addSeparator();
+
+
+ addAction(tr("Rename Separator..."), [=]() { m_actions.renameMod(m_index); });
+ addAction(tr("Remove Separator..."), [=]() { m_actions.removeMods(m_selected); });
+ addSeparator();
+
+ if (m_view->sortColumn() == ModList::COL_PRIORITY) {
+ addMenu(createSendToContextMenu());
+ addSeparator();
+ }
+ addAction(tr("Select Color..."), [=]() { m_actions.setColor(m_selected, m_index); });
+
+ if (mod->color().isValid()) {
+ addAction(tr("Reset Color"), [=]() { m_actions.resetColor(m_selected, m_index); });
+ }
+
+ addSeparator();
}
void ModListContextMenu::addForeignActions(ModInfo::Ptr mod)
diff --git a/src/modlistcontextmenu.h b/src/modlistcontextmenu.h
index 05e6b601..8452bc65 100644
--- a/src/modlistcontextmenu.h
+++ b/src/modlistcontextmenu.h
@@ -9,6 +9,7 @@
#include "modinfo.h"
+class CategoryFactory;
class ModListView;
class ModListViewActions;
class OrganizerCore;
@@ -18,7 +19,47 @@ class ModListGlobalContextMenu : public QMenu
Q_OBJECT
public:
- ModListGlobalContextMenu(OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr);
+ ModListGlobalContextMenu(
+ OrganizerCore& core, ModListView* modListView, QWidget* parent = nullptr);
+
+};
+
+class ModListChangeCategoryMenu : public QMenu
+{
+ Q_OBJECT
+public:
+
+ ModListChangeCategoryMenu(
+ CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr);
+
+ // return a list of pair <category id, enabled> from the menu
+ //
+ std::vector<std::pair<int, bool>> categories() const;
+
+private:
+
+ // populate the tree with the category, using the enabled/disabled state from the
+ // given mod
+ //
+ bool populate(QMenu* menu, CategoryFactory& categories, ModInfo::Ptr mod, int targetId = 0);
+
+ // internal implementation of categories() for recursion
+ //
+ std::vector<std::pair<int, bool>> categories(const QMenu* menu) const;
+};
+
+class ModListPrimaryCategoryMenu : public QMenu
+{
+ Q_OBJECT
+public:
+
+ ModListPrimaryCategoryMenu(CategoryFactory& categories, ModInfo::Ptr mod, QMenu* parent = nullptr);
+
+private:
+
+ // populate the categories
+ //
+ void populate(const CategoryFactory& categories, ModInfo::Ptr mod);
};
@@ -30,7 +71,8 @@ public:
// creates a new context menu, the given index is the one for the click and should be valid
//
- ModListContextMenu(OrganizerCore& core, const QModelIndex& index, ModListView* modListView);
+ ModListContextMenu(
+ const QModelIndex& index, OrganizerCore& core, CategoryFactory& categories, ModListView* modListView);
public: // TODO: Move this to private when all is done
@@ -38,6 +80,11 @@ public: // TODO: Move this to private when all is done
//
QMenu* createSendToContextMenu();
+ // special menu for categories
+ //
+ void addMenuAsPushButton(QMenu* menu);
+
+
// add actions/menus to this menu for each type of mod
//
void addOverwriteActions(ModInfo::Ptr mod);
@@ -47,6 +94,7 @@ public: // TODO: Move this to private when all is done
void addRegularActions(ModInfo::Ptr mod);
OrganizerCore& m_core;
+ CategoryFactory& m_categories;
QModelIndex m_index;
QModelIndexList m_selected;
ModListView* m_view;
diff --git a/src/modlistviewactions.cpp b/src/modlistviewactions.cpp
index 53c29a5d..9a00353f 100644
--- a/src/modlistviewactions.cpp
+++ b/src/modlistviewactions.cpp
@@ -506,6 +506,16 @@ void ModListViewActions::sendModsToSeparator(const QModelIndexList& index) const
}
}
+void ModListViewActions::renameMod(const QModelIndex& index) const
+{
+ try {
+ m_view->edit(index);
+ }
+ catch (const std::exception& e) {
+ reportError(tr("failed to rename mod: %1").arg(e.what()));
+ }
+}
+
void ModListViewActions::removeMods(const QModelIndexList& indices) const
{
const int max_items = 20;
@@ -650,6 +660,94 @@ void ModListViewActions::visitWebPage(const QModelIndexList& indices) const
}
}
+void ModListViewActions::setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const
+{
+ auto& settings = m_core.settings();
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(refIndex.data(ModList::IndexRole).toInt());
+
+ QColorDialog dialog(m_view);
+ dialog.setOption(QColorDialog::ShowAlphaChannel);
+
+ QColor currentColor = modInfo->color();
+ if (currentColor.isValid()) {
+ dialog.setCurrentColor(currentColor);
+ }
+ else if (auto c = settings.colors().previousSeparatorColor()) {
+ dialog.setCurrentColor(*c);
+ }
+
+ if (!dialog.exec())
+ return;
+
+ currentColor = dialog.currentColor();
+ if (!currentColor.isValid())
+ return;
+
+ settings.colors().setPreviousSeparatorColor(currentColor);
+
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ info->setColor(currentColor);
+ }
+
+}
+
+void ModListViewActions::resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const
+{
+ for (auto& idx : indices) {
+ ModInfo::Ptr info = ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt());
+ info->setColor(QColor());
+ }
+ m_core.settings().colors().removePreviousSeparatorColor();
+}
+
+void ModListViewActions::setCategories(ModInfo::Ptr mod, const std::vector<std::pair<int, bool>>& categories) const
+{
+ for (auto& [id, enabled] : categories) {
+ mod->setCategory(id, enabled);
+ }
+}
+
+void ModListViewActions::setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector<std::pair<int, bool>>& categories) const
+{
+ for (auto& [id, enabled] : categories) {
+ if (ref->categorySet(id) != enabled) {
+ mod->setCategory(id, enabled);
+ }
+ }
+}
+
+void ModListViewActions::setCategories(const QModelIndexList& selected, const QModelIndex& ref,
+ const std::vector<std::pair<int, bool>>& categories) const
+{
+ ModInfo::Ptr refMod = ModInfo::getByIndex(ref.data(ModList::IndexRole).toInt());
+ if (selected.size() > 1) {
+
+ for (auto& idx : selected) {
+ if (idx.row() != ref.row()) {
+ setCategoriesIf(
+ ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt()),
+ refMod, categories);
+ }
+ }
+ setCategories(refMod, categories);
+ }
+ else if (!selected.isEmpty()) {
+ // for single mod selections, just do a replace
+ setCategories(refMod, categories);
+ }
+
+ for (auto& idx : selected) {
+ m_core.modList()->notifyChange(idx.data(ModList::IndexRole).toInt());
+ }
+
+ // reset the selection manually - still needed
+ auto viewIndices = m_view->indexModelToView(selected);
+ for (auto& idx : viewIndices) {
+ m_view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ }
+}
+
void ModListViewActions::openExplorer(const QModelIndexList& index) const
{
for (auto& idx : index) {
diff --git a/src/modlistviewactions.h b/src/modlistviewactions.h
index 05994813..33442ce7 100644
--- a/src/modlistviewactions.h
+++ b/src/modlistviewactions.h
@@ -60,13 +60,29 @@ public:
void sendModsToPriority(const QModelIndexList& index) const;
void sendModsToSeparator(const QModelIndexList& index) const;
- // actions for most type of mods
+ // actions for most regular mods
+ //
+ void renameMod(const QModelIndex& index) const;
void removeMods(const QModelIndexList& indices) const;
void ignoreMissingData(const QModelIndexList& indices) const;
void markConverted(const QModelIndexList& indices) const;
void visitOnNexus(const QModelIndexList& indices) const;
void visitWebPage(const QModelIndexList& indices) const;
+ // set/reset color of the given selection, using the given reference index (index
+ // at which the context menu was shown)
+ //
+ void setColor(const QModelIndexList& indices, const QModelIndex& refIndex) const;
+ void resetColor(const QModelIndexList& indices, const QModelIndex& refIndex) const;
+
+ // set the category of the mod in the given list, using the given index as reference
+ // - the categories are set as-is on the refernce mod
+ // - for the other mods, the category is only set if the current state of the category
+ // on the reference is different
+ //
+ void setCategories(const QModelIndexList& selected, const QModelIndex& ref,
+ const std::vector<std::pair<int, bool>>& categories) const;
+
// open the Windows explorer for the specified mods
//
void openExplorer(const QModelIndexList& index) const;
@@ -89,8 +105,19 @@ signals:
private:
+ // move the contents of the overwrite to the given path
+ //
void moveOverwriteContentsTo(const QString& absolutePath) const;
+ // set the category of the given mod based on the given array
+ //
+ void setCategories(ModInfo::Ptr mod, const std::vector<std::pair<int, bool>>& categories) const;
+
+ // set the category of the given mod if the category from the reference mod does not match
+ // the one in the array of categories
+ //
+ void setCategoriesIf(ModInfo::Ptr mod, ModInfo::Ptr ref, const std::vector<std::pair<int, bool>>& categories) const;
+
private:
OrganizerCore& m_core;