diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 0878051c5..4af1d6297 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -11,6 +11,7 @@ #include "util/cd_image.h" #include "util/http_downloader.h" +#include "util/ini_settings_interface.h" #include "common/assert.h" #include "common/byte_stream.h" @@ -69,13 +70,19 @@ static bool GetExeListEntry(const std::string& path, Entry* entry); static bool GetPsfListEntry(const std::string& path, Entry* entry); static bool GetDiscListEntry(const std::string& path, Entry* entry); -static bool GetGameListEntryFromCache(const std::string& path, Entry* entry); +static void ApplyCustomAttributes(const std::string& path, Entry* entry, + const INISettingsInterface& custom_attributes_ini); +static bool RescanCustomAttributesForPath(const std::string& path, const INISettingsInterface& custom_attributes_ini); +static bool GetGameListEntryFromCache(const std::string& path, Entry* entry, + const INISettingsInterface& custom_attributes_ini); +static Entry* GetMutableEntryForPath(std::string_view path); static void ScanDirectory(const char* path, bool recursive, bool only_cache, const std::vector& excluded_paths, const PlayedTimeMap& played_time_map, - ProgressCallback* progress); -static bool AddFileFromCache(const std::string& path, std::time_t timestamp, const PlayedTimeMap& played_time_map); + const INISettingsInterface& custom_attributes_ini, ProgressCallback* progress); +static bool AddFileFromCache(const std::string& path, std::time_t timestamp, const PlayedTimeMap& played_time_map, + const INISettingsInterface& custom_attributes_ini); static bool ScanFile(std::string path, std::time_t timestamp, std::unique_lock& lock, - const PlayedTimeMap& played_time_map); + const PlayedTimeMap& played_time_map, const INISettingsInterface& custom_attributes_ini); static std::string GetCacheFilename(); static void LoadCache(); @@ -92,6 +99,8 @@ static std::string MakePlayedTimeLine(const std::string& serial, const PlayedTim static PlayedTimeMap LoadPlayedTimeMap(const std::string& path); static PlayedTimeEntry UpdatePlayedTimeFile(const std::string& path, const std::string& serial, std::time_t last_time, std::time_t add_time); + +static std::string GetCustomPropertiesFile(); } // namespace GameList static std::vector s_entries; @@ -307,7 +316,8 @@ bool GameList::PopulateEntryFromPath(const std::string& path, Entry* entry) return GetDiscListEntry(path, entry); } -bool GameList::GetGameListEntryFromCache(const std::string& path, Entry* entry) +bool GameList::GetGameListEntryFromCache(const std::string& path, Entry* entry, + const INISettingsInterface& custom_attributes_ini) { auto iter = s_cache_map.find(path); if (iter == s_cache_map.end()) @@ -315,6 +325,7 @@ bool GameList::GetGameListEntryFromCache(const std::string& path, Entry* entry) *entry = std::move(iter->second); s_cache_map.erase(iter); + ApplyCustomAttributes(path, entry, custom_attributes_ini); return true; } @@ -491,7 +502,7 @@ static bool IsPathExcluded(const std::vector& excluded_paths, const void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache, const std::vector& excluded_paths, const PlayedTimeMap& played_time_map, - ProgressCallback* progress) + const INISettingsInterface& custom_attributes_ini, ProgressCallback* progress) { INFO_LOG("Scanning {}{}", path, recursive ? " (recursively)" : ""); @@ -521,15 +532,15 @@ void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache, } std::unique_lock lock(s_mutex); - if (GetEntryForPath(ffd.FileName) || AddFileFromCache(ffd.FileName, ffd.ModificationTime, played_time_map) || - only_cache) + if (GetEntryForPath(ffd.FileName) || + AddFileFromCache(ffd.FileName, ffd.ModificationTime, played_time_map, custom_attributes_ini) || only_cache) { continue; } progress->SetStatusText(SmallString::from_format(TRANSLATE_FS("GameList", "Scanning '{}'..."), FileSystem::GetDisplayNameFromPath(ffd.FileName))); - ScanFile(std::move(ffd.FileName), ffd.ModificationTime, lock, played_time_map); + ScanFile(std::move(ffd.FileName), ffd.ModificationTime, lock, played_time_map, custom_attributes_ini); progress->SetProgressValue(files_scanned); } @@ -537,10 +548,11 @@ void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache, progress->PopState(); } -bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp, const PlayedTimeMap& played_time_map) +bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp, const PlayedTimeMap& played_time_map, + const INISettingsInterface& custom_attributes_ini) { Entry entry; - if (!GetGameListEntryFromCache(path, &entry) || entry.last_modified_time != timestamp) + if (!GetGameListEntryFromCache(path, &entry, custom_attributes_ini) || entry.last_modified_time != timestamp) return false; auto iter = played_time_map.find(entry.serial); @@ -555,7 +567,7 @@ bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp, } bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_lock& lock, - const PlayedTimeMap& played_time_map) + const PlayedTimeMap& played_time_map, const INISettingsInterface& custom_attributes_ini) { // don't block UI while scanning lock.unlock(); @@ -575,18 +587,97 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc WARNING_LOG("Failed to write entry '{}' to cache", entry.path); } - auto iter = played_time_map.find(entry.serial); + const auto iter = played_time_map.find(entry.serial); if (iter != played_time_map.end()) { entry.last_played_time = iter->second.last_played_time; entry.total_played_time = iter->second.total_played_time; } + ApplyCustomAttributes(path, &entry, custom_attributes_ini); + lock.lock(); - s_entries.push_back(std::move(entry)); + + // replace if present + auto it = std::find_if(s_entries.begin(), s_entries.end(), + [&entry](const Entry& existing_entry) { return (existing_entry.path == entry.path); }); + if (it != s_entries.end()) + *it = std::move(entry); + else + s_entries.push_back(std::move(entry)); + + return true; +} + +bool GameList::RescanCustomAttributesForPath(const std::string& path, const INISettingsInterface& custom_attributes_ini) +{ + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(path.c_str(), &sd)) + return false; + + { + // cancel if excluded + const std::vector excluded_paths(Host::GetBaseStringListSetting("GameList", "ExcludedPaths")); + if (IsPathExcluded(excluded_paths, path)) + return false; + } + + Entry entry; + if (!PopulateEntryFromPath(path, &entry)) + return false; + + entry.path = std::move(path); + entry.last_modified_time = sd.ModificationTime; + + const PlayedTimeMap played_time_map(LoadPlayedTimeMap(GetPlayedTimeFile())); + const auto iter = played_time_map.find(entry.serial); + if (iter != played_time_map.end()) + { + entry.last_played_time = iter->second.last_played_time; + entry.total_played_time = iter->second.total_played_time; + } + + ApplyCustomAttributes(path, &entry, custom_attributes_ini); + + std::unique_lock lock(s_mutex); + + // replace if present + auto it = std::find_if(s_entries.begin(), s_entries.end(), + [&entry](const Entry& existing_entry) { return (existing_entry.path == entry.path); }); + if (it != s_entries.end()) + *it = std::move(entry); + else + s_entries.push_back(std::move(entry)); + return true; } +void GameList::ApplyCustomAttributes(const std::string& path, Entry* entry, + const INISettingsInterface& custom_attributes_ini) +{ + std::optional custom_title = custom_attributes_ini.GetOptionalStringValue(path.c_str(), "Title"); + if (custom_title.has_value()) + { + entry->title = std::move(custom_title.value()); + entry->has_custom_title = true; + } + const std::optional custom_region_str = + custom_attributes_ini.GetOptionalSmallStringValue(path.c_str(), "Region"); + if (custom_region_str.has_value()) + { + const std::optional custom_region = Settings::ParseDiscRegionName(custom_region_str.value()); + if (custom_region.has_value()) + { + entry->region = custom_region.value(); + entry->has_custom_region = true; + } + else + { + WARNING_LOG("Invalid region '{}' in custom attributes for '{}'", custom_region_str.value(), path); + } + } +} + std::unique_lock GameList::GetLock() { return std::unique_lock(s_mutex); @@ -599,7 +690,12 @@ const GameList::Entry* GameList::GetEntryByIndex(u32 index) const GameList::Entry* GameList::GetEntryForPath(std::string_view path) { - for (const Entry& entry : s_entries) + return GetMutableEntryForPath(path); +} + +GameList::Entry* GameList::GetMutableEntryForPath(std::string_view path) +{ + for (Entry& entry : s_entries) { // Use case-insensitive compare on Windows, since it's the same file. #ifdef _WIN32 @@ -709,6 +805,8 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback* const std::vector dirs(Host::GetBaseStringListSetting("GameList", "Paths")); std::vector recursive_dirs(Host::GetBaseStringListSetting("GameList", "RecursivePaths")); const PlayedTimeMap played_time(LoadPlayedTimeMap(GetPlayedTimeFile())); + INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile()); + custom_attributes_ini.Load(); #ifdef __ANDROID__ recursive_dirs.push_back(Path::Combine(EmuFolders::DataRoot, "games")); @@ -726,7 +824,7 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback* if (progress->IsCancelled()) break; - ScanDirectory(dir.c_str(), false, only_cache, excluded_paths, played_time, progress); + ScanDirectory(dir.c_str(), false, only_cache, excluded_paths, played_time, custom_attributes_ini, progress); progress->SetProgressValue(++directory_counter); } for (const std::string& dir : recursive_dirs) @@ -734,7 +832,7 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback* if (progress->IsCancelled()) break; - ScanDirectory(dir.c_str(), true, only_cache, excluded_paths, played_time, progress); + ScanDirectory(dir.c_str(), true, only_cache, excluded_paths, played_time, custom_attributes_ini, progress); progress->SetProgressValue(++directory_counter); } } @@ -1425,3 +1523,109 @@ bool GameList::DownloadCovers(const std::vector& url_templates, boo return true; } + +std::string GameList::GetCustomPropertiesFile() +{ + return Path::Combine(EmuFolders::DataRoot, "custom_properties.ini"); +} + +void GameList::SaveCustomTitleForPath(const std::string& path, const std::string& custom_title) +{ + INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile()); + custom_attributes_ini.Load(); + + if (!custom_title.empty()) + { + custom_attributes_ini.SetStringValue(path.c_str(), "Title", custom_title.c_str()); + } + else + { + custom_attributes_ini.DeleteValue(path.c_str(), "Title"); + custom_attributes_ini.RemoveEmptySections(); + } + + Error error; + if (!custom_attributes_ini.Save(&error)) + { + ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription()); + return; + } + + if (!custom_title.empty()) + { + // Can skip the rescan and just update the value directly. + auto lock = GetLock(); + Entry* entry = GetMutableEntryForPath(path); + if (entry) + { + entry->title = custom_title; + entry->has_custom_title = 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); + } +} + +void GameList::SaveCustomRegionForPath(const std::string& path, const std::optional custom_region) +{ + INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile()); + custom_attributes_ini.Load(); + + if (custom_region.has_value()) + { + custom_attributes_ini.SetStringValue(path.c_str(), "Region", Settings::GetDiscRegionName(custom_region.value())); + } + else + { + custom_attributes_ini.DeleteValue(path.c_str(), "Region"); + custom_attributes_ini.RemoveEmptySections(); + } + + Error error; + if (!custom_attributes_ini.Save(&error)) + { + ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription()); + return; + } + + if (custom_region.has_value()) + { + // Can skip the rescan and just update the value directly. + auto lock = GetLock(); + Entry* entry = GetMutableEntryForPath(path); + if (entry) + { + entry->region = custom_region.value(); + entry->has_custom_region = 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); + } +} + +std::string GameList::GetCustomTitleForPath(const std::string_view path) +{ + std::string ret; + + std::unique_lock lock(s_mutex); + const GameList::Entry* entry = GetEntryForPath(path); + if (entry && entry->has_custom_title) + ret = entry->title; + + return ret; +} + +std::optional GameList::GetCustomRegionForPath(const std::string_view path) +{ + const GameList::Entry* entry = GetEntryForPath(path); + if (entry && entry->has_custom_region) + return entry->region; + else + return std::nullopt; +} diff --git a/src/core/game_list.h b/src/core/game_list.h index f596c34ff..e74dce9b3 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -59,6 +59,8 @@ struct Entry u8 max_blocks = 0; s8 disc_set_index = -1; bool disc_set_member = false; + bool has_custom_title = false; + bool has_custom_region = false; GameDatabase::CompatibilityRating compatibility = GameDatabase::CompatibilityRating::Unknown; @@ -122,6 +124,12 @@ GetMatchingEntriesForSerial(const std::span serials); bool DownloadCovers(const std::vector& url_templates, bool use_serial = false, ProgressCallback* progress = nullptr, std::function save_callback = {}); + +// Custom properties support +void SaveCustomTitleForPath(const std::string& path, const std::string& custom_title); +void SaveCustomRegionForPath(const std::string& path, const std::optional custom_region); +std::string GetCustomTitleForPath(const std::string_view path); +std::optional GetCustomRegionForPath(const std::string_view path); }; // namespace GameList namespace Host { diff --git a/src/core/system.cpp b/src/core/system.cpp index e9e5d6786..36e13118c 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -186,7 +186,8 @@ static std::string s_running_game_serial; static std::string s_running_game_title; static const GameDatabase::Entry* s_running_game_entry = nullptr; static System::GameHash s_running_game_hash; -static bool s_was_fast_booted; +static bool s_running_game_custom_title = false; +static bool s_was_fast_booted = false; static bool s_system_executing = false; static bool s_system_interrupted = false; @@ -988,20 +989,6 @@ DiscRegion System::GetRegionForPsf(const char* path) return psf.GetRegion(); } -std::optional System::GetRegionForPath(const char* image_path) -{ - if (IsExeFileName(image_path)) - return GetRegionForExe(image_path); - else if (IsPsfFileName(image_path)) - return GetRegionForPsf(image_path); - - std::unique_ptr cdi = CDImage::Open(image_path, false, nullptr); - if (!cdi) - return {}; - - return GetRegionForImage(cdi.get()); -} - std::string System::GetGameSettingsPath(std::string_view game_serial) { // multi-disc games => always use the first disc @@ -1507,7 +1494,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error) return false; } - disc_region = GetRegionForImage(disc.get()); + disc_region = GameList::GetCustomRegionForPath(parameters.filename).value_or(GetRegionForImage(disc.get())); if (s_region == ConsoleRegion::Auto) { if (disc_region != DiscRegion::Other) @@ -2583,7 +2570,8 @@ bool System::LoadStateFromStream(ByteStream* state, Error* error, bool update_di CDROM::Reset(); if (media) { - const DiscRegion region = GetRegionForImage(media.get()); + const DiscRegion region = + GameList::GetCustomRegionForPath(media_filename).value_or(GetRegionForImage(media.get())); CDROM::InsertMedia(std::move(media), region); if (g_settings.cdrom_load_image_to_ram) CDROM::PrecacheMedia(); @@ -3385,7 +3373,9 @@ std::unique_ptr System::GetMemoryCardForSlot(u32 slot, MemoryCardTyp // But prefer a disc-specific card if one already exists. std::string disc_card_path = g_settings.GetGameMemoryCardPath( - Path::SanitizeFileName(s_running_game_entry ? s_running_game_entry->title : s_running_game_title), slot); + Path::SanitizeFileName((s_running_game_entry && !s_running_game_custom_title) ? s_running_game_entry->title : + s_running_game_title), + slot); if (disc_card_path != card_path) { if (card_path.empty() || !g_settings.memory_card_use_playlist_title || @@ -3627,7 +3617,7 @@ bool System::InsertMedia(const char* path) return false; } - const DiscRegion region = GetRegionForImage(image.get()); + const DiscRegion region = GameList::GetCustomRegionForPath(path).value_or(GetRegionForImage(image.get())); UpdateRunningGame(path, image.get(), false); CDROM::InsertMedia(std::move(image), region); INFO_LOG("Inserted media from {} ({}, {})", s_running_game_path, s_running_game_serial, s_running_game_title); @@ -3668,14 +3658,19 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) s_running_game_title.clear(); s_running_game_entry = nullptr; s_running_game_hash = 0; + s_running_game_custom_title = false; if (path && std::strlen(path) > 0) { s_running_game_path = path; + s_running_game_title = GameList::GetCustomTitleForPath(s_running_game_path); + s_running_game_custom_title = !s_running_game_title.empty(); if (IsExeFileName(path)) { - s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); + if (s_running_game_title.empty()) + s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); + s_running_game_hash = GetGameHashFromFile(path); if (s_running_game_hash != 0) s_running_game_serial = GetGameHashId(s_running_game_hash); @@ -3683,7 +3678,8 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) else if (IsPsfFileName(path)) { // TODO: We could pull the title from the PSF. - s_running_game_title = Path::GetFileTitle(path); + if (s_running_game_title.empty()) + s_running_game_title = Path::GetFileTitle(path); } // Check for an audio CD. Those shouldn't set any title. else if (image && image->GetTrack(1).mode != CDImage::TrackMode::Audio) @@ -3695,19 +3691,24 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) if (s_running_game_entry) { s_running_game_serial = s_running_game_entry->serial; - s_running_game_title = s_running_game_entry->title; + if (s_running_game_title.empty()) + s_running_game_title = s_running_game_entry->title; } else { s_running_game_serial = std::move(id); - s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); + if (s_running_game_title.empty()) + s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); } if (image->HasSubImages()) { std::string image_title = image->GetMetadata("title"); if (!image_title.empty()) + { s_running_game_title = std::move(image_title); + s_running_game_custom_title = false; + } } } } diff --git a/src/core/system.h b/src/core/system.h index 0162278f7..ded9af2d8 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -140,7 +140,6 @@ DiscRegion GetRegionFromSystemArea(CDImage* cdi); DiscRegion GetRegionForImage(CDImage* cdi); DiscRegion GetRegionForExe(const char* path); DiscRegion GetRegionForPsf(const char* path); -std::optional GetRegionForPath(const char* image_path); /// Returns the path for the game settings ini file for the specified serial. std::string GetGameSettingsPath(std::string_view game_serial); diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 6778af849..40034f488 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -254,6 +254,11 @@ void GameListWidget::refresh(bool invalidate_cache) m_refresh_thread->start(); } +void GameListWidget::refreshModel() +{ + m_model->refresh(); +} + void GameListWidget::cancelRefresh() { if (!m_refresh_thread) diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index f5ca657e2..7f3bb4866 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -45,6 +45,7 @@ public: void resizeTableViewColumnsToFit(); void refresh(bool invalidate_cache); + void refreshModel(); void cancelRefresh(); void reloadThemeSpecificImages(); diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index 1b64ccf39..299e39362 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "gamesummarywidget.h" +#include "mainwindow.h" #include "qthost.h" #include "qtprogresscallback.h" #include "settingswindow.h" @@ -54,6 +55,17 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string& connect(m_ui.compatibilityComments, &QToolButton::clicked, this, &GameSummaryWidget::onCompatibilityCommentsClicked); connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged); connect(m_ui.computeHashes, &QAbstractButton::clicked, this, &GameSummaryWidget::onComputeHashClicked); + + connect(m_ui.title, &QLineEdit::editingFinished, this, [this]() { + if (m_ui.title->isModified()) + { + setCustomTitle(m_ui.title->text().toStdString()); + m_ui.title->setModified(false); + } + }); + connect(m_ui.restoreTitle, &QAbstractButton::clicked, this, [this]() { setCustomTitle(std::string()); }); + connect(m_ui.region, &QComboBox::currentIndexChanged, this, [this](int index) { setCustomRegion(index); }); + connect(m_ui.restoreRegion, &QAbstractButton::clicked, this, [this]() { setCustomRegion(-1); }); } GameSummaryWidget::~GameSummaryWidget() = default; @@ -157,7 +169,56 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s else m_ui.inputProfile->setCurrentIndex(0); + populateCustomAttributes(); populateTracksInfo(); + updateWindowTitle(); +} + +void GameSummaryWidget::populateCustomAttributes() +{ + auto lock = GameList::GetLock(); + const GameList::Entry* entry = GameList::GetEntryForPath(m_path); + if (!entry || entry->IsDiscSet()) + return; + + { + QSignalBlocker sb(m_ui.title); + m_ui.title->setText(QString::fromStdString(entry->title)); + m_ui.restoreTitle->setEnabled(entry->has_custom_title); + } + + { + QSignalBlocker sb(m_ui.region); + m_ui.region->setCurrentIndex(static_cast(entry->region)); + m_ui.restoreRegion->setEnabled(entry->has_custom_region); + } +} + +void GameSummaryWidget::updateWindowTitle() +{ + const QString window_title = tr("%1 [%2]").arg(m_ui.title->text()).arg(m_ui.serial->text()); + m_dialog->setWindowTitle(window_title); +} + +void GameSummaryWidget::setCustomTitle(const std::string& text) +{ + m_ui.restoreTitle->setEnabled(!text.empty()); + + GameList::SaveCustomTitleForPath(m_path, text); + populateCustomAttributes(); + updateWindowTitle(); + g_main_window->refreshGameListModel(); +} + +void GameSummaryWidget::setCustomRegion(int region) +{ + m_ui.restoreRegion->setEnabled(region >= 0); + + GameList::SaveCustomRegionForPath(m_path, (region >= 0) ? std::optional(static_cast(region)) : + std::optional()); + populateCustomAttributes(); + updateWindowTitle(); + g_main_window->refreshGameListModel(); } static QString MSFTotString(const CDImage::Position& position) diff --git a/src/duckstation-qt/gamesummarywidget.h b/src/duckstation-qt/gamesummarywidget.h index fb0dbe107..cbc11fc25 100644 --- a/src/duckstation-qt/gamesummarywidget.h +++ b/src/duckstation-qt/gamesummarywidget.h @@ -32,6 +32,11 @@ private Q_SLOTS: private: void populateUi(const std::string& path, const std::string& serial, DiscRegion region, const GameDatabase::Entry* entry); + void populateCustomAttributes(); + void updateWindowTitle(); + void setCustomTitle(const std::string& text); + void setCustomRegion(int region); + void populateTracksInfo(); Ui::GameSummaryWidget m_ui; diff --git a/src/duckstation-qt/gamesummarywidget.ui b/src/duckstation-qt/gamesummarywidget.ui index 7bd0b416e..66734be96 100644 --- a/src/duckstation-qt/gamesummarywidget.ui +++ b/src/duckstation-qt/gamesummarywidget.ui @@ -38,18 +38,49 @@ - - - true - - + + + + + Clear the line to restore the original title... + + + + + + + false + + + Restore + + + + - - - false - - + + + + + + 0 + 0 + + + + + + + + false + + + Restore + + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 75d172ac6..195ffeb94 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -2821,6 +2821,11 @@ void MainWindow::refreshGameList(bool invalidate_cache) m_game_list_widget->refresh(invalidate_cache); } +void MainWindow::refreshGameListModel() +{ + m_game_list_widget->refreshModel(); +} + void MainWindow::cancelGameListRefresh() { m_game_list_widget->cancelRefresh(); diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index cd5b43eda..6e9dd5e45 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -107,6 +107,7 @@ public Q_SLOTS: void updateDebugMenuVisibility(); void refreshGameList(bool invalidate_cache); + void refreshGameListModel(); void cancelGameListRefresh(); void runOnUIThread(const std::function& func); diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp index db0e1065c..f1bef85ec 100644 --- a/src/duckstation-qt/settingswindow.cpp +++ b/src/duckstation-qt/settingswindow.cpp @@ -653,11 +653,7 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std if (FileSystem::FileExists(sif->GetFileName().c_str())) sif->Load(); - const QString window_title( - tr("%1 [%2]").arg(QString::fromStdString(dentry ? dentry->title : title)).arg(QString::fromStdString(real_serial))); - SettingsWindow* dialog = new SettingsWindow(path, real_serial, region, dentry, std::move(sif)); - dialog->setWindowTitle(window_title); dialog->show(); }