GameList: Add custom title/regions
Largely inspired by https://github.com/PCSX2/pcsx2/pull/9330, but almost entirely rewritten.
This commit is contained in:
parent
01fc3258a3
commit
ccb76d1451
|
@ -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<std::string>& 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<std::recursive_mutex>& 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<GameList::Entry> 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<std::string>& excluded_paths, const
|
|||
|
||||
void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache,
|
||||
const std::vector<std::string>& 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<std::recursive_mutex>& 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<std::string> 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<std::string> 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<SmallString> custom_region_str =
|
||||
custom_attributes_ini.GetOptionalSmallStringValue(path.c_str(), "Region");
|
||||
if (custom_region_str.has_value())
|
||||
{
|
||||
const std::optional<DiscRegion> 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<std::recursive_mutex> GameList::GetLock()
|
||||
{
|
||||
return std::unique_lock<std::recursive_mutex>(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<std::string> dirs(Host::GetBaseStringListSetting("GameList", "Paths"));
|
||||
std::vector<std::string> 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<std::string>& 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<DiscRegion> 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<DiscRegion> 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;
|
||||
}
|
||||
|
|
|
@ -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<const std::string> serials);
|
|||
bool DownloadCovers(const std::vector<std::string>& url_templates, bool use_serial = false,
|
||||
ProgressCallback* progress = nullptr,
|
||||
std::function<void(const Entry*, std::string)> 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<DiscRegion> custom_region);
|
||||
std::string GetCustomTitleForPath(const std::string_view path);
|
||||
std::optional<DiscRegion> GetCustomRegionForPath(const std::string_view path);
|
||||
}; // namespace GameList
|
||||
|
||||
namespace Host {
|
||||
|
|
|
@ -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<DiscRegion> 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<CDImage> 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<MemoryCard> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,7 +140,6 @@ DiscRegion GetRegionFromSystemArea(CDImage* cdi);
|
|||
DiscRegion GetRegionForImage(CDImage* cdi);
|
||||
DiscRegion GetRegionForExe(const char* path);
|
||||
DiscRegion GetRegionForPsf(const char* path);
|
||||
std::optional<DiscRegion> 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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
void resizeTableViewColumnsToFit();
|
||||
|
||||
void refresh(bool invalidate_cache);
|
||||
void refreshModel();
|
||||
void cancelRefresh();
|
||||
void reloadThemeSpecificImages();
|
||||
|
||||
|
|
|
@ -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<int>(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<DiscRegion>(static_cast<DiscRegion>(region)) :
|
||||
std::optional<DiscRegion>());
|
||||
populateCustomAttributes();
|
||||
updateWindowTitle();
|
||||
g_main_window->refreshGameListModel();
|
||||
}
|
||||
|
||||
static QString MSFTotString(const CDImage::Position& position)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -38,18 +38,49 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="title">
|
||||
<property name="placeholderText">
|
||||
<string>Clear the line to restore the original title...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreTitle">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Restore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="region">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QComboBox" name="region">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreRegion">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Restore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -107,6 +107,7 @@ public Q_SLOTS:
|
|||
void updateDebugMenuVisibility();
|
||||
|
||||
void refreshGameList(bool invalidate_cache);
|
||||
void refreshGameListModel();
|
||||
void cancelGameListRefresh();
|
||||
|
||||
void runOnUIThread(const std::function<void()>& func);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue