Skip to content

Commit 010bf17

Browse files
authored
Improve Game Collections (#19349)
Follow up of #19292 to improve Game Collections in context menu: - Add and remove the game to/from multiple collections (e.g. "3rd person" and "Fantasy") - Create a collection and add the game to it - Remove the game from current collection if it is not the "All games"
1 parent 01c59b9 commit 010bf17

5 files changed

Lines changed: 93 additions & 105 deletions

File tree

rpcs3/rpcs3qt/game_list_actions.cpp

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,13 @@ void game_list_actions::ShowDiskUsageDialog()
608608
});
609609
}
610610

611+
// How a game collection is listed in either menu: its name, and how many of its games the game list is
612+
// showing. One pass: a collection may be named "%1", and a chained arg() would substitute into it.
613+
static QString collection_entry_text(const QString& name, qsizetype count)
614+
{
615+
return QString("%0 (%1)").arg(gui::utils::escape_mnemonics(name), QString::number(count));
616+
}
617+
611618
void game_list_actions::UpdateGameCollectionMenu(QMenu* menu, QActionGroup* act_group, QMenu* rename_menu, QMenu* remove_menu)
612619
{
613620
const QStringList collections = m_gui_settings->GetGameCollections();
@@ -664,20 +671,25 @@ void game_list_actions::UpdateGameCollectionMenu(QMenu* menu, QActionGroup* act_
664671

665672
for (const QString& name : collections)
666673
{
667-
// One pass: a collection may be named "%1", and a chained arg() would substitute into it.
668-
add_entry(QString("%0 (%1)").arg(gui::utils::escape_mnemonics(name),
669-
QString::number(counts.value(name))), name);
674+
add_entry(collection_entry_text(name, counts.value(name)), name);
670675
}
671676
}
672677

673-
void game_list_actions::CreateGameCollection()
678+
void game_list_actions::CreateGameCollection(const QSet<QString>& serials)
674679
{
675680
const QString name = AskForCollectionName(tr("Create Game Collection"), {},
676681
[this](const QString& to) { return m_gui_settings->AddGameCollection(to); });
677682

678-
if (!name.isEmpty())
683+
if (name.isEmpty())
684+
{
685+
return;
686+
}
687+
688+
game_list_log.notice("Created game collection '%s'", name);
689+
690+
if (!serials.isEmpty())
679691
{
680-
game_list_log.notice("Created game collection '%s'", name);
692+
ChangeCollectionMembership(serials, name, true);
681693
}
682694
}
683695

@@ -732,7 +744,7 @@ void game_list_actions::SelectGameCollection(const QString& name)
732744
m_game_list_frame->SetGameCollection(name);
733745
}
734746

735-
void game_list_actions::AddMoveToCollectionMenu(QMenu* parent, const std::vector<game_info>& games)
747+
void game_list_actions::AddCollectionMenu(QMenu* parent, const std::vector<game_info>& games)
736748
{
737749
QSet<QString> serials;
738750

@@ -750,51 +762,47 @@ void game_list_actions::AddMoveToCollectionMenu(QMenu* parent, const std::vector
750762
}
751763

752764
const QStringList collections = m_gui_settings->GetGameCollections();
765+
const QHash<QString, qsizetype> counts = m_game_list_frame->CountGamesPerCollection(collections);
753766

754-
QMenu* collection_menu = parent->addMenu(tr("&Move To Collection"));
755-
collection_menu->setEnabled(!collections.isEmpty());
767+
QMenu* collection_menu = parent->addMenu(tr("&Add to Collection"));
756768

757-
if (collections.isEmpty())
769+
// Always there: with no collection yet this is the only thing the submenu can offer
770+
connect(collection_menu->addAction(tr("&Create and Add")), &QAction::triggered, this, [this, serials]()
758771
{
759-
return;
760-
}
761-
762-
// A single game shows the collection it currently sits in. A multi selection is a bulk move to whatever
763-
// entry is picked, no matter which collections the games come from, so it shows no state at all.
764-
const bool is_single_game = serials.size() == 1;
765-
const QString current = is_single_game ? m_gui_settings->GetCollectionOfGame(*serials.cbegin()) : QString();
772+
CreateGameCollection(serials);
773+
});
766774

