Qt: Add custom titles for disc sets

Still questioning the point since all discs including patched will get
lumped into the same disc set... but it's straightforward enough.
This commit is contained in:
Stenzek
2026-01-15 15:30:57 +10:00
parent 09926a3769
commit 3bff452022
6 changed files with 149 additions and 53 deletions

View File

@@ -961,7 +961,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry)
};
const GameDatabase::DiscSetEntry* dsentry = entry->dbentry->disc_set;
OpenChoiceDialog(dsentry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()), false, std::move(options),
OpenChoiceDialog(entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles()), false, std::move(options),
[dsentry](s32 index, const std::string& title, bool checked) mutable {
switch (index)
{
@@ -1009,17 +1009,20 @@ void FullscreenUI::HandleSelectDiscForDiscSet(const GameDatabase::DiscSetEntry*
}
options.emplace_back(FSUI_ICONVSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false);
OpenChoiceDialog(
fmt::format(FSUI_FSTR("Select Disc for {}"), dsentry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles())),
false, std::move(options), [paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (static_cast<u32>(index) >= paths.size())
return;
const GameList::Entry* dsgentry = GameList::GetEntryForPath(dsentry->GetSaveTitle());
const bool localized_titles = GameList::ShouldShowLocalizedTitles();
OpenChoiceDialog(fmt::format(FSUI_FSTR("Select Disc for {}"), dsgentry ? dsgentry->GetDisplayTitle(localized_titles) :
dsentry->GetDisplayTitle(localized_titles)),
false, std::move(options),
[paths = std::move(paths)](s32 index, const std::string& title, bool checked) {
if (static_cast<u32>(index) >= paths.size())
return;
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(paths[index]);
if (entry)
HandleGameListActivate(entry);
});
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(paths[index]);
if (entry)
HandleGameListActivate(entry);
});
}
void FullscreenUI::SwitchToGameList()

View File

