From a8b4bb3fce8a15a2a901425e36117eff32cc702b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Aug 2025 13:42:55 +1000 Subject: [PATCH] GameDatabase: Add sort title, localized title, and save title fields --- src/core/cheats.cpp | 3 +- src/core/fullscreen_ui.cpp | 27 ++++--- src/core/game_database.cpp | 36 ++++++++- src/core/game_database.h | 98 +++++++++++++++--------- src/core/game_list.cpp | 20 ++++- src/core/game_list.h | 6 ++ src/core/system.cpp | 22 +++--- src/duckstation-qt/gamelistwidget.cpp | 35 +++++---- src/duckstation-qt/gamesummarywidget.cpp | 4 +- src/duckstation-qt/mainwindow.cpp | 10 +-- 10 files changed, 169 insertions(+), 92 deletions(-) diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 12aa1d433..366728f0f 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1994,7 +1994,8 @@ bool Cheats::ImportOldChtFile(const std::string_view serial) if (!dbentry || dbentry->title.empty()) return false; - const std::string old_path = fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, dbentry->title); + const std::string old_path = + fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, dbentry->GetSaveTitle()); if (!FileSystem::FileExists(old_path.c_str())) return false; diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 2b953b00b..31ca31d71 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -7873,7 +7873,7 @@ void FullscreenUI::PopulateGameListEntryList() } // fallback to title when all else is equal - const int res = StringUtil::Strcasecmp(lhs->title.c_str(), rhs->title.c_str()); + const int res = StringUtil::CompareNoCase(lhs->GetSortTitle(), rhs->GetSortTitle()); return reverse ? (res > 0) : (res < 0); }); } @@ -8026,7 +8026,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) summary.format("{} | {} | {} MB", entry->serial, Path::GetFileName(entry->path), to_mb(entry->file_size)); } - const ImGuiFullscreen::MenuButtonBounds mbb(entry->title, {}, summary, row_left_margin); + const ImGuiFullscreen::MenuButtonBounds mbb(entry->GetDisplayTitle(), {}, summary, row_left_margin); bool visible, hovered; bool pressed = MenuButtonFrame(entry->path, true, mbb.frame_bb, &visible, &hovered); @@ -8042,8 +8042,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImGui::GetWindowDrawList()->AddImage(cover_texture, image_rect.Min, image_rect.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); RenderShadowedTextClipped(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight, mbb.title_bb.Min, - mbb.title_bb.Max, text_color, entry->title, &mbb.title_size, ImVec2(0.0f, 0.0f), - mbb.title_size.x, &mbb.title_bb); + mbb.title_bb.Max, text_color, entry->GetDisplayTitle(), &mbb.title_size, + ImVec2(0.0f, 0.0f), mbb.title_size.x, &mbb.title_bb); if (!summary.empty()) { @@ -8145,12 +8145,13 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) if (selected_entry) { const ImVec4 subtitle_text_color = DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]); + const std::string_view title = selected_entry->GetDisplayTitle(); // title ImGui::PushFont(UIStyle.Font, UIStyle.LargeFontSize, UIStyle.BoldFontWeight); - text_width = ImGui::CalcTextSize(selected_entry->title.c_str(), nullptr, false, work_width).x; + text_width = ImGui::CalcTextSize(IMSTR_START_END(title), false, work_width).x; ImGui::SetCursorPosX((work_width - text_width) / 2.0f); - ImGui::TextWrapped("%s", selected_entry->title.c_str()); + ImGui::TextWrapped("%.*s", static_cast(title.size()), title.data()); ImGui::PopFont(); ImGui::PushFont(UIStyle.Font, UIStyle.MediumFontSize, UIStyle.BoldFontWeight); @@ -8388,8 +8389,9 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) for (size_t row_entry_index = entry_index; row_entry_index < row_entry_index_end; row_entry_index++) { const GameList::Entry* row_entry = s_state.game_list_sorted_entries[row_entry_index]; + const std::string_view row_title = row_entry->GetDisplayTitle(); const ImVec2 this_title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, - image_width, IMSTR_START_END(row_entry->title)); + image_width, IMSTR_START_END(row_title)); row_item_height = std::max(row_item_height, this_title_size.y); } @@ -8399,8 +8401,9 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) ImVec2 title_size; if (s_state.game_grid_show_titles) { + const std::string_view title = entry->GetDisplayTitle(); title_size = UIStyle.Font->CalcTextSizeA(title_font_size, title_font_weight, image_width, image_width, - IMSTR_START_END(entry->title)); + IMSTR_START_END(title)); } const ImGuiID id = window->GetID(entry->path.c_str(), entry->path.c_str() + entry->path.length()); @@ -8448,9 +8451,9 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) if (draw_title) { const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); - ImGuiFullscreen::RenderMultiLineShadowedTextClipped(dl, UIStyle.Font, title_font_size, title_font_weight, - title_bb.Min, title_bb.Max, text_color, entry->title, - LAYOUT_CENTER_ALIGN_TEXT, image_width, &title_bb); + ImGuiFullscreen::RenderMultiLineShadowedTextClipped( + dl, UIStyle.Font, title_font_size, title_font_weight, title_bb.Min, title_bb.Max, text_color, + entry->GetDisplayTitle(), LAYOUT_CENTER_ALIGN_TEXT, image_width, &title_bb); } if (pressed) @@ -8517,7 +8520,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) }; OpenChoiceDialog( - entry->title.c_str(), false, std::move(options), + entry->GetDisplayTitle(), false, std::move(options), [entry_path = entry->path, entry_serial = entry->serial](s32 index, const std::string& title, bool checked) mutable { switch (index) diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index bd92f623a..4ed8e452a 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -40,7 +40,7 @@ namespace GameDatabase { enum : u32 { GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48, - GAME_DATABASE_CACHE_VERSION = 28, + GAME_DATABASE_CACHE_VERSION = 29, }; static const Entry* GetEntryForId(std::string_view code); @@ -360,6 +360,21 @@ SmallString GameDatabase::Entry::GetLanguagesString() const return ret; } +std::string_view GameDatabase::Entry::GetDisplayTitle() const +{ + return !localized_title.empty() ? localized_title : title; +} + +std::string_view GameDatabase::Entry::GetSortTitle() const +{ + return !sort_title.empty() ? sort_title : title; +} + +std::string_view GameDatabase::Entry::GetSaveTitle() const +{ + return !save_title.empty() ? save_title : title; +} + void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_messages) const { if (display_active_start_offset.has_value()) @@ -917,8 +932,14 @@ static inline void AppendEnumSetting(SmallStringBase& str, bool& heading, std::s std::string GameDatabase::Entry::GenerateCompatibilityReport() const { LargeString ret; - ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Title"), title); ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Serial"), serial); + ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Title"), title); + if (!sort_title.empty()) + ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Sort Title"), title); + if (!localized_title.empty()) + ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Localized Title"), title); + if (!save_title.empty()) + ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Save Title"), title); if (languages.any()) ret.append_format("**{}:** {}\n\n", TRANSLATE_SV("GameDatabase", "Languages"), GetLanguagesString()); @@ -1052,8 +1073,9 @@ bool GameDatabase::LoadFromCache() u32 num_disc_set_serials; if (!reader.ReadSizePrefixedString(&entry.serial) || !reader.ReadSizePrefixedString(&entry.title) || - !reader.ReadSizePrefixedString(&entry.genre) || !reader.ReadSizePrefixedString(&entry.developer) || - !reader.ReadSizePrefixedString(&entry.publisher) || + !reader.ReadSizePrefixedString(&entry.sort_title) || !reader.ReadSizePrefixedString(&entry.localized_title) || + !reader.ReadSizePrefixedString(&entry.save_title) || !reader.ReadSizePrefixedString(&entry.genre) || + !reader.ReadSizePrefixedString(&entry.developer) || !reader.ReadSizePrefixedString(&entry.publisher) || !reader.ReadSizePrefixedString(&entry.compatibility_version_tested) || !reader.ReadSizePrefixedString(&entry.compatibility_comments) || !reader.ReadU64(&entry.release_date) || !reader.ReadU8(&entry.min_players) || !reader.ReadU8(&entry.max_players) || !reader.ReadU8(&entry.min_blocks) || @@ -1145,6 +1167,9 @@ bool GameDatabase::SaveToCache() { writer.WriteSizePrefixedString(entry.serial); writer.WriteSizePrefixedString(entry.title); + writer.WriteSizePrefixedString(entry.sort_title); + writer.WriteSizePrefixedString(entry.localized_title); + writer.WriteSizePrefixedString(entry.save_title); writer.WriteSizePrefixedString(entry.genre); writer.WriteSizePrefixedString(entry.developer); writer.WriteSizePrefixedString(entry.publisher); @@ -1282,6 +1307,9 @@ bool GameDatabase::LoadGameDBYaml() bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value) { GetStringFromObject(value, "name", &entry->title); + GetStringFromObject(value, "sortName", &entry->sort_title); + GetStringFromObject(value, "localizedName", &entry->localized_title); + GetStringFromObject(value, "saveName", &entry->save_title); entry->supported_controllers = static_cast(~0u); diff --git a/src/core/game_database.h b/src/core/game_database.h index a382b85b7..14cbd426c 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -100,53 +100,75 @@ struct Entry { static constexpr u16 SUPPORTS_MULTITAP_BIT = (1u << static_cast(ControllerType::Count)); - std::string_view serial; - std::string_view title; - std::string_view genre; - std::string_view developer; - std::string_view publisher; - std::string_view compatibility_version_tested; - std::string_view compatibility_comments; - u64 release_date; ///< Number of seconds since Epoch. - u8 min_players; - u8 max_players; - u8 min_blocks; - u8 max_blocks; - u16 supported_controllers; - CompatibilityRating compatibility; - - std::bitset(Trait::MaxCount)> traits{}; - std::bitset(Language::MaxCount)> languages{}; - std::optional display_active_start_offset; - std::optional display_active_end_offset; - std::optional display_line_start_offset; - std::optional display_line_end_offset; - std::optional display_crop_mode; - std::optional display_deinterlacing_mode; - std::optional gpu_line_detect_mode; - std::optional cpu_overclock; - std::optional dma_max_slice_ticks; - std::optional dma_halt_ticks; - std::optional cdrom_max_seek_speedup_cycles; - std::optional cdrom_max_read_speedup_cycles; - std::optional gpu_fifo_size; - std::optional gpu_max_run_ahead; - std::optional gpu_pgxp_tolerance; - std::optional gpu_pgxp_depth_threshold; - std::optional gpu_pgxp_preserve_proj_fp; - - std::string_view disc_set_name; - std::vector disc_set_serials; - + std::string_view serial; ///< Official serial of the game, e.g. "SLUS-00001". + std::string_view title; ///< Official title of the game. + std::string_view sort_title; ///< Title used for sorting in game lists. + std::string_view localized_title; ///< Title in the native language, if available. + std::string_view save_title; ///< Title used for per-game memory cards. + std::string_view genre; ///< Genre of the game. + std::string_view developer; ///< Developer of the game. + std::string_view publisher; ///< Publisher of the game. + std::string_view compatibility_version_tested; ///< Version of the application the game was tested with. + std::string_view compatibility_comments; ///< Comments about the game's compatibility. + u64 release_date; ///< Number of seconds since Epoch. + u8 min_players; ///< Minimum number of players supported. + u8 max_players; ///< Maximum number of players supported. + u8 min_blocks; ///< Minimum number of blocks the game uses. + u8 max_blocks; ///< Maximum number of blocks the game uses. + u16 supported_controllers; ///< Bitfield of supported controllers. + CompatibilityRating compatibility; ///< Compatibility rating of the game. + + std::bitset(Trait::MaxCount)> traits{}; ///< Traits for the game. + std::bitset(Language::MaxCount)> languages{}; ///< Languages supported by the game. + std::optional display_active_start_offset; ///< Display active start offset override. + std::optional display_active_end_offset; ///< Display active end offset override. + std::optional display_line_start_offset; ///< Display line start offset override. + std::optional display_line_end_offset; ///< Display line end offset override. + std::optional display_crop_mode; ///< Display crop mode override. + std::optional display_deinterlacing_mode; ///< Display deinterlacing mode override. + std::optional gpu_line_detect_mode; ///< GPU line detect mode override. + std::optional cpu_overclock; ///< CPU overclock percentage override. + std::optional dma_max_slice_ticks; ///< DMA max slice ticks override. + std::optional dma_halt_ticks; ///< DMA halt ticks override. + std::optional cdrom_max_seek_speedup_cycles; ///< CD-ROM max seek speedup cycles override. + std::optional cdrom_max_read_speedup_cycles; ///< CD-ROM max read speedup cycles override. + std::optional gpu_fifo_size; ///< GPU FIFO size override. + std::optional gpu_max_run_ahead; ///< GPU max runahead override. + std::optional gpu_pgxp_tolerance; ///< GPU PGXP tolerance override. + std::optional gpu_pgxp_depth_threshold; ///< GPU PGXP depth threshold override. + std::optional gpu_pgxp_preserve_proj_fp; ///< GPU PGXP preserve projection precision override. + + std::string_view disc_set_name; ///< Name of the disc set, if applicable. + std::vector disc_set_serials; ///< Serials of all discs in the set. + + /// Checks if a trait is present. ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast(trait)]; } + + /// Checks if a language is present. ALWAYS_INLINE bool HasLanguage(Language language) const { return languages.test(static_cast(language)); } + + /// Checks if any language is present. ALWAYS_INLINE bool HasAnyLanguage() const { return languages.any(); } + /// Returns the flag for the game's language if it only has one, and it is not English. Otherwise the region. std::string_view GetLanguageFlagName(DiscRegion region) const; + + /// Returns a comma-separated list of language names. SmallString GetLanguagesString() const; + /// Returns the title that should be displayed for this game. + std::string_view GetDisplayTitle() const; + + /// Returns the sort name if present, otherwise the title. + std::string_view GetSortTitle() const; + + /// Returns the name to use when creating memory cards for this game. + std::string_view GetSaveTitle() const; + + /// Applies any settings overrides to the given settings object. void ApplySettings(Settings& settings, bool display_osd_messages) const; + /// Generates a compatibility report in markdown format. std::string GenerateCompatibilityReport() const; }; diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp index 71128566c..d1f8d0ca1 100644 --- a/src/core/game_list.cpp +++ b/src/core/game_list.cpp @@ -319,7 +319,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) { // pull from database entry->serial = dentry->serial; - entry->title = dentry->title; + entry->title = dentry->GetDisplayTitle(); entry->dbentry = dentry; if (!cdi->HasSubImages()) @@ -1255,6 +1255,24 @@ std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const cha return Path::Combine(EmuFolders::Covers, Path::SanitizeFileName(name)); } +std::string_view GameList::Entry::GetDisplayTitle() const +{ + // if custom title is present, use that for display too + return has_custom_title ? title : (dbentry ? dbentry->GetDisplayTitle() : title); +} + +std::string_view GameList::Entry::GetSortTitle() const +{ + // if custom title is present, use that for sorting too + return has_custom_title ? title : (dbentry ? dbentry->GetSortTitle() : title); +} + +std::string_view GameList::Entry::GetSaveTitle() const +{ + // if custom title is present, use that for save folder too + return has_custom_title ? title : (dbentry ? dbentry->GetSaveTitle() : title); +} + std::string_view GameList::Entry::GetLanguageIcon() const { std::string_view ret; diff --git a/src/core/game_list.h b/src/core/game_list.h index 76e884e40..5075afe62 100644 --- a/src/core/game_list.h +++ b/src/core/game_list.h @@ -60,6 +60,12 @@ struct Entry u16 unlocked_achievements = 0; u16 unlocked_achievements_hc = 0; + std::string_view GetDisplayTitle() const; + + std::string_view GetSortTitle() const; + + std::string_view GetSaveTitle() const; + std::string_view GetLanguageIcon() const; TinyString GetLanguageIconName() const; diff --git a/src/core/system.cpp b/src/core/system.cpp index 4c6bdf8af..39280fff3 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -3445,8 +3445,7 @@ u32 System::CompressAndWriteStateData(std::FILE* fp, std::span src, Sa { ctype = CompressHelpers::CompressType::XZ; *header_type = static_cast(SAVE_STATE_HEADER::CompressionType::XZ); - clevel = - ((method == SaveStateCompressionMode::XZLow) ? 1 : ((method == SaveStateCompressionMode::XZHigh) ? 9 : 5)); + clevel = ((method == SaveStateCompressionMode::XZLow) ? 1 : ((method == SaveStateCompressionMode::XZHigh) ? 9 : 5)); } else { @@ -3808,11 +3807,12 @@ std::unique_ptr System::GetMemoryCardForSlot(u32 slot, MemoryCardTyp } else { + const std::string_view game_title = + s_state.running_game_custom_title ? s_state.running_game_title : s_state.running_game_entry->GetSaveTitle(); std::string card_path; // Playlist - use title if different. - if (HasMediaSubImages() && s_state.running_game_entry && - s_state.running_game_title != s_state.running_game_entry->title) + if (HasMediaSubImages() && s_state.running_game_entry && s_state.running_game_title != game_title) { card_path = g_settings.GetGameMemoryCardPath(Path::SanitizeFileName(s_state.running_game_title), slot); } @@ -3824,11 +3824,7 @@ 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_state.running_game_entry && !s_state.running_game_custom_title) ? - s_state.running_game_entry->title : - s_state.running_game_title), - slot); + std::string disc_card_path = g_settings.GetGameMemoryCardPath(Path::SanitizeFileName(game_title), slot); if (disc_card_path != card_path) { if (card_path.empty() || !g_settings.memory_card_use_playlist_title || @@ -4157,7 +4153,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo { s_state.running_game_entry = GameDatabase::GetEntryForSerial(s_state.running_game_serial); if (s_state.running_game_entry && s_state.running_game_title.empty()) - s_state.running_game_title = s_state.running_game_entry->title; + s_state.running_game_title = s_state.running_game_entry->GetDisplayTitle(); else if (s_state.running_game_title.empty()) s_state.running_game_title = s_state.running_game_serial; } @@ -4176,7 +4172,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo { s_state.running_game_serial = s_state.running_game_entry->serial; if (s_state.running_game_title.empty()) - s_state.running_game_title = s_state.running_game_entry->title; + s_state.running_game_title = s_state.running_game_entry->GetDisplayTitle(); } else { @@ -5775,7 +5771,7 @@ std::string System::GetGameMemoryCardPath(std::string_view serial, std::string_v const GameDatabase::Entry* entry = GameDatabase::GetEntryForSerial(serial); if (entry) { - ret = g_settings.GetGameMemoryCardPath(Path::SanitizeFileName(entry->title), slot); + ret = g_settings.GetGameMemoryCardPath(Path::SanitizeFileName(entry->GetSaveTitle()), slot); // Use disc set name if there isn't a per-disc card present. const bool global_use_playlist_title = Host::GetBaseBoolSettingValue(section, "UsePlaylistTitle", true); @@ -6193,7 +6189,7 @@ void System::UpdateRichPresence(bool update_session_time) { // Use disc set name if it's not a custom title. if (s_state.running_game_entry && !s_state.running_game_entry->disc_set_name.empty() && - s_state.running_game_title == s_state.running_game_entry->title) + s_state.running_game_title == s_state.running_game_entry->GetDisplayTitle()) { game_details = s_state.running_game_entry->disc_set_name; } diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index c17e222d1..774fbd573 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -529,7 +529,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList: return QtUtils::StringViewToQString(ge->serial); case Column_Title: - return QtUtils::StringViewToQString(ge->title); + return QtUtils::StringViewToQString(ge->GetDisplayTitle()); case Column_FileTitle: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); @@ -589,7 +589,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList: case Column_Cover: { if (m_show_titles_for_covers) - return QString::fromStdString(ge->title); + return QtUtils::StringViewToQString(ge->GetDisplayTitle()); else return {}; } @@ -665,7 +665,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList: return QtUtils::StringViewToQString(ge->serial); case Column_Title: - return QtUtils::StringViewToQString(ge->title); + return QtUtils::StringViewToQString(ge->GetDisplayTitle()); case Column_FileTitle: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); @@ -761,7 +761,7 @@ void GameListModel::refresh() bool GameListModel::titlesLessThan(const GameList::Entry* left, const GameList::Entry* right) const { - return (StringUtil::Strcasecmp(left->title.c_str(), right->title.c_str()) < 0); + return (StringUtil::CompareNoCase(left->GetSortTitle(), right->GetSortTitle()) < 0); } bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const @@ -1070,9 +1070,9 @@ public: if (!m_filter_name.empty()) { - if (!((!entry->IsDiscSet() && !entry->path.empty() && StringUtil::ContainsNoCase(entry->path, m_filter_name)) || - (!entry->serial.empty() && StringUtil::ContainsNoCase(entry->serial, m_filter_name)) || - (!entry->title.empty() && StringUtil::ContainsNoCase(entry->title, m_filter_name)))) + if (!((!entry->IsDiscSet() && StringUtil::ContainsNoCase(entry->path, m_filter_name)) || + StringUtil::ContainsNoCase(entry->serial, m_filter_name) || + StringUtil::ContainsNoCase(entry->GetDisplayTitle(), m_filter_name))) { return false; } @@ -1445,7 +1445,10 @@ void GameListWidget::onListViewItemActivated(const QModelIndex& index) const auto lock = GameList::GetLock(); const GameList::Entry* entry = GameList::GetEntryByIndex(static_cast(source_index.row())); if (entry) - SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->hash, entry->region); + { + SettingsWindow::openGamePropertiesDialog(entry->path, std::string(entry->GetDisplayTitle()), entry->serial, + entry->hash, entry->region); + } } else { @@ -1678,9 +1681,8 @@ void GameListListView::setFixedColumnWidth(const QFontMetrics& fm, int column, i const int margin = style()->pixelMetric(QStyle::PM_HeaderMargin, nullptr, this); const int header_width = fm.size(0, m_model->getColumnDisplayName(column)).width() + style()->pixelMetric(QStyle::PM_HeaderMarkSize, nullptr, this) + // sort indicator - margin; // space between text and sort indicator - const int width = std::max(header_width, str_width) + - 2 * margin; // left and right margins + margin; // space between text and sort indicator + const int width = std::max(header_width, str_width) + 2 * margin; // left and right margins setFixedColumnWidth(column, width); } @@ -1703,11 +1705,12 @@ void GameListListView::setFixedColumnWidths() // And this is a monstrosity. setFixedColumnWidth( fm, GameListModel::Column_LastPlayed, - std::max(width_for(qApp->translate("GameList", "Today")), - std::max(width_for(qApp->translate("GameList", "Yesterday")), - std::max(width_for(qApp->translate("GameList", "Never")), - width_for(QtHost::FormatNumber(Host::NumberFormatType::ShortDate, - static_cast(QDateTime::currentSecsSinceEpoch()))))))); + std::max( + width_for(qApp->translate("GameList", "Today")), + std::max(width_for(qApp->translate("GameList", "Yesterday")), + std::max(width_for(qApp->translate("GameList", "Never")), + width_for(QtHost::FormatNumber(Host::NumberFormatType::ShortDate, + static_cast(QDateTime::currentSecsSinceEpoch()))))))); // Assume 8 is the widest digit. int size_width = width_for(QStringLiteral("%1 MB").arg(8888.88, 0, 'f', 2)); diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index 1cf46efc0..825355856 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -126,7 +126,7 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s if (entry) { - m_ui.title->setText(QtUtils::StringViewToQString(entry->title)); + m_ui.title->setText(QtUtils::StringViewToQString(entry->GetDisplayTitle())); m_ui.compatibility->setCurrentIndex(static_cast(entry->compatibility)); m_ui.genre->setText(entry->genre.empty() ? tr("Unknown") : QtUtils::StringViewToQString(entry->genre)); if (!entry->developer.empty() && !entry->publisher.empty() && entry->developer != entry->publisher) @@ -268,7 +268,7 @@ void GameSummaryWidget::populateCustomAttributes() { QSignalBlocker sb(m_ui.title); - m_ui.title->setText(QString::fromStdString(entry->title)); + m_ui.title->setText(QtUtils::StringViewToQString(entry->GetDisplayTitle())); m_ui.restoreTitle->setEnabled(entry->has_custom_title); } diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 0c26520c2..2465bfe9b 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1565,8 +1565,8 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) if (!entry) return; - SettingsWindow::openGamePropertiesDialog(entry->path, entry->title, entry->serial, entry->hash, - entry->region); + SettingsWindow::openGamePropertiesDialog(entry->path, std::string(entry->GetDisplayTitle()), entry->serial, + entry->hash, entry->region); }); connect(menu.addAction(tr("Open Containing Directory...")), &QAction::triggered, [this, qpath]() { @@ -1644,8 +1644,8 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) const GameList::Entry* first_disc = GameList::GetFirstDiscSetMember(disc_set_name.toStdString()); if (first_disc) { - SettingsWindow::openGamePropertiesDialog(first_disc->path, first_disc->title, first_disc->serial, - first_disc->hash, first_disc->region); + SettingsWindow::openGamePropertiesDialog(first_disc->path, std::string(first_disc->GetDisplayTitle()), + first_disc->serial, first_disc->hash, first_disc->region); } }); @@ -1743,7 +1743,7 @@ void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry) if (QMessageBox::question( this, tr("Confirm Reset"), tr("Are you sure you want to reset the play time for '%1'?\n\nThis action cannot be undone.") - .arg(QString::fromStdString(entry->title))) != QMessageBox::Yes) + .arg(QtUtils::StringViewToQString(entry->GetDisplayTitle()))) != QMessageBox::Yes) { return; }