767-
QActionGroup* collection_act_group = is_single_game ? new QActionGroup(collection_menu) : nullptr;
775+
if (!collections.isEmpty())
776+
{
777+
collection_menu->addSeparator();
778+
}
768779

769-
const auto add_entry = [&](const QString& text, const QString& name)
780+
for (const QString& collection : collections)
770781
{
771-
QAction* act = collection_menu->addAction(text);
782+
// A multi selection is ticked only once every game in it belongs to the collection, so that the
783+
// entry finishes adding the ones that are missing before it starts taking any out
784+
const bool is_member = m_gui_settings->GetGamesInCollection(collection).contains(serials);
772785

773-
if (collection_act_group)
774-
{
775-
act->setCheckable(true);
776-
act->setChecked(name == current);
777-
collection_act_group->addAction(act);
778-
}
786+
QAction* act = collection_menu->addAction(collection_entry_text(collection, counts.value(collection)));
787+
act->setCheckable(true);
788+
act->setChecked(is_member);
779789

780-
// Moving one game is trivially undone. Moving a block is not: the games can come from several
781-
// collections at once and where each of them was is not recorded anywhere, so that one asks first.
782-
connect(act, &QAction::triggered, this, [this, serials, name, is_single_game]()
790+
connect(act, &QAction::triggered, this, [this, serials, collection, is_member]()
783791
{
784-
MoveGamesToCollection(serials, name, !is_single_game);
792+
ChangeCollectionMembership(serials, collection, !is_member);
785793
});
786-
};
787-
788-
const QHash<QString, qsizetype> counts = m_game_list_frame->CountGamesPerCollection(collections);
794+
}
789795

790-
add_entry(gui_settings::GetAllGamesCollectionLabel(), {});
796+
const QString current = m_gui_settings->GetCurrentGameCollection();
791797

792-
for (const QString& collection : collections)
798+
// Acts on the collection the game list is filtered by, which is the one the user is looking at
799+
if (!current.isEmpty() && m_gui_settings->GetGamesInCollection(current).intersects(serials))
793800
{
794-
// One pass: a collection may be named "%1", and a chained arg() would substitute into it.
795-
const QString text = QString("%0 (%1)").arg(gui::utils::escape_mnemonics(collection),
796-
QString::number(counts.value(collection)));
797-
add_entry(text, collection);
801+
connect(parent->addAction(tr("&Remove from Collection '%0'").arg(gui::utils::escape_mnemonics(current))),
802+
&QAction::triggered, this, [this, serials, current]()
803+
{
804+
ChangeCollectionMembership(serials, current, false);
805+
});
798806
}
799807
}
800808

@@ -851,39 +859,40 @@ QString game_list_actions::AskForCollectionName(const QString& title, const QStr
851859
return {};
852860
}
853861