@@ -123,7 +123,8 @@ static void ScanFile(std::string path, std::time_t timestamp, std::unique_lock<s
static bool LoadOrInitializeCache(std::FILE* fp, bool invalidate_cache);
static bool LoadEntriesFromCache(BinaryFileReader& reader);
static bool WriteEntryToCache(const Entry* entry, const std::string& entry_path, BinaryFileWriter& writer);
static void CreateDiscSetEntries(const std::vector<std::string>& excluded_paths, const PlayedTimeMap& played_time_map);
static void CreateDiscSetEntries(const std::vector<std::string>& excluded_paths, const PlayedTimeMap& played_time_map,
const INISettingsInterface& custom_attributes_ini);
static std::string GetPlayedTimePath();
static bool ParsePlayedTimeLine(char* line, std::string_view& serial, PlayedTimeEntry& entry);
@@ -1118,7 +1119,7 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
s_state.cache_map.clear();
// merge multi-disc games
CreateDiscSetEntries(excluded_paths, played_time);
CreateDiscSetEntries(excluded_paths, played_time, custom_attributes_ini);
}
GameList::EntryList GameList::TakeEntryList()
@@ -1129,7 +1130,8 @@ GameList::EntryList GameList::TakeEntryList()
}
void GameList::CreateDiscSetEntries(const std::vector<std::string>& excluded_paths,
const PlayedTimeMap& played_time_map)
const PlayedTimeMap& played_time_map,
const INISettingsInterface& custom_attributes_ini)
{
std::unique_lock lock(s_state.mutex);
@@ -1213,6 +1215,7 @@ void GameList::CreateDiscSetEntries(const std::vector<std::string>& excluded_pat
}
DEV_LOG("Created disc set {} from {} entries", dsentry->title, num_parts);
ApplyCustomAttributes(set_entry.path, &set_entry, custom_attributes_ini);
// we have to do the exclusion check at the end, because otherwise the individual discs get added
if (!IsPathExcluded(excluded_paths, dsentry->title))
@@ -1897,23 +1900,31 @@ bool GameList::SaveCustomTitleForPath(const std::string& path, const std::string
if (!PutCustomPropertiesField(custom_attributes_ini, path, "Title", custom_title.c_str()))
return false;
if (!custom_title.empty())
// Can skip the rescan and just update the value directly.
auto lock = GetLock();
Entry* entry = GetMutableEntryForPath(path);
if (entry)
{
// Can skip the rescan and just update the value directly.
auto lock = GetLock();
Entry* entry = GetMutableEntryForPath(path);
if (entry)
if (!custom_title.empty())
{
entry->title = custom_title;
entry->has_custom_title = true;
return true;
}
else
{
// Can pull it from the db? Applies to both discs + disc sets.
if (entry->dbentry)
{
entry->title = {};
entry->has_custom_title = false;
return true;
}
}
}
else
{
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
}
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
return true;
}

View File

@@ -16,6 +16,7 @@
#include "util/translation.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/string_util.h"
@@ -79,7 +80,18 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo
m_ui.title->setModified(false);
}
});
connect(m_ui.restoreTitle, &QAbstractButton::clicked, this, [this]() { setCustomTitle(std::string()); });
connect(m_ui.restoreTitle, &QAbstractButton::clicked, this, [this]() { setCustomTitle({}); });
if (m_ui.discSetTitle)
{
connect(m_ui.discSetTitle, &QLineEdit::editingFinished, this, [this]() {
if (m_ui.discSetTitle->isModified())
{
setCustomDiscSetTitle(m_ui.discSetTitle->text().toStdString());
m_ui.discSetTitle->setModified(false);
}
});
connect(m_ui.restoreDiscSetTitle, &QAbstractButton::clicked, this, [this]() { setCustomDiscSetTitle({}); });
}
connect(m_ui.region, &QComboBox::currentIndexChanged, this, [this](int index) { setCustomRegion(index); });
connect(m_ui.restoreRegion, &QAbstractButton::clicked, this, [this]() { setCustomRegion(-1); });
connect(m_ui.customLanguage, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onCustomLanguageChanged);
@@ -135,6 +147,26 @@ void GameSummaryWidget::populateUi(const GameList::Entry* entry)
if (entry->IsDiscSet())
m_ui.customLanguage->setEnabled(false);
// Need to look up the entry for the disc set itself
Assert(!entry->disc_set_member || entry->GetDiscSetEntry());
const GameList::Entry* disc_set_entry =
entry->disc_set_member ? GameList::GetEntryForPath(entry->GetDiscSetEntry()->GetSaveTitle()) : nullptr;
if (disc_set_entry)
{
m_ui.discSetTitle->setText(
QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles())));
m_ui.restoreDiscSetTitle->setEnabled(disc_set_entry->has_custom_title);
}
else
{
m_ui.mainLayout->removeWidget(m_ui.discSetTitleLabel);
QtUtils::SafeDeleteWidget(m_ui.discSetTitleLabel);
m_ui.mainLayout->removeWidget(m_ui.discSetTitle);
QtUtils::SafeDeleteWidget(m_ui.discSetTitle);
m_ui.mainLayout->removeWidget(m_ui.restoreDiscSetTitle);
QtUtils::SafeDeleteWidget(m_ui.restoreDiscSetTitle);
}
if (const GameDatabase::Entry* dbentry = entry->dbentry)
{
m_ui.compatibility->setCurrentIndex(static_cast<int>(dbentry->compatibility));
@@ -278,6 +310,30 @@ void GameSummaryWidget::setCustomTitle(const std::string& text)
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(m_path, GameListModel::Column_Title);
}
void GameSummaryWidget::setCustomDiscSetTitle(const std::string& text)
{
const auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(m_path);
if (!entry->disc_set_member)
return;
const GameList::Entry* disc_set_entry =
entry->disc_set_member ? GameList::GetEntryForPath(entry->GetDiscSetEntry()->GetSaveTitle()) : nullptr;
if (!disc_set_entry)
return;
GameList::SaveCustomTitleForPath(disc_set_entry->path, text);
const QSignalBlocker sb(m_ui.title);
m_ui.discSetTitle->setText(
QtUtils::StringViewToQString(disc_set_entry->GetDisplayTitle(GameList::ShouldShowLocalizedTitles())));
m_ui.restoreDiscSetTitle->setEnabled(disc_set_entry->has_custom_title);
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(disc_set_entry->path,
GameListModel::Column_Title);
}
void GameSummaryWidget::setCustomRegion(int region)
{
GameList::SaveCustomRegionForPath(m_path, (region >= 0) ? std::optional<DiscRegion>(static_cast<DiscRegion>(region)) :

View File

@@ -33,6 +33,7 @@ public:
private:
void populateUi(const GameList::Entry* entry);
void setCustomTitle(const std::string& text);
void setCustomDiscSetTitle(const std::string& text);
void setCustomRegion(int region);
void populateTracksInfo();

View File

@@ -10,7 +10,7 @@
<height>562</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<layout class="QGridLayout" name="mainLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -61,27 +61,50 @@
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="discSetTitleLabel">
<property name="text">
<string>Disc Set Title:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="discSetTitle"/>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="restoreDiscSetTitle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Restore Disc Set Title</string>
</property>
<property name="icon">
<iconset theme="restart-line"/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Serial:</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="serial">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<item row="4" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,0">
<item>
<widget class="QComboBox" name="entryType">
@@ -102,14 +125,14 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -119,7 +142,7 @@
</property>
</widget>
</item>
<item row="4" column="2">
<item row="5" column="2">
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
@@ -132,14 +155,14 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="6" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Languages:</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<item row="6" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<item>
<widget class="QLineEdit" name="languages">
@@ -153,91 +176,91 @@
</item>
</layout>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QComboBox" name="compatibility">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="8" column="2">
<widget class="QPushButton" name="compatibilityComments">
<property name="icon">
<iconset theme="information-line"/>
</property>
</widget>
</item>
<item row="7" column="0">
<item row="8" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Genre:</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="2">
<item row="8" column="1" colspan="2">
<widget class="QLineEdit" name="genre">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<item row="9" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Developer:</string>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2">
<item row="9" column="1" colspan="2">
<widget class="QLineEdit" name="developer">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0">
<item row="10" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Release Info:</string>
</property>
</widget>
</item>
<item row="9" column="1" colspan="2">
<item row="10" column="1" colspan="2">
<widget class="QLineEdit" name="releaseInfo">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controllers:</string>
</property>
</widget>
</item>
<item row="10" column="1" colspan="2">
<item row="11" column="1" colspan="2">
<widget class="QLineEdit" name="controllers">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="11" column="0">
<item row="12" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Controller Preset:</string>
</property>
</widget>
</item>
<item row="11" column="1" colspan="2">
<item row="12" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
<widget class="QComboBox" name="inputProfile"/>
@@ -251,14 +274,14 @@
</item>
</layout>
</item>
<item row="12" column="0">
<item row="13" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="12" column="1" colspan="2">
<item row="13" column="1" colspan="2">
<layout class="QHBoxLayout" name="verifyLayout" stretch="1,0">
<item>
<widget class="QLineEdit" name="revision">
@@ -282,7 +305,7 @@
</item>
</layout>
</item>
<item row="13" column="0" colspan="3">
<item row="14" column="0" colspan="3">
<widget class="QTreeWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
@@ -328,7 +351,7 @@
</column>
</widget>
</item>
<item row="14" column="0" colspan="3">
<item row="15" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>

View File

@@ -83,6 +83,8 @@ void SelectDiscDialog::populateList(const GameDatabase::DiscSetEntry* dsentry, b
}
}
setWindowTitle(
tr("Select Disc for %1").arg(QtUtils::StringViewToQString(dsentry->GetDisplayTitle(localized_titles))));
const GameList::Entry* dsgentry = GameList::GetEntryForPath(dsentry->GetSaveTitle());
setWindowTitle(tr("Select Disc for %1")
.arg(QtUtils::StringViewToQString(dsgentry ? dsgentry->GetDisplayTitle(localized_titles) :
dsentry->GetDisplayTitle(localized_titles))));
}