diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp index 595f7c7894..e9e371669b 100644 --- a/pcsx2-qt/GameList/GameListModel.cpp +++ b/pcsx2-qt/GameList/GameListModel.cpp @@ -179,7 +179,7 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge) // while there's outstanding jobs, the old jobs won't proceed (at the wrong size), or get added into the grid. const u32 counter = m_cover_scale_counter.load(std::memory_order_acquire); - QFuture future = QtConcurrent::run([this, path = ge->path, title = ge->title, serial = ge->serial, counter]() -> QPixmap { + QFuture future = QtConcurrent::run([this, path = ge->path, title = ge->GetTitle(m_prefer_english_titles), serial = ge->serial, counter]() -> QPixmap { QPixmap image; if (m_cover_scale_counter.load(std::memory_order_acquire) == counter) { @@ -294,7 +294,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const return QString::fromStdString(ge->serial); case Column_Title: - return QString::fromStdString(ge->title); + return QString::fromStdString(ge->GetTitle(m_prefer_english_titles)); case Column_FileTitle: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); @@ -319,7 +319,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_Cover: { if (m_show_titles_for_covers) - return QString::fromStdString(ge->title); + return QString::fromStdString(ge->GetTitle(m_prefer_english_titles)); else return {}; } @@ -341,7 +341,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_Title: case Column_Cover: - return QString::fromStdString(ge->GetTitleSort()); + return QString::fromStdString(ge->GetTitleSort(m_prefer_english_titles)); case Column_FileTitle: return QtUtils::StringViewToQString(Path::GetFileTitle(ge->path)); @@ -424,6 +424,7 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int void GameListModel::refresh() { + m_prefer_english_titles = Host::GetBaseBoolSettingValue("UI", "PreferEnglishGameList", false); beginResetModel(); endResetModel(); } @@ -438,7 +439,8 @@ bool GameListModel::titlesLessThan(int left_row, int right_row) const const GameList::Entry* left = GameList::GetEntryByIndex(left_row); const GameList::Entry* right = GameList::GetEntryByIndex(right_row); - return QtHost::LocaleSensitiveCompare(QString::fromStdString(left->GetTitleSort()), QString::fromStdString(right->GetTitleSort())) < 0; + return QtHost::LocaleSensitiveCompare(QString::fromStdString(left->GetTitleSort(m_prefer_english_titles)), + QString::fromStdString(right->GetTitleSort(m_prefer_english_titles))) < 0; } bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const diff --git a/pcsx2-qt/GameList/GameListModel.h b/pcsx2-qt/GameList/GameListModel.h index d2a7707691..bfd84dec1d 100644 --- a/pcsx2-qt/GameList/GameListModel.h +++ b/pcsx2-qt/GameList/GameListModel.h @@ -96,6 +96,7 @@ private: float m_cover_scale = 0.0f; std::atomic m_cover_scale_counter{0}; bool m_show_titles_for_covers = false; + bool m_prefer_english_titles = false; std::array m_column_display_names; std::array(GameList::EntryType::Count)> m_type_pixmaps; diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp index 92bda95e05..0ec940e959 100644 --- a/pcsx2-qt/GameList/GameListWidget.cpp +++ b/pcsx2-qt/GameList/GameListWidget.cpp @@ -87,7 +87,8 @@ public: if (!m_filter_name.isEmpty() && !QString::fromStdString(entry->path).contains(m_filter_name, Qt::CaseInsensitive) && !QString::fromStdString(entry->serial).contains(m_filter_name, Qt::CaseInsensitive) && - !QString::fromStdString(entry->title).contains(m_filter_name, Qt::CaseInsensitive)) + !QString::fromStdString(entry->title).contains(m_filter_name, Qt::CaseInsensitive) && + !QString::fromStdString(entry->title_en).contains(m_filter_name, Qt::CaseInsensitive)) return false; } diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 8a3819eff9..322c62a1de 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -2299,6 +2299,9 @@ SettingsWindow* MainWindow::getSettingsWindow() g_main_window->doSettings("Interface"); }); }); + connect(m_setings_window->getGameListSettingsWidget(), &GameListSettingsWidget::preferEnglishGameListChanged, this, []{ + g_main_window->m_game_list_widget->refreshGridCovers(); + }); } return m_setings_window; diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 4f7efe5457..d4b3de72ae 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -1296,6 +1296,7 @@ void Host::SetDefaultUISettings(SettingsInterface& si) si.SetBoolValue("UI", "RenderToSeparateWindow", false); si.SetBoolValue("UI", "HideMainWindowWhenRunning", false); si.SetBoolValue("UI", "DisableWindowResize", false); + si.SetBoolValue("UI", "PreferEnglishGameList", false); si.SetStringValue("UI", "Theme", QtHost::GetDefaultThemeName()); } diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.cpp b/pcsx2-qt/Settings/GameListSettingsWidget.cpp index a4682a9860..7525aa2cc2 100644 --- a/pcsx2-qt/Settings/GameListSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GameListSettingsWidget.cpp @@ -30,12 +30,21 @@ #include "MainWindow.h" #include "QtHost.h" #include "QtUtils.h" +#include "SettingWidgetBinder.h" GameListSettingsWidget::GameListSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QWidget(parent) { + SettingsInterface* sif = dialog->getSettingsInterface(); + m_ui.setupUi(this); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.preferEnglishGameList, "UI", "PreferEnglishGameList", false); + connect(m_ui.preferEnglishGameList, &QCheckBox::stateChanged, [this]{ emit preferEnglishGameListChanged(); }); + + dialog->registerWidgetHelp(m_ui.preferEnglishGameList, tr("Prefer English Titles"), tr("Unchecked"), + tr("For games with both a title in the game's native language and one in English, prefer the English title.")); + m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection); m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows); m_ui.searchDirectoryList->setAlternatingRowColors(true); diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.h b/pcsx2-qt/Settings/GameListSettingsWidget.h index 1c9bce55ce..fd51a1ae3a 100644 --- a/pcsx2-qt/Settings/GameListSettingsWidget.h +++ b/pcsx2-qt/Settings/GameListSettingsWidget.h @@ -32,6 +32,9 @@ public: bool addExcludedPath(const std::string& path); void refreshExclusionList(); +Q_SIGNALS: + void preferEnglishGameListChanged(); + public Q_SLOTS: void addSearchDirectory(QWidget* parent_widget); diff --git a/pcsx2-qt/Settings/GameListSettingsWidget.ui b/pcsx2-qt/Settings/GameListSettingsWidget.ui index df461ecc93..f9d54e5320 100644 --- a/pcsx2-qt/Settings/GameListSettingsWidget.ui +++ b/pcsx2-qt/Settings/GameListSettingsWidget.ui @@ -23,6 +23,22 @@ 0 + + + + Display + + + + + + Prefer English Titles + + + + + + diff --git a/pcsx2/GameList.h b/pcsx2/GameList.h index 8003a06afd..cd8e688a2d 100644 --- a/pcsx2/GameList.h +++ b/pcsx2/GameList.h @@ -97,8 +97,17 @@ namespace GameList std::time_t last_played_time = 0; std::time_t total_played_time = 0; - const std::string& GetTitleEN() const { return title_en.empty() ? title : title_en; } - const std::string& GetTitleSort() const { return title_sort.empty() ? title : title_sort; } + const std::string& GetTitle(bool force_en = false) const + { + return title_en.empty() || !force_en ? title : title_en; + } + const std::string& GetTitleSort(bool force_en = false) const + { + // If there's a separate EN title, then title_sort is in the wrong language and we can't use it + if (force_en && !title_en.empty()) + return title_en; + return title_sort.empty() ? title : title_sort; + } u32 crc = 0; diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 8521749e5b..8bbc978484 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -2460,7 +2460,7 @@ void FullscreenUI::DrawSettingsWindow() if (s_game_settings_entry) { NavTitle(SmallString::from_fmt( - "{} ({})", Host::TranslateToCString(TR_CONTEXT, titles[static_cast(pages[index])]), s_game_settings_entry->GetTitleEN())); + "{} ({})", Host::TranslateToCString(TR_CONTEXT, titles[static_cast(pages[index])]), s_game_settings_entry->GetTitle(true))); } else { @@ -2575,8 +2575,8 @@ void FullscreenUI::DrawSummarySettingsPage() if (s_game_settings_entry) { - if (MenuButton(FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"), s_game_settings_entry->GetTitleEN().c_str(), true)) - CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."), s_game_settings_entry->GetTitleEN()); + if (MenuButton(FSUI_ICONSTR(ICON_FA_WINDOW_MAXIMIZE, "Title"), s_game_settings_entry->GetTitle(true).c_str(), true)) + CopyTextToClipboard(FSUI_STR("Game title copied to clipboard."), s_game_settings_entry->GetTitle(true)); if (MenuButton(FSUI_ICONSTR(ICON_FA_PAGER, "Serial"), s_game_settings_entry->serial.c_str(), true)) CopyTextToClipboard(FSUI_STR("Game serial copied to clipboard."), s_game_settings_entry->serial); if (MenuButton(FSUI_ICONSTR(ICON_FA_CODE, "CRC"), fmt::format("{:08X}", s_game_settings_entry->crc).c_str(), true)) @@ -5438,7 +5438,7 @@ void FullscreenUI::PopulateGameListEntryList() } // fallback to title when all else is equal - const int res = StringUtil::Strcasecmp(lhs->GetTitleEN().c_str(), rhs->GetTitleEN().c_str()); + const int res = StringUtil::Strcasecmp(lhs->GetTitleSort(true).c_str(), rhs->GetTitleSort(true).c_str()); return reverse ? (res > 0) : (res < 0); }); } @@ -5575,7 +5575,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) ImGui::PushFont(g_large_font); // TODO: Fix font fallback issues and enable native-language titles - ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry->GetTitleEN().c_str(), entry->GetTitleEN().c_str() + entry->GetTitleEN().size(), nullptr, + ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, entry->GetTitle(true).c_str(), entry->GetTitle(true).c_str() + entry->GetTitle(true).size(), nullptr, ImVec2(0.0f, 0.0f), &title_bb); ImGui::PopFont(); @@ -5635,11 +5635,11 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size) { // title ImGui::PushFont(g_large_font); - const std::string_view title(std::string_view(selected_entry->GetTitleEN()).substr(0, 37)); + const std::string_view title(std::string_view(selected_entry->GetTitle(true)).substr(0, 37)); text_width = ImGui::CalcTextSize(title.data(), title.data() + title.length(), false, work_width).x; ImGui::SetCursorPosX((work_width - text_width) / 2.0f); ImGui::TextWrapped( - "%.*s%s", static_cast(title.size()), title.data(), (title.length() == selected_entry->GetTitleEN().length()) ? "" : "..."); + "%.*s%s", static_cast(title.size()), title.data(), (title.length() == selected_entry->GetTitle(true).length()) ? "" : "..."); ImGui::PopFont(); ImGui::PushFont(g_medium_font); @@ -5785,9 +5785,9 @@ void FullscreenUI::DrawGameGrid(const ImVec2& heading_size) ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255)); const ImRect title_bb(ImVec2(bb.Min.x, bb.Min.y + image_height + title_spacing), bb.Max); - const std::string_view title(std::string_view(entry->GetTitleEN()).substr(0, 31)); + const std::string_view title(std::string_view(entry->GetTitle(true)).substr(0, 31)); draw_title.clear(); - fmt::format_to(std::back_inserter(draw_title), "{}{}", title, (title.length() == entry->GetTitleEN().length()) ? "" : "..."); + fmt::format_to(std::back_inserter(draw_title), "{}{}", title, (title.length() == entry->GetTitle(true).length()) ? "" : "..."); ImGui::PushFont(g_medium_font); ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, draw_title.c_str(), draw_title.c_str() + draw_title.length(), nullptr, ImVec2(0.5f, 0.0f), &title_bb); @@ -5841,7 +5841,7 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) }; const bool has_resume_state = VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1); - OpenChoiceDialog(entry->GetTitleEN().c_str(), false, std::move(options), + OpenChoiceDialog(entry->GetTitle(true).c_str(), false, std::move(options), [has_resume_state, entry_path = entry->path, entry_serial = entry->serial](s32 index, const std::string& title, bool checked) { switch (index) {