854-
void game_list_actions::MoveGamesToCollection(const QSet<QString>& serials, const QString& name, bool is_interactive)
862+
void game_list_actions::ChangeCollectionMembership(const QSet<QString>& serials, const QString& name, bool add)
855863
{
856-
const bool moves_anything = !m_gui_settings->GetGamesInCollection(name).contains(serials);
857-
858-
if (is_interactive && moves_anything)
864+
// Adding is undone by the very entry that did it, but a removal takes memberships that were given one
865+
// game at a time and cannot be handed back the same way, so a block of them asks first
866+
if (!add && serials.size() > 1)
859867
{
860-
const QString question = name.isEmpty()
861-
? tr("Remove %Ln game(s) from their game collection?", "", static_cast<int>(serials.size()))
862-
: tr("Move %Ln game(s) to the '%0' game collection?", "", static_cast<int>(serials.size())).arg(name);
868+
const QString question = tr("Remove %Ln game(s) from the '%0' game collection?", "",
869+
static_cast<int>(serials.size())).arg(name);
863870

864-
if (gui::utils::plain_message(m_game_list_frame, QMessageBox::Question, tr("Confirm Move"), question,
871+
if (gui::utils::plain_message(m_game_list_frame, QMessageBox::Question, tr("Confirm Removal"), question,
865872
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
866873
{
867874
return;
868875
}
869876
}
870877

871-
// The user picked the collection the games already sit in
872-
if (!m_gui_settings->MoveGamesToCollection(serials, name))
878+
const bool changed = m_gui_settings->SetGameCollectionMembership(serials, name, add);
879+
880+
// The menu was built before the collection changed under it
881+
if (!changed)
873882
{
874883
return;
875884
}
876885

877-
if (name.isEmpty())
886+
if (add)
878887
{
879-
game_list_log.notice("Removed %d game(s) from their game collection", serials.size());
888+
game_list_log.notice("Added %d game(s) to game collection '%s'", serials.size(), name);
880889
}
881890
else
882891
{
883-
game_list_log.notice("Moved %d game(s) to game collection '%s'", serials.size(), name);
892+
game_list_log.notice("Removed %d game(s) from game collection '%s'", serials.size(), name);
884893
}
885894

886-
// The moved games may have entered or left the collection the game list is filtered by
895+
// The games may have entered or left the collection the game list is filtered by
887896
m_game_list_frame->ReloadGameCollection();
888897
}
889898

rpcs3/rpcs3qt/game_list_actions.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ class game_list_actions : QObject
6666
// submenus belong to the Manage menu, and are null for the View one, which carries neither.
6767
void UpdateGameCollectionMenu(QMenu* menu, QActionGroup* act_group, QMenu* rename_menu, QMenu* remove_menu);
6868

69-
// Asks for a name until it is accepted or the dialog is dismissed
70-
void CreateGameCollection();
69+
// Asks for a name until it is accepted or the dialog is dismissed, then adds the given games to what
70+
// was created
71+
void CreateGameCollection(const QSet<QString>& serials = {});
7172

7273
// Asks for a new name for a game collection until it is accepted or the dialog is dismissed
7374
void RenameGameCollection(const QString& name);
@@ -78,8 +79,10 @@ class game_list_actions : QObject
7879
// Makes a game collection the one the game list is filtered by. An empty name shows every game.
7980
void SelectGameCollection(const QString& name);
8081

81-
// Appends the "Move To Collection" submenu, which moves the given games to a user defined game collection
82-
void AddMoveToCollectionMenu(QMenu* parent, const std::vector<game_info>& games);
82+
// Appends the "Add to Collection" submenu, which puts the given games in a user defined game collection
83+
// or takes them out of it, and the entry that removes them from the collection the game list is
84+
// filtered by
85+
void AddCollectionMenu(QMenu* parent, const std::vector<game_info>& games);
8386

8487
// NOTES:
8588
// - SetContentList() MUST always be called to set the content's info to be removed by:
@@ -140,8 +143,8 @@ class game_list_actions : QObject
140143
QString AskForCollectionName(const QString& title, const QString& initial,
141144
const std::function<bool(const QString&)>& accept);
142145

143-
// Performs what an entry of the "Move To Collection" submenu does. Confirms first when interactive.
144-
void MoveGamesToCollection(const QSet<QString>& serials, const QString& name, bool is_interactive);
146+
// Puts the given games in a game collection or takes them out of it, then refreshes the game list
147+
void ChangeCollectionMembership(const QSet<QString>& serials, const QString& name, bool add);
145148

146149
void BatchActionBySerials(progress_dialog* pdlg, const std::set<std::string>& serials,
147150
QString progressLabel, std::function<bool(const std::string&)> action,

rpcs3/rpcs3qt/game_list_context_menu.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,8 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
326326

327327
manage_game_menu->addSeparator();
328328

329-
// Move the game to a user defined game collection
330-
m_game_list_actions->AddMoveToCollectionMenu(manage_game_menu, {gameinfo});
329+
// Add the game to user defined game collections
330+
m_game_list_actions->AddCollectionMenu(manage_game_menu, {gameinfo});
331331

332332
// Hide/rename game in game list
333333
QAction* hide_hidden_serial = manage_game_menu->addAction(tr("&Hide Game In Game List"));
@@ -965,8 +965,8 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
965965

966966
manage_game_menu->addSeparator();
967967

968-
// Move the games to a user defined game collection
969-
m_game_list_actions->AddMoveToCollectionMenu(manage_game_menu, games);
968+
// Add the games to user defined game collections
969+
m_game_list_actions->AddCollectionMenu(manage_game_menu, games);
970970

971971
// Hide game in game list
972972
QAction* hide_hidden_serial = manage_game_menu->addAction(tr("&Hide Game In Game List"));

rpcs3/rpcs3qt/gui_settings.cpp

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -306,19 +306,6 @@ QSet<QString> gui_settings::GetGamesInCollection(const QString& name) const
306306
return gui::utils::list_to_set(GetValue(gui::game_collection, gc_games_prefix + name, QStringList()).toStringList());
307307
}
308308

309-
QString gui_settings::GetCollectionOfGame(const QString& serial) const
310-
{
311-
for (const QString& collection : GetGameCollections())
312-
{
313-
if (GetGamesInCollection(collection).contains(serial))
314-
{
315-
return collection;
316-
}
317-
}
318-
319-
return {};
320-
}
321-
322309
bool gui_settings::CleanupCollections(const QSet<QString>& serials) const
323310
{
324311
const QStringList& collections = GetGameCollections();
@@ -352,42 +339,34 @@ bool gui_settings::CleanupCollections(const QSet<QString>& serials) const
352339
return changed;
353340
}
354341

355-
bool gui_settings::MoveGamesToCollection(const QSet<QString>& serials, const QString& name) const
342+
bool gui_settings::SetGameCollectionMembership(const QSet<QString>& serials, const QString& name, bool add) const
356343
{
357-
const QStringList collections = GetGameCollections();
358-
359-
if (serials.isEmpty() || (!name.isEmpty() && !collections.contains(name)))
344+
if (serials.isEmpty() || !GetGameCollections().contains(name))
360345
{
361346
return false;
362347
}
363348

364-
bool changed = false;
349+
QSet<QString> games = GetGamesInCollection(name);
350+
const qsizetype old_size = games.size();
365351

366-
for (const QString& collection : collections)
352+
if (add)
353+
{
354+
games.unite(serials);
355+
}
356+
else
367357
{
368-
const QSet<QString> old_games = GetGamesInCollection(collection);
369-
QSet<QString> games = old_games;
370-
371358
games.subtract(serials);
372-
373-
if (collection == name)
374-
{
375-
games.unite(serials);
376-
}
377-
378-
if (games != old_games)
379-
{
380-
SetGamesInCollection(collection, games);
381-
changed = true;
382-
}
383359
}
384360

385-
if (changed)
361+
// unite only adds and subtract only removes, so a size that did not move is a set that did not either
362+
if (games.size() == old_size)
386363
{
387-
sync();
364+
return false;
388365
}
389366

390-
return changed;
367+
SetGamesInCollection(name, games);
368+
sync();
369+
return true;
391370
}
392371

393372
QString gui_settings::GetAllGamesCollectionLabel()

rpcs3/rpcs3qt/gui_settings.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,16 +378,13 @@ class gui_settings : public settings
378378
void SetCurrentGameCollection(const QString& name, bool sync = true) const;
379379
QSet<QString> GetGamesInCollection(const QString& name) const;
380380

381-
/** Returns the game collection the given game belongs to, empty if it belongs to none */
382-
QString GetCollectionOfGame(const QString& serial) const;
383-
384381
/** Cleans up all game collections according to the provided serials.
385382
Returns whether anything actually changed. */
386383
bool CleanupCollections(const QSet<QString>& serials) const;
387384

388-
/** Moves the given games to a game collection, removing them from any other one. An empty name only
389-
removes them. Returns whether anything actually changed. */
390-
bool MoveGamesToCollection(const QSet<QString>& serials, const QString& name) const;
385+
/** Puts the given games in a game collection or takes them out of it, leaving every collection they
386+
already belong to alone. Returns whether anything actually changed. */
387+
bool SetGameCollectionMembership(const QSet<QString>& serials, const QString& name, bool add) const;
391388

392389
/** Label of the default game collection entry. Reserved, so it can never name a real collection. */
393390
static QString GetAllGamesCollectionLabel();

0 commit comments

Comments
 (0)