diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 7a1e8cab4c..4de8b66eb8 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -201,6 +201,7 @@ void SConfig::SaveGameListSettings(IniFile& ini) gamelist->Set("ColumnID", m_showIDColumn); gamelist->Set("ColumnRegion", m_showRegionColumn); gamelist->Set("ColumnSize", m_showSizeColumn); + gamelist->Set("ColumnTags", m_showTagsColumn); } void SConfig::SaveCoreSettings(IniFile& ini) @@ -475,6 +476,7 @@ void SConfig::LoadGameListSettings(IniFile& ini) gamelist->Get("ColumnID", &m_showIDColumn, false); gamelist->Get("ColumnRegion", &m_showRegionColumn, true); gamelist->Get("ColumnSize", &m_showSizeColumn, true); + gamelist->Get("ColumnTags", &m_showTagsColumn, false); } void SConfig::LoadCoreSettings(IniFile& ini) diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index f39c9738c6..2aae8c31d1 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -270,6 +270,7 @@ struct SConfig bool m_showIDColumn; bool m_showRegionColumn; bool m_showSizeColumn; + bool m_showTagsColumn; std::string m_WirelessMac; bool m_PauseMovie; diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 847997c5dd..6dbe3e1211 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,7 @@ void GameList::MakeListView() hor_header->setSectionResizeMode(GameListModel::COL_COUNTRY, QHeaderView::Fixed); hor_header->setSectionResizeMode(GameListModel::COL_SIZE, QHeaderView::Fixed); hor_header->setSectionResizeMode(GameListModel::COL_FILE_NAME, QHeaderView::Interactive); + hor_header->setSectionResizeMode(GameListModel::COL_TAGS, QHeaderView::Interactive); // There's some odd platform-specific behavior with default minimum section size hor_header->setMinimumSectionSize(38); @@ -174,6 +176,7 @@ void GameList::UpdateColumnVisibility() m_list->setColumnHidden(GameListModel::COL_SIZE, !SConfig::GetInstance().m_showSizeColumn); m_list->setColumnHidden(GameListModel::COL_FILE_NAME, !SConfig::GetInstance().m_showFileNameColumn); + m_list->setColumnHidden(GameListModel::COL_TAGS, !SConfig::GetInstance().m_showTagsColumn); } void GameList::MakeEmptyView() @@ -336,6 +339,35 @@ void GameList::ShowContextMenu(const QPoint&) menu->addAction(tr("Open &containing folder"), this, &GameList::OpenContainingFolder); menu->addAction(tr("Delete File..."), this, &GameList::DeleteFile); + menu->addSeparator(); + + auto* model = Settings::Instance().GetGameListModel(); + + auto* tags_menu = menu->addMenu(tr("Tags")); + + auto path = game->GetFilePath(); + auto game_tags = model->GetGameTags(path); + + for (const auto& tag : model->GetAllTags()) + { + auto* tag_action = tags_menu->addAction(tag); + + tag_action->setCheckable(true); + tag_action->setChecked(game_tags.contains(tag)); + + connect(tag_action, &QAction::toggled, this, [this, path, tag, model](bool checked) { + if (!checked) + model->RemoveGameTag(path, tag); + else + model->AddGameTag(path, tag); + }); + } + + menu->addAction(tr("New Tag..."), this, &GameList::NewTag); + menu->addAction(tr("Remove Tag..."), this, &GameList::DeleteTag); + + menu->addSeparator(); + QAction* netplay_host = new QAction(tr("Host with NetPlay"), menu); connect(netplay_host, &QAction::triggered, [this, game] { @@ -742,7 +774,8 @@ void GameList::OnColumnVisibilityToggled(const QString& row, bool visible) {tr("File Name"), GameListModel::COL_FILE_NAME}, {tr("Game ID"), GameListModel::COL_ID}, {tr("Region"), GameListModel::COL_COUNTRY}, - {tr("File Size"), GameListModel::COL_SIZE}}; + {tr("File Size"), GameListModel::COL_SIZE}, + {tr("Tags"), GameListModel::COL_TAGS}}; m_list->setColumnHidden(rowname_to_col_index[row], !visible); } @@ -859,6 +892,26 @@ void GameList::OnHeaderViewChanged() block = false; } +void GameList::NewTag() +{ + auto tag = QInputDialog::getText(this, tr("New tag"), tr("Name for a new tag:")); + + if (tag.isEmpty()) + return; + + Settings::Instance().GetGameListModel()->NewTag(tag); +} + +void GameList::DeleteTag() +{ + auto tag = QInputDialog::getText(this, tr("Remove tag"), tr("Name of the tag to remove:")); + + if (tag.isEmpty()) + return; + + Settings::Instance().GetGameListModel()->DeleteTag(tag); +} + void GameList::SetSearchTerm(const QString& term) { m_model->SetSearchTerm(term); diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index 2d42c7cbc3..dd2635ea4b 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -60,6 +60,8 @@ private: void ExportWiiSave(); void CompressISO(bool decompress); void ChangeDisc(); + void NewTag(); + void DeleteTag(); void UpdateColumnVisibility(); void ZoomIn(); diff --git a/Source/Core/DolphinQt/GameList/GameListModel.cpp b/Source/Core/DolphinQt/GameList/GameListModel.cpp index 890140340d..17cd5a6334 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.cpp +++ b/Source/Core/DolphinQt/GameList/GameListModel.cpp @@ -42,6 +42,11 @@ GameListModel::GameListModel(QObject* parent) : QAbstractTableModel(parent) emit layoutAboutToBeChanged(); emit layoutChanged(); }); + + auto& settings = Settings::GetQSettings(); + + m_tag_list = settings.value(QStringLiteral("gamelist/tags")).toStringList(); + m_game_tags = settings.value(QStringLiteral("gamelist/game_tags")).toMap(); } QVariant GameListModel::data(const QModelIndex& index, int role) const @@ -117,6 +122,14 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const if (role == Qt::InitialSortOrderRole) return static_cast(game.GetFileSize()); break; + case COL_TAGS: + if (role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) + { + auto tags = GetGameTags(game.GetFilePath()); + tags.sort(); + + return tags.join(QStringLiteral(", ")); + } } return QVariant(); @@ -143,6 +156,8 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int return tr("File Name"); case COL_SIZE: return tr("Size"); + case COL_TAGS: + return tr("Tags"); } return QVariant(); } @@ -293,3 +308,57 @@ float GameListModel::GetScale() const { return m_scale; } + +const QStringList& GameListModel::GetAllTags() const +{ + return m_tag_list; +} + +const QStringList GameListModel::GetGameTags(const std::string& path) const +{ + return m_game_tags[QString::fromStdString(path)].toStringList(); +} + +void GameListModel::AddGameTag(const std::string& path, const QString& name) +{ + auto tags = GetGameTags(path); + + if (tags.contains(name)) + return; + + tags << name; + + m_game_tags[QString::fromStdString(path)] = tags; + Settings::GetQSettings().setValue(QStringLiteral("gamelist/game_tags"), m_game_tags); +} + +void GameListModel::RemoveGameTag(const std::string& path, const QString& name) +{ + auto tags = GetGameTags(path); + + tags.removeAll(name); + + m_game_tags[QString::fromStdString(path)] = tags; + + Settings::GetQSettings().setValue(QStringLiteral("gamelist/game_tags"), m_game_tags); +} + +void GameListModel::NewTag(const QString& name) +{ + if (m_tag_list.contains(name)) + return; + + m_tag_list << name; + + Settings::GetQSettings().setValue(QStringLiteral("gamelist/tags"), m_tag_list); +} + +void GameListModel::DeleteTag(const QString& name) +{ + m_tag_list.removeAll(name); + + for (const auto& file : m_game_tags.keys()) + RemoveGameTag(file.toStdString(), name); + + Settings::GetQSettings().setValue(QStringLiteral("gamelist/tags"), m_tag_list); +} diff --git a/Source/Core/DolphinQt/GameList/GameListModel.h b/Source/Core/DolphinQt/GameList/GameListModel.h index 99cc28e080..9b7d06a421 100644 --- a/Source/Core/DolphinQt/GameList/GameListModel.h +++ b/Source/Core/DolphinQt/GameList/GameListModel.h @@ -8,7 +8,10 @@ #include #include +#include #include +#include +#include #include "Core/TitleDatabase.h" @@ -52,6 +55,7 @@ public: COL_COUNTRY, COL_SIZE, COL_FILE_NAME, + COL_TAGS, NUM_COLS }; @@ -62,10 +66,22 @@ public: void SetScale(float scale); float GetScale() const; + const QStringList& GetAllTags() const; + const QStringList GetGameTags(const std::string& path) const; + + void AddGameTag(const std::string& path, const QString& name); + void RemoveGameTag(const std::string& path, const QString& name); + + void NewTag(const QString& name); + void DeleteTag(const QString& name); + private: // Index in m_games, or -1 if it isn't found int FindGame(const std::string& path) const; + QStringList m_tag_list; + QMap m_game_tags; + GameTracker m_tracker; QList> m_games; Core::TitleDatabase m_title_database; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 2dbb83a936..b2b98c1019 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -570,7 +570,8 @@ void MenuBar::AddListColumnsMenu(QMenu* view_menu) {tr("File Name"), &SConfig::GetInstance().m_showFileNameColumn}, {tr("Game ID"), &SConfig::GetInstance().m_showIDColumn}, {tr("Region"), &SConfig::GetInstance().m_showRegionColumn}, - {tr("File Size"), &SConfig::GetInstance().m_showSizeColumn}}; + {tr("File Size"), &SConfig::GetInstance().m_showSizeColumn}, + {tr("Tags"), &SConfig::GetInstance().m_showTagsColumn}}; QActionGroup* column_group = new QActionGroup(this); QMenu* cols_menu = view_menu->addMenu(tr("List Columns"));