Merge branch 'master' of https://github.com/dolphin-emu/dolphin into dolphin-emu-master

This commit is contained in:
Nayla Hanegan 2024-08-23 13:38:17 -04:00
commit 7e6752e8fc
516 changed files with 60670 additions and 270317 deletions

View file

@ -89,8 +89,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
// in your translation, please use the type of curly quotes that's appropriate for
// your language. If you aren't sure which type is appropriate, see
// https://en.wikipedia.org/wiki/Quotation_mark#Specific_language_features
tr("\u00A9 2003-2015+ Dolphin Team. "
"\u201cGameCube\u201d and \u201cWii\u201d are "
tr("\u00A9 2003-2024+ Dolphin Team. \u201cGameCube\u201d and \u201cWii\u201d are "
"trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.")));
QLabel* logo = new QLabel();

View file

@ -4,10 +4,12 @@
#ifdef USE_RETRO_ACHIEVEMENTS
#include "DolphinQt/Achievements/AchievementBox.h"
#include <QByteArray>
#include <QDateTime>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QSizePolicy>
#include <QVBoxLayout>
#include <QWidget>
@ -18,6 +20,8 @@
#include "DolphinQt/QtUtils/FromStdString.h"
static constexpr size_t PROGRESS_LENGTH = 24;
AchievementBox::AchievementBox(QWidget* parent, rc_client_achievement_t* achievement)
: QGroupBox(parent), m_achievement(achievement)
{
@ -27,23 +31,39 @@ AchievementBox::AchievementBox(QWidget* parent, rc_client_achievement_t* achieve
m_badge = new QLabel();
QLabel* title = new QLabel(QString::fromUtf8(achievement->title, strlen(achievement->title)));
title->setWordWrap(true);
title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QLabel* description =
new QLabel(QString::fromUtf8(achievement->description, strlen(achievement->description)));
description->setWordWrap(true);
description->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QLabel* points = new QLabel(tr("%1 points").arg(achievement->points));
m_status = new QLabel();
m_progress_bar = new QProgressBar();
QSizePolicy sp_retain = m_progress_bar->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
m_progress_bar->setSizePolicy(sp_retain);
m_progress_label = new QLabel();
m_progress_label->setStyleSheet(QStringLiteral("background-color:transparent;"));
m_progress_label->setAlignment(Qt::AlignCenter);
QVBoxLayout* a_col_left = new QVBoxLayout();
a_col_left->addSpacerItem(new QSpacerItem(0, 0));
a_col_left->addWidget(m_badge);
a_col_left->addSpacerItem(new QSpacerItem(0, 0));
a_col_left->setSizeConstraint(QLayout::SetFixedSize);
a_col_left->setAlignment(Qt::AlignCenter);
QVBoxLayout* a_col_right = new QVBoxLayout();
a_col_right->addWidget(title);
a_col_right->addWidget(description);
a_col_right->addWidget(points);
a_col_right->addWidget(m_status);
a_col_right->addWidget(m_progress_bar);
QVBoxLayout* a_prog_layout = new QVBoxLayout(m_progress_bar);
a_prog_layout->setContentsMargins(0, 0, 0, 0);
a_prog_layout->addWidget(m_progress_label);
QHBoxLayout* a_total = new QHBoxLayout();
a_total->addWidget(m_badge);
a_total->addLayout(a_col_left);
a_total->addLayout(a_col_right);
setLayout(a_total);
@ -52,47 +72,65 @@ AchievementBox::AchievementBox(QWidget* parent, rc_client_achievement_t* achieve
void AchievementBox::UpdateData()
{
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
const auto& badge = AchievementManager::GetInstance().GetAchievementBadge(
m_achievement->id, m_achievement->state != RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED);
std::string_view color = AchievementManager::GRAY;
if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE)
color = AchievementManager::GOLD;
else if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE)
color = AchievementManager::BLUE;
if (Config::Get(Config::RA_BADGES_ENABLED) && badge.name != "")
{
QImage i_badge{};
if (i_badge.loadFromData(&badge.badge.front(), static_cast<int>(badge.badge.size())))
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
// rc_client guarantees m_achievement will be valid as long as the game is loaded
if (!AchievementManager::GetInstance().IsGameLoaded())
return;
const auto& badge = AchievementManager::GetInstance().GetAchievementBadge(
m_achievement->id, !m_achievement->unlocked);
std::string_view color = AchievementManager::GRAY;
if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE)
color = AchievementManager::GOLD;
else if (m_achievement->unlocked & RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE)
color = AchievementManager::BLUE;
QImage i_badge(&badge.data.front(), badge.width, badge.height, QImage::Format_RGBA8888);
m_badge->setPixmap(
QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_badge->adjustSize();
m_badge->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
if (m_achievement->unlocked)
{
m_badge->setPixmap(QPixmap::fromImage(i_badge).scaled(64, 64, Qt::KeepAspectRatio,
Qt::SmoothTransformation));
m_badge->adjustSize();
m_badge->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
if (m_achievement->unlock_time != 0)
{
m_status->setText(
// i18n: %1 is a date/time.
tr("Unlocked at %1")
.arg(QDateTime::fromSecsSinceEpoch(m_achievement->unlock_time).toString()));
}
else
{
m_status->setText(tr("Unlocked"));
}
}
else
{
m_status->setText(tr("Locked"));
}
}
else
{
m_badge->setText({});
}
if (m_achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED)
{
m_status->setText(
tr("Unlocked at %1")
.arg(QDateTime::fromSecsSinceEpoch(m_achievement->unlock_time).toString()));
}
else
{
m_status->setText(tr("Locked"));
}
UpdateProgress();
}
void AchievementBox::UpdateProgress()
{
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
// rc_client guarantees m_achievement will be valid as long as the game is loaded
if (!AchievementManager::GetInstance().IsGameLoaded())
return;
if (m_achievement->measured_percent > 0.000)
{
m_progress_bar->setRange(0, 100);
m_progress_bar->setValue(m_achievement->measured_percent);
m_progress_bar->setValue(m_achievement->unlocked ? 100 : m_achievement->measured_percent);
m_progress_bar->setTextVisible(false);
m_progress_label->setText(
QString::fromUtf8(m_achievement->measured_progress,
qstrnlen(m_achievement->measured_progress, PROGRESS_LENGTH)));
m_progress_label->setVisible(!m_achievement->unlocked);
m_progress_bar->setVisible(true);
}
else

View file

@ -20,11 +20,13 @@ class AchievementBox final : public QGroupBox
public:
explicit AchievementBox(QWidget* parent, rc_client_achievement_t* achievement);
void UpdateData();
void UpdateProgress();
private:
QLabel* m_badge;
QLabel* m_status;
QProgressBar* m_progress_bar;
QLabel* m_progress_label;
rc_client_achievement_t* m_achievement;
};

View file

@ -27,11 +27,18 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare
m_name = new QLabel();
m_points = new QLabel();
m_game_progress = new QProgressBar();
m_progress_label = new QLabel();
m_rich_presence = new QLabel();
m_name->setWordWrap(true);
m_points->setWordWrap(true);
m_rich_presence->setWordWrap(true);
QSizePolicy sp_retain = m_game_progress->sizePolicy();
sp_retain.setRetainSizeWhenHidden(true);
m_game_progress->setSizePolicy(sp_retain);
m_game_progress->setTextVisible(false);
m_progress_label->setStyleSheet(QStringLiteral("background-color:transparent;"));
m_progress_label->setAlignment(Qt::AlignCenter);
QVBoxLayout* icon_col = new QVBoxLayout();
icon_col->addWidget(m_user_icon);
@ -41,6 +48,9 @@ AchievementHeaderWidget::AchievementHeaderWidget(QWidget* parent) : QWidget(pare
text_col->addWidget(m_points);
text_col->addWidget(m_game_progress);
text_col->addWidget(m_rich_presence);
QVBoxLayout* prog_layout = new QVBoxLayout(m_game_progress);
prog_layout->setContentsMargins(0, 0, 0, 0);
prog_layout->addWidget(m_progress_label);
QHBoxLayout* header_layout = new QHBoxLayout();
header_layout->addLayout(icon_col);
header_layout->addLayout(text_col);
@ -68,24 +78,23 @@ void AchievementHeaderWidget::UpdateData()
QString user_name = QtUtils::FromStdString(instance.GetPlayerDisplayName());
QString game_name = QtUtils::FromStdString(instance.GetGameDisplayName());
AchievementManager::BadgeStatus player_badge = instance.GetPlayerBadge();
AchievementManager::BadgeStatus game_badge = instance.GetGameBadge();
const AchievementManager::Badge& player_badge = instance.GetPlayerBadge();
const AchievementManager::Badge& game_badge = instance.GetGameBadge();
m_user_icon->setVisible(false);
m_user_icon->clear();
m_user_icon->setText({});
if (Config::Get(Config::RA_BADGES_ENABLED) && !player_badge.name.empty())
if (!player_badge.data.empty())
{
QImage i_user_icon{};
if (i_user_icon.loadFromData(&player_badge.badge.front(), (int)player_badge.badge.size()))
{
m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_user_icon->adjustSize();
m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent"));
m_user_icon->setVisible(true);
}
QImage i_user_icon(player_badge.data.data(), player_badge.width, player_badge.height,
QImage::Format_RGBA8888);
m_user_icon->setPixmap(QPixmap::fromImage(i_user_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
m_user_icon->adjustSize();
m_user_icon->setStyleSheet(QStringLiteral("border: 4px solid transparent"));
m_user_icon->setVisible(true);
m_game_icon->setVisible(false);
m_game_icon->clear();
m_game_icon->setText({});
@ -94,26 +103,22 @@ void AchievementHeaderWidget::UpdateData()
{
rc_client_user_game_summary_t game_summary;
rc_client_get_user_game_summary(instance.GetClient(), &game_summary);
if (Config::Get(Config::RA_BADGES_ENABLED) && !game_badge.name.empty())
if (!game_badge.data.empty())
{
QImage i_game_icon{};
if (i_game_icon.loadFromData(&game_badge.badge.front(), (int)game_badge.badge.size()))
{
m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
m_game_icon->adjustSize();
std::string_view color = AchievementManager::GRAY;
if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements)
{
color =
instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE;
}
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
m_game_icon->setVisible(true);
}
QImage i_game_icon(game_badge.data.data(), game_badge.width, game_badge.height,
QImage::Format_RGBA8888);
m_game_icon->setPixmap(QPixmap::fromImage(i_game_icon)
.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
m_game_icon->adjustSize();
std::string_view color = AchievementManager::GRAY;
if (game_summary.num_core_achievements == game_summary.num_unlocked_achievements)
{
color = instance.IsHardcoreModeActive() ? AchievementManager::GOLD : AchievementManager::BLUE;
}
m_game_icon->setStyleSheet(
QStringLiteral("border: 4px solid %1").arg(QtUtils::FromStdString(color)));
m_game_icon->setVisible(true);
m_name->setText(tr("%1 is playing %2").arg(user_name).arg(game_name));
m_points->setText(tr("%1 has unlocked %2/%3 achievements worth %4/%5 points")
@ -123,10 +128,15 @@ void AchievementHeaderWidget::UpdateData()
.arg(game_summary.points_unlocked)
.arg(game_summary.points_core));
m_game_progress->setRange(0, game_summary.num_core_achievements);
if (!m_game_progress->isVisible())
m_game_progress->setVisible(true);
// This ensures that 0/0 renders as empty instead of full
m_game_progress->setRange(
0, (game_summary.num_core_achievements == 0) ? 1 : game_summary.num_core_achievements);
m_game_progress->setVisible(true);
m_game_progress->setValue(game_summary.num_unlocked_achievements);
m_progress_label->setVisible(true);
m_progress_label->setText(tr("%1/%2")
.arg(game_summary.num_unlocked_achievements)
.arg(game_summary.num_core_achievements));
m_rich_presence->setText(QString::fromUtf8(instance.GetRichPresence().data()));
m_rich_presence->setVisible(true);
}
@ -136,6 +146,7 @@ void AchievementHeaderWidget::UpdateData()
m_points->setText(tr("%1 points").arg(instance.GetPlayerScore()));
m_game_progress->setVisible(false);
m_progress_label->setVisible(false);
m_rich_presence->setVisible(false);
}
}

View file

@ -25,6 +25,7 @@ private:
QLabel* m_name;
QLabel* m_points;
QProgressBar* m_game_progress;
QLabel* m_progress_label;
QLabel* m_rich_presence;
QGroupBox* m_header_box;
};

View file

@ -30,6 +30,7 @@ AchievementLeaderboardWidget::AchievementLeaderboardWidget(QWidget* parent) : QW
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop);
layout->addWidget(m_common_box);
layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(layout);
}
@ -44,39 +45,39 @@ void AchievementLeaderboardWidget::UpdateData(bool clean_all)
return;
auto* client = instance.GetClient();
auto* leaderboard_list =
rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING);
u32 row = 0;
for (u32 bucket = 0; bucket < leaderboard_list->num_buckets; bucket++)
{
const auto& leaderboard_bucket = leaderboard_list->buckets[bucket];
m_common_layout->addWidget(new QLabel(tr(leaderboard_bucket.label)), row, 0);
row += 2;
for (u32 board = 0; board < leaderboard_bucket.num_leaderboards; board++)
{
const auto* leaderboard = leaderboard_bucket.leaderboards[board];
m_leaderboard_order[leaderboard->id] = row;
QLabel* a_title = new QLabel(QString::fromUtf8(leaderboard->title));
a_title->setWordWrap(true);
a_title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QLabel* a_description = new QLabel(QString::fromUtf8(leaderboard->description));
a_description->setWordWrap(true);
a_description->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QVBoxLayout* a_col_left = new QVBoxLayout();
a_col_left->addWidget(a_title);
a_col_left->addWidget(a_description);
if (row > 0)
{
QFrame* a_divider = new QFrame();
a_divider->setFrameShape(QFrame::HLine);
m_common_layout->addWidget(a_divider, row - 1, 0);
}
QFrame* a_divider = new QFrame();
a_divider->setFrameShape(QFrame::HLine);
m_common_layout->addWidget(a_divider, row - 1, 0);
m_common_layout->addLayout(a_col_left, row, 0);
for (size_t ix = 0; ix < 4; ix++)
{
QVBoxLayout* a_col = new QVBoxLayout();
for (size_t jx = 0; jx < 3; jx++)
a_col->addWidget(new QLabel(QStringLiteral("---")));
if (row > 0)
{
QFrame* a_divider = new QFrame();
a_divider->setFrameShape(QFrame::HLine);
m_common_layout->addWidget(a_divider, row - 1, static_cast<int>(ix) + 1);
}
QFrame* a_divider_2 = new QFrame();
a_divider_2->setFrameShape(QFrame::HLine);
m_common_layout->addWidget(a_divider_2, row - 1, static_cast<int>(ix) + 1);
m_common_layout->addLayout(a_col, row, static_cast<int>(ix) + 1);
}
row += 2;
@ -86,7 +87,7 @@ void AchievementLeaderboardWidget::UpdateData(bool clean_all)
}
for (auto row : m_leaderboard_order)
{
UpdateRow(row.second);
UpdateRow(row.first);
}
}
@ -97,7 +98,7 @@ void AchievementLeaderboardWidget::UpdateData(
{
if (update_ids.contains(row.first))
{
UpdateRow(row.second);
UpdateRow(row.first);
}
}
}

View file

@ -33,6 +33,7 @@ AchievementProgressWidget::AchievementProgressWidget(QWidget* parent) : QWidget(
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop);
layout->addWidget(m_common_box);
layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(layout);
}
@ -42,32 +43,51 @@ void AchievementProgressWidget::UpdateData(bool clean_all)
{
m_achievement_boxes.clear();
ClearLayoutRecursively(m_common_layout);
auto& instance = AchievementManager::GetInstance();
if (!instance.IsGameLoaded())
return;
auto* client = instance.GetClient();
auto* achievement_list = rc_client_create_achievement_list(
client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
for (u32 ix = 0; ix < achievement_list->num_buckets; ix++)
{
for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++)
{
auto* achievement = achievement_list->buckets[ix].achievements[jx];
m_achievement_boxes[achievement->id] = std::make_shared<AchievementBox>(this, achievement);
m_common_layout->addWidget(m_achievement_boxes[achievement->id].get());
}
}
rc_client_destroy_achievement_list(achievement_list);
}
else
{
for (auto box : m_achievement_boxes)
while (auto* item = m_common_layout->takeAt(0))
{
box.second->UpdateData();
auto* widget = item->widget();
m_common_layout->removeWidget(widget);
if (std::strcmp(widget->metaObject()->className(), "QLabel") == 0)
{
widget->deleteLater();
delete item;
}
}
}
auto& instance = AchievementManager::GetInstance();
if (!instance.IsGameLoaded())
return;
auto* client = instance.GetClient();
auto* achievement_list =
rc_client_create_achievement_list(client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
if (!achievement_list)
return;
for (u32 ix = 0; ix < achievement_list->num_buckets; ix++)
{
m_common_layout->addWidget(new QLabel(tr(achievement_list->buckets[ix].label)));
for (u32 jx = 0; jx < achievement_list->buckets[ix].num_achievements; jx++)
{
auto* achievement = achievement_list->buckets[ix].achievements[jx];
auto box_itr = m_achievement_boxes.lower_bound(achievement->id);
if (box_itr != m_achievement_boxes.end() && box_itr->first == achievement->id)
{
box_itr->second->UpdateProgress();
m_common_layout->addWidget(box_itr->second.get());
}
else
{
const auto new_box_itr = m_achievement_boxes.try_emplace(
box_itr, achievement->id, std::make_shared<AchievementBox>(this, achievement));
m_common_layout->addWidget(new_box_itr->second.get());
}
}
}
rc_client_destroy_achievement_list(achievement_list);
}
void AchievementProgressWidget::UpdateData(

View file

@ -105,11 +105,6 @@ void AchievementSettingsWidget::CreateLayout()
tr("Enable progress notifications on achievements.<br><br>Displays a brief popup message "
"whenever the player makes progress on an achievement that tracks an accumulated value, "
"such as 60 out of 120 stars."));
m_common_badges_enabled_input = new ToolTipCheckBox(tr("Enable Achievement Badges"));
m_common_badges_enabled_input->SetDescription(
tr("Enable achievement badges.<br><br>Displays icons for the player, game, and achievements. "
"Simple visual option, but will require a small amount of extra memory and time to "
"download the images."));
m_common_layout->addWidget(m_common_integration_enabled_input);
m_common_layout->addWidget(m_common_username_label);
@ -119,17 +114,18 @@ void AchievementSettingsWidget::CreateLayout()
m_common_layout->addWidget(m_common_login_button);
m_common_layout->addWidget(m_common_logout_button);
m_common_layout->addWidget(m_common_login_failed);
// i18n: Settings that affect the functionality of unlocking achievements.
m_common_layout->addWidget(new QLabel(tr("Function Settings")));
m_common_layout->addWidget(m_common_hardcore_enabled_input);
m_common_layout->addWidget(m_common_unofficial_enabled_input);
m_common_layout->addWidget(m_common_encore_enabled_input);
m_common_layout->addWidget(m_common_spectator_enabled_input);
// i18n: Settings that affect how achievements are displayed while playing.
m_common_layout->addWidget(new QLabel(tr("Display Settings")));
#ifdef USE_DISCORD_PRESENCE
m_common_layout->addWidget(m_common_discord_presence_enabled_input);
#endif // USE_DISCORD_PRESENCE
m_common_layout->addWidget(m_common_progress_enabled_input);
m_common_layout->addWidget(m_common_badges_enabled_input);
m_common_layout->setAlignment(Qt::AlignTop);
setLayout(m_common_layout);
@ -139,8 +135,8 @@ void AchievementSettingsWidget::ConnectWidgets()
{
connect(m_common_integration_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleRAIntegration);
connect(m_common_login_button, &QPushButton::pressed, this, &AchievementSettingsWidget::Login);
connect(m_common_logout_button, &QPushButton::pressed, this, &AchievementSettingsWidget::Logout);
connect(m_common_login_button, &QPushButton::clicked, this, &AchievementSettingsWidget::Login);
connect(m_common_logout_button, &QPushButton::clicked, this, &AchievementSettingsWidget::Logout);
connect(m_common_hardcore_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleHardcore);
connect(m_common_unofficial_enabled_input, &QCheckBox::toggled, this,
@ -153,8 +149,6 @@ void AchievementSettingsWidget::ConnectWidgets()
&AchievementSettingsWidget::ToggleDiscordPresence);
connect(m_common_progress_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleProgress);
connect(m_common_badges_enabled_input, &QCheckBox::toggled, this,
&AchievementSettingsWidget::ToggleBadges);
}
void AchievementSettingsWidget::OnControllerInterfaceConfigure()
@ -184,6 +178,15 @@ void AchievementSettingsWidget::LoadSettings()
SignalBlocking(m_common_login_button)->setVisible(logged_out);
SignalBlocking(m_common_login_button)
->setEnabled(enabled && !Core::IsRunning(Core::System::GetInstance()));
if (enabled && Core::IsRunning(Core::System::GetInstance()))
{
SignalBlocking(m_common_login_button)->setText(tr("To log in, stop the current emulation."));
}
else
{
SignalBlocking(m_common_login_button)->setText(tr("Log In"));
}
SignalBlocking(m_common_logout_button)->setVisible(!logged_out);
SignalBlocking(m_common_logout_button)->setEnabled(enabled);
@ -214,9 +217,6 @@ void AchievementSettingsWidget::LoadSettings()
SignalBlocking(m_common_progress_enabled_input)
->setChecked(Config::Get(Config::RA_PROGRESS_ENABLED));
SignalBlocking(m_common_progress_enabled_input)->setEnabled(enabled);
SignalBlocking(m_common_badges_enabled_input)->setChecked(Config::Get(Config::RA_BADGES_ENABLED));
SignalBlocking(m_common_badges_enabled_input)->setEnabled(enabled);
}
void AchievementSettingsWidget::SaveSettings()
@ -235,7 +235,6 @@ void AchievementSettingsWidget::SaveSettings()
m_common_discord_presence_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_PROGRESS_ENABLED,
m_common_progress_enabled_input->isChecked());
Config::SetBaseOrCurrent(Config::RA_BADGES_ENABLED, m_common_badges_enabled_input->isChecked());
Config::Save();
}
@ -249,7 +248,10 @@ void AchievementSettingsWidget::ToggleRAIntegration()
else
instance.Shutdown();
if (Config::Get(Config::RA_HARDCORE_ENABLED))
{
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
emit Settings::Instance().HardcoreStateChanged();
}
}
void AchievementSettingsWidget::Login()
@ -275,10 +277,11 @@ void AchievementSettingsWidget::ToggleHardcore()
if (Config::Get(Config::MAIN_EMULATION_SPEED) < 1.0f)
Config::SetBaseOrCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
Config::SetBaseOrCurrent(Config::FREE_LOOK_ENABLED, false);
Settings::Instance().SetCheatsEnabled(false);
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, false);
Settings::Instance().SetDebugModeEnabled(false);
}
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
emit Settings::Instance().HardcoreStateChanged();
}
void AchievementSettingsWidget::ToggleUnofficial()
@ -308,11 +311,4 @@ void AchievementSettingsWidget::ToggleProgress()
SaveSettings();
}
void AchievementSettingsWidget::ToggleBadges()
{
SaveSettings();
AchievementManager::GetInstance().FetchPlayerBadge();
AchievementManager::GetInstance().FetchGameBadges();
}
#endif // USE_RETRO_ACHIEVEMENTS

View file

@ -38,7 +38,6 @@ private:
void ToggleSpectator();
void ToggleDiscordPresence();
void ToggleProgress();
void ToggleBadges();
QGroupBox* m_common_box;
QVBoxLayout* m_common_layout;
@ -56,7 +55,6 @@ private:
ToolTipCheckBox* m_common_spectator_enabled_input;
ToolTipCheckBox* m_common_discord_presence_enabled_input;
ToolTipCheckBox* m_common_progress_enabled_input;
ToolTipCheckBox* m_common_badges_enabled_input;
};
#endif // USE_RETRO_ACHIEVEMENTS

View file

@ -7,6 +7,8 @@
#include <mutex>
#include <QDialogButtonBox>
#include <QScrollArea>
#include <QScrollBar>
#include <QTabWidget>
#include <QVBoxLayout>
@ -34,6 +36,8 @@ AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
});
});
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[this] { m_settings_widget->UpdateData(); });
connect(&Settings::Instance(), &Settings::HardcoreStateChanged, this,
[this] { AchievementsWindow::UpdateData({.all = true}); });
}
@ -81,6 +85,8 @@ void AchievementsWindow::UpdateData(AchievementManager::UpdatedItems updated_ite
m_header_widget->UpdateData();
m_progress_widget->UpdateData(true);
m_leaderboard_widget->UpdateData(true);
static_cast<QScrollArea*>(m_tab_widget->widget(1))->verticalScrollBar()->setValue(0);
static_cast<QScrollArea*>(m_tab_widget->widget(2))->verticalScrollBar()->setValue(0);
}
else
{
@ -89,7 +95,7 @@ void AchievementsWindow::UpdateData(AchievementManager::UpdatedItems updated_ite
{
m_header_widget->UpdateData();
}
if (updated_items.all_achievements)
if (updated_items.all_achievements || updated_items.rich_presence)
m_progress_widget->UpdateData(false);
else if (updated_items.achievements.size() > 0)
m_progress_widget->UpdateData(updated_items.achievements);

View file

@ -566,15 +566,14 @@ endif()
if(APPLE)
include(BundleUtilities)
set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Dolphin.app)
set(BUNDLE_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/DolphinQt.app)
# Ask for an application bundle.
set_target_properties(dolphin-mpn PROPERTIES
MACOSX_BUNDLE true
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/DolphinEmu.entitlements"
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep --options=runtime"
OUTPUT_NAME Dolphin
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
OUTPUT_NAME DolphinQt
)
# Copy qt.conf into the bundle
@ -638,56 +637,18 @@ if(APPLE)
endif()
if(MACOS_CODE_SIGNING)
# Code sign make file builds
add_custom_command(TARGET dolphin-mpn POST_BUILD
COMMAND /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu$<$<CONFIG:Debug>:Debug>.entitlements" "$<TARGET_BUNDLE_DIR:dolphin-mpn>")
add_custom_command(TARGET dolphin-mpn
POST_BUILD
COMMAND "${CMAKE_SOURCE_DIR}/Tools/mac-codesign.sh"
"-e" "${CMAKE_CURRENT_SOURCE_DIR}/DolphinEmu$<$<CONFIG:Debug>:Debug>.entitlements"
"${MACOS_CODE_SIGNING_IDENTITY}"
"${BUNDLE_PATH}"
)
endif()
else()
install(TARGETS dolphin-mpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND STEAM)
# Set that we want ORIGIN in FLAGS.
# We also want RPATH, not RUNPATH, so disable the new tags.
target_link_options(dolphin-mpn
PRIVATE
LINKER:-z,origin
LINKER:--disable-new-dtags
)
# For Steam Runtime builds, our Qt shared libraries will be in a "lib" folder.
set_target_properties(dolphin-mpn PROPERTIES
BUILD_WITH_INSTALL_RPATH true
INSTALL_RPATH "\$ORIGIN/lib"
)
add_custom_command(TARGET dolphin-mpn POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/lib"
COMMAND cp "${Qt6_DIR}/../../LICENSE.*" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/lib"
COMMAND cp -P "${Qt6_DIR}/../../*.so*" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/lib"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${Qt6_DIR}/../../../plugins" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/plugins"
)
# Copy qt.conf
target_sources(dolphin-mpn PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf")
add_custom_command(TARGET dolphin-mpn POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/qt.conf" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/qt.conf"
)
# Mark all data files as resources
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/Data/Sys")
file(GLOB_RECURSE resources RELATIVE "${CMAKE_SOURCE_DIR}/Data" "${CMAKE_SOURCE_DIR}/Data/Sys/*")
foreach(res ${resources})
target_sources(dolphin-mpn PRIVATE "${CMAKE_SOURCE_DIR}/Data/${res}")
source_group("Resources" FILES "${CMAKE_SOURCE_DIR}/Data/${res}")
endforeach()
# Copy Sys folder
add_custom_command(TARGET dolphin-mpn POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/Data/Sys" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys"
)
endif()
if(USE_MGBA)
target_sources(dolphin-mpn PRIVATE
GBAHost.cpp

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/CheatSearchFactoryWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include <string>
#include <vector>
@ -26,6 +25,7 @@
#include "Core/System.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
CheatSearchFactoryWidget::CheatSearchFactoryWidget()
{

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/CheatSearchWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include <functional>
#include <optional>
@ -42,6 +41,7 @@
#include "DolphinQt/Config/CheatCodeEditor.h"
#include "DolphinQt/Config/CheatWarningWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include "DolphinQt/Settings.h"
#include "UICommon/GameFile.h"

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/ARCodeWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include <algorithm>
#include <utility>
@ -25,6 +24,7 @@
#include "DolphinQt/Config/HardcoreWarningWidget.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include "UICommon/GameFile.h"

View file

@ -30,3 +30,60 @@ void ConfigChoice::Update(int choice)
{
Config::SetBaseOrCurrent(m_setting, choice);
}
ConfigStringChoice::ConfigStringChoice(const std::vector<std::string>& options,
const Config::Info<std::string>& setting)
: m_setting(setting), m_text_is_data(true)
{
for (const auto& op : options)
addItem(QString::fromStdString(op));
Connect();
Load();
}
ConfigStringChoice::ConfigStringChoice(const std::vector<std::pair<QString, QString>>& options,
const Config::Info<std::string>& setting)
: m_setting(setting), m_text_is_data(false)
{
for (const auto& [option_text, option_data] : options)
addItem(option_text, option_data);
Connect();
Load();
}
void ConfigStringChoice::Connect()
{
const auto on_config_changed = [this]() {
QFont bf = font();
bf.setBold(Config::GetActiveLayerForConfig(m_setting) != Config::LayerType::Base);
setFont(bf);
Load();
};
connect(&Settings::Instance(), &Settings::ConfigChanged, this, on_config_changed);
connect(this, &QComboBox::currentIndexChanged, this, &ConfigStringChoice::Update);
}
void ConfigStringChoice::Update(int index)
{
if (m_text_is_data)
{
Config::SetBaseOrCurrent(m_setting, itemText(index).toStdString());
}
else
{
Config::SetBaseOrCurrent(m_setting, itemData(index).toString().toStdString());
}
}
void ConfigStringChoice::Load()
{
const QString setting_value = QString::fromStdString(Config::Get(m_setting));
const int index = m_text_is_data ? findText(setting_value) : findData(setting_value);
const QSignalBlocker blocker(this);
setCurrentIndex(index);
}

View file

@ -3,6 +3,10 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h"
#include "Common/Config/Config.h"
@ -18,3 +22,21 @@ private:
Config::Info<int> m_setting;
};
class ConfigStringChoice : public ToolTipComboBox
{
Q_OBJECT
public:
ConfigStringChoice(const std::vector<std::string>& options,
const Config::Info<std::string>& setting);
ConfigStringChoice(const std::vector<std::pair<QString, QString>>& options,
const Config::Info<std::string>& setting);
private:
void Connect();
void Update(int index);
void Load();
Config::Info<std::string> m_setting;
bool m_text_is_data = false;
};

View file

@ -27,8 +27,13 @@ ConfigRadioInt::ConfigRadioInt(const QString& label, const Config::Info<int>& se
void ConfigRadioInt::Update()
{
if (!isChecked())
return;
Config::SetBaseOrCurrent(m_setting, m_value);
if (isChecked())
{
Config::SetBaseOrCurrent(m_setting, m_value);
emit OnSelected(m_value);
}
else
{
emit OnDeselected(m_value);
}
}

View file

@ -13,6 +13,12 @@ class ConfigRadioInt : public ToolTipRadioButton
public:
ConfigRadioInt(const QString& label, const Config::Info<int>& setting, int value);
signals:
// Since selecting a new radio button deselects the old one, ::toggled will generate two signals.
// These are convenience functions so you can receive only one signal if desired.
void OnSelected(int new_value);
void OnDeselected(int old_value);
private:
void Update();

View file

@ -213,8 +213,7 @@ void FilesystemWidget::PopulateDirectory(int partition_id, QStandardItem* root,
QString FilesystemWidget::SelectFolder()
{
return DolphinFileDialog::getExistingDirectory(this,
QObject::tr("Choose the folder to extract to"));
return DolphinFileDialog::getExistingDirectory(this, QObject::tr("Choose Folder to Extract To"));
}
void FilesystemWidget::ShowContextMenu(const QPoint&)
@ -267,7 +266,7 @@ void FilesystemWidget::ShowContextMenu(const QPoint&)
switch (type)
{
case EntryType::Disc:
menu->addAction(tr("Extract Entire Disc..."), this, [this, path] {
menu->addAction(tr("Extract Entire Disc..."), this, [this] {
auto folder = SelectFolder();
if (folder.isEmpty())
@ -300,7 +299,7 @@ void FilesystemWidget::ShowContextMenu(const QPoint&)
case EntryType::File:
menu->addAction(tr("Extract File..."), this, [this, partition, path] {
auto dest =
DolphinFileDialog::getSaveFileName(this, tr("Save File to"), QFileInfo(path).fileName());
DolphinFileDialog::getSaveFileName(this, tr("Save File To"), QFileInfo(path).fileName());
if (!dest.isEmpty())
ExtractFile(partition, path, dest);

View file

@ -143,7 +143,7 @@ void GameConfigEdit::OnSelectionChanged()
{
const QString& keyword = m_edit->textCursor().selectedText();
if (m_keyword_map.count(keyword))
if (m_keyword_map.contains(keyword))
QWhatsThis::showText(QCursor::pos(), m_keyword_map[keyword], this);
}

View file

@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/Config/GeckoCodeWidget.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include <algorithm>
#include <utility>
@ -31,6 +30,7 @@
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/QtUtils/WrapInScrollArea.h"
#include "UICommon/GameFile.h"

View file

@ -241,7 +241,7 @@ void EnhancementsWidget::ConnectWidgets()
connect(m_3d_mode, &QComboBox::currentIndexChanged, [this] {
m_block_save = true;
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
LoadPPShaders();
LoadPPShaders(static_cast<StereoMode>(m_3d_mode->currentIndex()));
m_block_save = false;
SaveSettings();
@ -250,23 +250,30 @@ void EnhancementsWidget::ConnectWidgets()
&EnhancementsWidget::ConfigureColorCorrection);
connect(m_configure_pp_effect, &QPushButton::clicked, this,
&EnhancementsWidget::ConfigurePostProcessingShader);
connect(&Settings::Instance(), &Settings::ConfigChanged, this, [this] {
const QSignalBlocker blocker(this);
m_block_save = true;
LoadPPShaders(Config::Get(Config::GFX_STEREO_MODE));
m_block_save = false;
});
}
void EnhancementsWidget::LoadPPShaders()
void EnhancementsWidget::LoadPPShaders(StereoMode stereo_mode)
{
std::vector<std::string> shaders = VideoCommon::PostProcessing::GetShaderList();
if (g_Config.stereo_mode == StereoMode::Anaglyph)
if (stereo_mode == StereoMode::Anaglyph)
{
shaders = VideoCommon::PostProcessing::GetAnaglyphShaderList();
}
else if (g_Config.stereo_mode == StereoMode::Passive)
else if (stereo_mode == StereoMode::Passive)
{
shaders = VideoCommon::PostProcessing::GetPassiveShaderList();
}
m_pp_effect->clear();
if (g_Config.stereo_mode != StereoMode::Anaglyph && g_Config.stereo_mode != StereoMode::Passive)
if (stereo_mode != StereoMode::Anaglyph && stereo_mode != StereoMode::Passive)
m_pp_effect->addItem(tr("(off)"));
auto selected_shader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
@ -283,10 +290,23 @@ void EnhancementsWidget::LoadPPShaders()
}
}
if (g_Config.stereo_mode == StereoMode::Anaglyph && !found)
m_pp_effect->setCurrentIndex(m_pp_effect->findText(QStringLiteral("dubois")));
else if (g_Config.stereo_mode == StereoMode::Passive && !found)
m_pp_effect->setCurrentIndex(m_pp_effect->findText(QStringLiteral("horizontal")));
if (!found)
{
if (stereo_mode == StereoMode::Anaglyph)
selected_shader = "dubois";
else if (stereo_mode == StereoMode::Passive)
selected_shader = "horizontal";
else
selected_shader = "";
int index = m_pp_effect->findText(QString::fromStdString(selected_shader));
if (index >= 0)
m_pp_effect->setCurrentIndex(index);
else
m_pp_effect->setCurrentIndex(0);
Config::SetBaseOrCurrent(Config::GFX_ENHANCE_POST_SHADER, selected_shader);
}
const bool supports_postprocessing = g_Config.backend_info.bSupportsPostProcessing;
m_pp_effect->setEnabled(supports_postprocessing);
@ -381,7 +401,7 @@ void EnhancementsWidget::LoadSettings()
m_configure_color_correction->setEnabled(g_Config.backend_info.bSupportsPostProcessing);
// Post Processing Shader
LoadPPShaders();
LoadPPShaders(Config::Get(Config::GFX_STEREO_MODE));
// Stereoscopy
const bool supports_stereoscopy = g_Config.backend_info.bSupportsGeometryShaders;

View file

@ -17,6 +17,7 @@ class QPushButton;
class QSlider;
class ToolTipComboBox;
class ToolTipPushButton;
enum class StereoMode : int;
class EnhancementsWidget final : public QWidget
{
@ -33,7 +34,7 @@ private:
void AddDescriptions();
void ConfigureColorCorrection();
void ConfigurePostProcessingShader();
void LoadPPShaders();
void LoadPPShaders(StereoMode stereo_mode);
// Enhancements
ConfigChoice* m_ir_combo;

View file

@ -140,7 +140,7 @@ void GraphicsModListWidget::RefreshModList()
// If no group matches the mod's features, or if the mod has no features, skip it
if (std::none_of(mod.m_features.begin(), mod.m_features.end(),
[&groups](const GraphicsModFeatureConfig& feature) {
return groups.count(feature.m_group) == 1;
return groups.contains(feature.m_group);
}))
{
continue;

View file

@ -59,8 +59,6 @@
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/InputConfig.h"
constexpr const char* PROFILES_DIR = "Profiles/";
MappingWindow::MappingWindow(QWidget* parent, Type type, int port_num)
: QDialog(parent), m_port(port_num)
{

View file

@ -177,10 +177,8 @@ void VerifyWidget::Verify()
}
verifier.Finish();
const DiscIO::VolumeVerifier::Result result = verifier.GetResult();
progress.Reset();
return result;
return verifier.GetResult();
});
SetQWidgetWindowDecorations(progress.GetRaw());
progress.GetRaw()->exec();

View file

@ -367,7 +367,7 @@ void ConvertDialog::Convert()
if (m_files.size() > 1)
{
dst_dir = DolphinFileDialog::getExistingDirectory(
this, tr("Select where you want to save the converted images"),
this, tr("Save Converted Images"),
QFileInfo(QString::fromStdString(m_files[0]->GetFilePath())).dir().absolutePath());
if (dst_dir.isEmpty())
@ -376,7 +376,7 @@ void ConvertDialog::Convert()
else
{
dst_path = DolphinFileDialog::getSaveFileName(
this, tr("Select where you want to save the converted image"),
this, tr("Save Converted Image"),
QFileInfo(QString::fromStdString(m_files[0]->GetFilePath()))
.dir()
.absoluteFilePath(

View file

@ -770,7 +770,7 @@ bool AssemblerWidget::SaveEditor(AsmEditor* editor)
QString selected_filter;
save_path = DolphinFileDialog::getSaveFileName(
this, tr("Save File to"), QString::fromStdString(default_dir),
this, tr("Save File To"), QString::fromStdString(default_dir),
QStringLiteral("%1;;%2").arg(asm_filter).arg(all_filter), &selected_filter);
if (save_path.isEmpty())

View file

@ -5,6 +5,7 @@
#include <algorithm>
#include <optional>
#include <ranges>
#include <utility>
#include <QApplication>
@ -46,6 +47,7 @@
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/Resources.h"
#include "DolphinQt/Settings.h"
class BranchWatchProxyModel final : public QSortFilterProxyModel
@ -57,6 +59,12 @@ public:
: QSortFilterProxyModel(parent), m_branch_watch(branch_watch)
{
}
~BranchWatchProxyModel() override = default;
BranchWatchProxyModel(const BranchWatchProxyModel&) = delete;
BranchWatchProxyModel(BranchWatchProxyModel&&) = delete;
BranchWatchProxyModel& operator=(const BranchWatchProxyModel&) = delete;
BranchWatchProxyModel& operator=(BranchWatchProxyModel&&) = delete;
BranchWatchTableModel* sourceModel() const
{
@ -93,7 +101,6 @@ public:
this->*member = std::nullopt;
invalidateRowsFilter();
}
void OnDelete(QModelIndexList index_list);
bool IsBranchTypeAllowed(UGeckoInstruction inst) const;
void SetInspected(const QModelIndex& index);
@ -135,27 +142,20 @@ bool BranchWatchProxyModel::filterAcceptsRow(int source_row, const QModelIndex&)
if (!m_origin_symbol_name.isEmpty())
{
if (const QVariant& symbol_name_v = sourceModel()->GetSymbolList()[source_row].origin_name;
!symbol_name_v.isValid() ||
!symbol_name_v.value<QString>().contains(m_origin_symbol_name, Qt::CaseInsensitive))
!symbol_name_v.isValid() || !static_cast<const QString*>(symbol_name_v.data())
->contains(m_origin_symbol_name, Qt::CaseInsensitive))
return false;
}
if (!m_destin_symbol_name.isEmpty())
{
if (const QVariant& symbol_name_v = sourceModel()->GetSymbolList()[source_row].destin_name;
!symbol_name_v.isValid() ||
!symbol_name_v.value<QString>().contains(m_destin_symbol_name, Qt::CaseInsensitive))
!symbol_name_v.isValid() || !static_cast<const QString*>(symbol_name_v.data())
->contains(m_destin_symbol_name, Qt::CaseInsensitive))
return false;
}
return true;
}
void BranchWatchProxyModel::OnDelete(QModelIndexList index_list)
{
std::transform(index_list.begin(), index_list.end(), index_list.begin(),
[this](const QModelIndex& index) { return mapToSource(index); });
sourceModel()->OnDelete(std::move(index_list));
}
static constexpr bool BranchSavesLR(UGeckoInstruction inst)
{
DEBUG_ASSERT(inst.OPCD == 18 || inst.OPCD == 16 ||
@ -201,7 +201,6 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
{
setWindowTitle(tr("Branch Watch Tool"));
setWindowFlags((windowFlags() | Qt::WindowMinMaxButtonsHint) & ~Qt::WindowContextHelpButtonHint);
SetQWidgetWindowDecorations(this);
setLayout([this, &ppc_symbol_db]() {
auto* main_layout = new QVBoxLayout;
@ -216,6 +215,7 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
m_table_proxy->setSourceModel(
m_table_model = new BranchWatchTableModel(m_system, m_branch_watch, ppc_symbol_db));
m_table_proxy->setSortRole(UserRole::SortRole);
m_table_proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
m_table_model->setFont(ui_settings.GetDebugFont());
connect(&ui_settings, &Settings::DebugFontChanged, m_table_model,
@ -234,6 +234,11 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
table_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
table_view->setCornerButtonEnabled(false);
table_view->verticalHeader()->hide();
table_view->setColumnWidth(Column::Instruction, 50);
table_view->setColumnWidth(Column::Condition, 50);
table_view->setColumnWidth(Column::OriginSymbol, 250);
table_view->setColumnWidth(Column::DestinSymbol, 250);
// The default column width (100 units) is fine for the rest.
QHeaderView* const horizontal_header = table_view->horizontalHeader();
horizontal_header->restoreState( // Restore column visibility state.
@ -486,25 +491,19 @@ BranchWatchDialog::BranchWatchDialog(Core::System& system, Core::BranchWatch& br
return group_box;
}());
UpdateIcons();
connect(m_timer = new QTimer, &QTimer::timeout, this, &BranchWatchDialog::OnTimeout);
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&BranchWatchDialog::OnEmulationStateChanged);
connect(&Settings::Instance(), &Settings::ThemeChanged, this,
&BranchWatchDialog::OnThemeChanged);
connect(m_table_proxy, &BranchWatchProxyModel::layoutChanged, this,
&BranchWatchDialog::UpdateStatus);
return main_layout;
}());
// FIXME: On Linux, Qt6 has recently been resetting column widths to their defaults in many
// unexpected ways. This affects all kinds of QTables in Dolphin's GUI, so to avoid it in
// this QTableView, I have deferred this operation. Any earlier, and this would be undone.
// SetQWidgetWindowDecorations was moved to before these operations for the same reason.
m_table_view->setColumnWidth(Column::Instruction, 50);
m_table_view->setColumnWidth(Column::Condition, 50);
m_table_view->setColumnWidth(Column::OriginSymbol, 250);
m_table_view->setColumnWidth(Column::DestinSymbol, 250);
// The default column width (100 units) is fine for the rest.
const auto& settings = Settings::GetQSettings();
restoreGeometry(settings.value(QStringLiteral("branchwatchdialog/geometry")).toByteArray());
}
@ -596,7 +595,7 @@ void BranchWatchDialog::OnSaveAs()
}
const QString filepath = DolphinFileDialog::getSaveFileName(
this, tr("Save Branch Watch snapshot"),
this, tr("Save Branch Watch Snapshot"),
QString::fromStdString(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX)),
tr("Text file (*.txt);;All Files (*)"));
if (filepath.isEmpty())
@ -613,7 +612,7 @@ void BranchWatchDialog::OnLoad()
void BranchWatchDialog::OnLoadFrom()
{
const QString filepath = DolphinFileDialog::getOpenFileName(
this, tr("Load Branch Watch snapshot"),
this, tr("Load Branch Watch Snapshot"),
QString::fromStdString(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX)),
tr("Text file (*.txt);;All Files (*)"), nullptr, QFileDialog::Option::ReadOnly);
if (filepath.isEmpty())
@ -700,13 +699,19 @@ void BranchWatchDialog::OnEmulationStateChanged(Core::State new_state)
Update();
}
void BranchWatchDialog::OnThemeChanged()
{
UpdateIcons();
}
void BranchWatchDialog::OnHelp()
{
ModalMessageBox::information(
this, tr("Branch Watch Tool Help (1/4)"),
tr("Branch Watch is a code-searching tool that can isolate branches tracked by the emulated "
"CPU by testing candidate branches with simple criteria. If you are familiar with Cheat "
"Engine's Ultimap, Branch Watch is similar to that.\n\n"
"Engine's Ultimap, Branch Watch is similar to that."
"\n\n"
"Press the \"Start Branch Watch\" button to activate Branch Watch. Branch Watch persists "
"across emulation sessions, and a snapshot of your progress can be saved to and loaded "
"from the User Directory to persist after Dolphin Emulator is closed. \"Save As...\" and "
@ -729,7 +734,8 @@ void BranchWatchDialog::OnHelp()
"taken since the last time it was checked. It is also possible to reduce the candidates "
"by determining whether a branch instruction has or has not been overwritten since it was "
"first hit. Filter the candidates by branch kind, branch condition, origin or destination "
"address, and origin or destination symbol name.\n\n"
"address, and origin or destination symbol name."
"\n\n"
"After enough passes and experimentation, you may be able to find function calls and "
"conditional code paths that are only taken when an action is performed in the emulated "
"software."));
@ -737,17 +743,27 @@ void BranchWatchDialog::OnHelp()
this, tr("Branch Watch Tool Help (4/4)"),
tr("Rows in the table can be left-clicked on the origin, destination, and symbol columns to "
"view the associated address in Code View. Right-clicking the selected row(s) will bring "
"up a context menu.\n\n"
"up a context menu."
"\n\n"
"If the origin, destination, or symbol columns are right-clicked, an action copy the "
"relevant address(es) to the clipboard will be available, and an action to set a "
"breakpoint at the relevant address(es) will be available. Note that, for the origin / "
"destination symbol columns, these actions will only be enabled if every row in the "
"selection has a symbol."
"\n\n"
"If the origin column of a row selection is right-clicked, an action to replace the "
"branch instruction at the origin(s) with a NOP instruction (No Operation), and an action "
"to copy the address(es) to the clipboard will be available.\n\n"
"branch instruction at the origin(s) with a NOP instruction (No Operation) will be "
"available."
"\n\n"
"If the destination column of a row selection is right-clicked, an action to replace the "
"instruction at the destination(s) with a BLR instruction (Branch to Link Register) will "
"be available, but only if the branch instruction at every origin saves the link "
"register, and an action to copy the address(es) to the clipboard will be available.\n\n"
"be available, but will only be enabled if the branch instruction at every origin updates "
"the link register."
"\n\n"
"If the origin / destination symbol column of a row selection is right-clicked, an action "
"to replace the instruction(s) at the start of the symbol with a BLR instruction will be "
"available, but only if every origin / destination symbol is found.\n\n"
"to replace the instruction at the start of the symbol(s) with a BLR instruction will be "
"available, but will only be enabled if every row in the selection has a symbol."
"\n\n"
"All context menus have the action to delete the selected row(s) from the candidates."));
}
@ -759,7 +775,7 @@ void BranchWatchDialog::OnToggleAutoSave(bool checked)
const QString filepath = DolphinFileDialog::getSaveFileName(
// i18n: If the user selects a file, Branch Watch will save to that file.
// If the user presses Cancel, Branch Watch will save to a file in the user folder.
this, tr("Select Branch Watch snapshot auto-save file (for user folder location, cancel)"),
this, tr("Select Branch Watch Snapshot Auto-Save File (for user folder location, cancel)"),
QString::fromStdString(File::GetUserPath(D_DUMPDEBUG_BRANCHWATCH_IDX)),
tr("Text file (*.txt);;All Files (*)"));
if (filepath.isEmpty())
@ -800,68 +816,18 @@ void BranchWatchDialog::OnTableClicked(const QModelIndex& index)
void BranchWatchDialog::OnTableContextMenu(const QPoint& pos)
{
if (m_table_view->horizontalHeader()->hiddenSectionCount() == Column::NumberOfColumns)
{
m_mnu_column_visibility->exec(m_table_view->viewport()->mapToGlobal(pos));
return;
}
const QModelIndex index = m_table_view->indexAt(pos);
if (!index.isValid())
return;
QModelIndexList index_list = m_table_view->selectionModel()->selectedRows(index.column());
QMenu* const menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
menu->addAction(tr("&Delete"), [this, index_list]() { OnTableDelete(std::move(index_list)); });
switch (index.column())
{
case Column::Origin:
{
QAction* const action = menu->addAction(tr("Insert &NOP"));
if (Core::GetState(m_system) != Core::State::Uninitialized)
connect(action, &QAction::triggered,
[this, index_list]() { OnTableSetNOP(std::move(index_list)); });
else
action->setEnabled(false);
menu->addAction(tr("&Copy Address"), [this, index_list = std::move(index_list)]() {
OnTableCopyAddress(std::move(index_list));
});
break;
}
case Column::Destination:
{
QAction* const action = menu->addAction(tr("Insert &BLR"));
const bool enable_action =
Core::GetState(m_system) != Core::State::Uninitialized &&
std::all_of(index_list.begin(), index_list.end(), [this](const QModelIndex& idx) {
const QModelIndex sibling = idx.siblingAtColumn(Column::Instruction);
return BranchSavesLR(m_table_proxy->data(sibling, UserRole::ClickRole).value<u32>());
});
if (enable_action)
connect(action, &QAction::triggered,
[this, index_list]() { OnTableSetBLR(std::move(index_list)); });
else
action->setEnabled(false);
menu->addAction(tr("&Copy Address"), [this, index_list = std::move(index_list)]() {
OnTableCopyAddress(std::move(index_list));
});
break;
}
case Column::OriginSymbol:
case Column::DestinSymbol:
{
QAction* const action = menu->addAction(tr("Insert &BLR at start"));
const bool enable_action =
Core::GetState(m_system) != Core::State::Uninitialized &&
std::all_of(index_list.begin(), index_list.end(), [this](const QModelIndex& idx) {
return m_table_proxy->data(idx, UserRole::ClickRole).isValid();
});
if (enable_action)
connect(action, &QAction::triggered, [this, index_list = std::move(index_list)]() {
OnTableSetBLR(std::move(index_list));
});
else
action->setEnabled(false);
break;
}
}
menu->exec(m_table_view->viewport()->mapToGlobal(pos));
m_index_list_temp = m_table_view->selectionModel()->selectedRows(index.column());
GetTableContextMenu(index)->exec(m_table_view->viewport()->mapToGlobal(pos));
m_index_list_temp.clear();
m_index_list_temp.shrink_to_fit();
}
void BranchWatchDialog::OnTableHeaderContextMenu(const QPoint& pos)
@ -869,67 +835,72 @@ void BranchWatchDialog::OnTableHeaderContextMenu(const QPoint& pos)
m_mnu_column_visibility->exec(m_table_view->horizontalHeader()->mapToGlobal(pos));
}
void BranchWatchDialog::OnTableDelete(QModelIndexList index_list)
void BranchWatchDialog::OnTableDelete()
{
m_table_proxy->OnDelete(std::move(index_list));
std::ranges::transform(
m_index_list_temp, m_index_list_temp.begin(),
[this](const QModelIndex& index) { return m_table_proxy->mapToSource(index); });
std::ranges::sort(m_index_list_temp, std::less{});
for (const auto& index : std::ranges::reverse_view{m_index_list_temp})
{
if (!index.isValid())
continue;
m_table_model->removeRow(index.row());
}
UpdateStatus();
}
void BranchWatchDialog::OnTableDeleteKeypress()
{
OnTableDelete(m_table_view->selectionModel()->selectedRows());
m_index_list_temp = m_table_view->selectionModel()->selectedRows();
OnTableDelete();
m_index_list_temp.clear();
m_index_list_temp.shrink_to_fit();
}
void BranchWatchDialog::OnTableSetBLR(QModelIndexList index_list)
void BranchWatchDialog::OnTableSetBLR()
{
for (const QModelIndex& index : index_list)
{
m_system.GetPowerPC().GetDebugInterface().SetPatch(
Core::CPUThreadGuard{m_system},
m_table_proxy->data(index, UserRole::ClickRole).value<u32>(), 0x4e800020);
m_table_proxy->SetInspected(index);
}
// TODO: This is not ideal. What I need is a signal for when memory has been changed by the GUI,
// but I cannot find one. UpdateDisasmDialog comes close, but does too much in one signal. For
// example, CodeViewWidget will scroll to the current PC when UpdateDisasmDialog is signaled. This
// seems like a pervasive issue. For example, modifying an instruction in the CodeViewWidget will
// not reflect in the MemoryViewWidget, and vice versa. Neither of these widgets changing memory
// will reflect in the JITWidget, either. At the very least, we can make sure the CodeWidget
// is updated in an acceptable way.
m_code_widget->Update();
SetStubPatches(0x4e800020);
}
void BranchWatchDialog::OnTableSetNOP(QModelIndexList index_list)
void BranchWatchDialog::OnTableSetNOP()
{
for (const QModelIndex& index : index_list)
{
m_system.GetPowerPC().GetDebugInterface().SetPatch(
Core::CPUThreadGuard{m_system},
m_table_proxy->data(index, UserRole::ClickRole).value<u32>(), 0x60000000);
m_table_proxy->SetInspected(index);
}
// Same issue as OnSetBLR.
m_code_widget->Update();
SetStubPatches(0x60000000);
}
void BranchWatchDialog::OnTableCopyAddress(QModelIndexList index_list)
void BranchWatchDialog::OnTableCopyAddress()
{
auto iter = index_list.begin();
if (iter == index_list.end())
auto iter = m_index_list_temp.begin();
if (iter == m_index_list_temp.end())
return;
QString text;
text.reserve(index_list.size() * 9 - 1);
text.reserve(m_index_list_temp.size() * 9 - 1);
while (true)
{
text.append(QString::number(m_table_proxy->data(*iter, UserRole::ClickRole).value<u32>(), 16));
if (++iter == index_list.end())
if (++iter == m_index_list_temp.end())
break;
text.append(QChar::fromLatin1('\n'));
}
QApplication::clipboard()->setText(text);
}
void BranchWatchDialog::OnTableSetBreakpointBreak()
{
SetBreakpoints(true, false);
}
void BranchWatchDialog::OnTableSetBreakpointLog()
{
SetBreakpoints(false, true);
}
void BranchWatchDialog::OnTableSetBreakpointBoth()
{
SetBreakpoints(true, true);
}
void BranchWatchDialog::SaveSettings()
{
auto& settings = Settings::GetQSettings();
@ -982,6 +953,12 @@ void BranchWatchDialog::UpdateStatus()
}
}
void BranchWatchDialog::UpdateIcons()
{
m_icn_full = Resources::GetThemeIcon("debugger_breakpoint");
m_icn_partial = Resources::GetThemeIcon("stop");
}
void BranchWatchDialog::Save(const Core::CPUThreadGuard& guard, const std::string& filepath)
{
File::IOFile file(filepath, "w");
@ -1018,3 +995,144 @@ void BranchWatchDialog::AutoSave(const Core::CPUThreadGuard& guard)
return;
Save(guard, m_autosave_filepath ? m_autosave_filepath.value() : GetSnapshotDefaultFilepath());
}
void BranchWatchDialog::SetStubPatches(u32 value) const
{
auto& debug_interface = m_system.GetPowerPC().GetDebugInterface();
for (const Core::CPUThreadGuard guard(m_system); const QModelIndex& index : m_index_list_temp)
{
debug_interface.SetPatch(guard, m_table_proxy->data(index, UserRole::ClickRole).value<u32>(),
value);
m_table_proxy->SetInspected(index);
}
// TODO: This is not ideal. What I need is a signal for when memory has been changed by the GUI,
// but I cannot find one. UpdateDisasmDialog comes close, but does too much in one signal. For
// example, CodeViewWidget will scroll to the current PC when UpdateDisasmDialog is signaled. This
// seems like a pervasive issue. For example, modifying an instruction in the CodeViewWidget will
// not reflect in the MemoryViewWidget, and vice versa. Neither of these widgets changing memory
// will reflect in the JITWidget, either. At the very least, we can make sure the CodeWidget
// is updated in an acceptable way.
m_code_widget->Update();
}
void BranchWatchDialog::SetBreakpoints(bool break_on_hit, bool log_on_hit) const
{
auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
for (const QModelIndex& index : m_index_list_temp)
{
const u32 address = m_table_proxy->data(index, UserRole::ClickRole).value<u32>();
breakpoints.Add(address, break_on_hit, log_on_hit, {});
}
emit m_code_widget->BreakpointsChanged();
m_code_widget->Update();
}
QMenu* BranchWatchDialog::GetTableContextMenu(const QModelIndex& index)
{
if (m_mnu_table_context == nullptr)
{
m_mnu_table_context = new QMenu(this);
m_mnu_table_context->addAction(tr("&Delete"), this, &BranchWatchDialog::OnTableDelete);
m_act_insert_nop =
m_mnu_table_context->addAction(tr("Insert &NOP"), this, &BranchWatchDialog::OnTableSetNOP);
m_act_insert_blr =
m_mnu_table_context->addAction(tr("Insert &BLR"), this, &BranchWatchDialog::OnTableSetBLR);
m_act_copy_address = m_mnu_table_context->addAction(tr("&Copy Address"), this,
&BranchWatchDialog::OnTableCopyAddress);
m_mnu_set_breakpoint = new QMenu(tr("Set Brea&kpoint"));
m_act_break_on_hit = m_mnu_set_breakpoint->addAction(
tr("&Break on Hit"), this, &BranchWatchDialog::OnTableSetBreakpointBreak);
m_act_log_on_hit = m_mnu_set_breakpoint->addAction(tr("&Log on Hit"), this,
&BranchWatchDialog::OnTableSetBreakpointLog);
m_act_both_on_hit = m_mnu_set_breakpoint->addAction(
tr("Break &and Log on Hit"), this, &BranchWatchDialog::OnTableSetBreakpointBoth);
m_mnu_table_context->addMenu(m_mnu_set_breakpoint);
}
const bool core_initialized = Core::GetState(m_system) != Core::State::Uninitialized;
bool supported_column = true;
switch (index.column())
{
case Column::Origin:
m_act_insert_blr->setVisible(false);
m_act_insert_nop->setVisible(true);
m_act_insert_nop->setEnabled(core_initialized);
m_act_copy_address->setEnabled(true);
m_mnu_set_breakpoint->setEnabled(true);
break;
case Column::Destination:
{
m_act_insert_nop->setVisible(false);
m_act_insert_blr->setVisible(true);
const bool all_branches_save_lr =
core_initialized &&
std::all_of(
m_index_list_temp.begin(), m_index_list_temp.end(), [this](const QModelIndex& idx) {
const QModelIndex sibling = idx.siblingAtColumn(Column::Instruction);
return BranchSavesLR(m_table_proxy->data(sibling, UserRole::ClickRole).value<u32>());
});
m_act_insert_blr->setEnabled(all_branches_save_lr);
m_act_copy_address->setEnabled(true);
m_mnu_set_breakpoint->setEnabled(true);
break;
}
case Column::OriginSymbol:
case Column::DestinSymbol:
{
m_act_insert_nop->setVisible(false);
m_act_insert_blr->setVisible(true);
const bool all_symbols_valid =
core_initialized &&
std::all_of(m_index_list_temp.begin(), m_index_list_temp.end(),
[this](const QModelIndex& idx) {
return m_table_proxy->data(idx, UserRole::ClickRole).isValid();
});
m_act_insert_blr->setEnabled(all_symbols_valid);
m_act_copy_address->setEnabled(all_symbols_valid);
m_mnu_set_breakpoint->setEnabled(all_symbols_valid);
break;
}
default:
m_act_insert_nop->setVisible(false);
m_act_insert_blr->setVisible(false);
supported_column = false;
break;
}
m_act_copy_address->setVisible(supported_column);
m_mnu_set_breakpoint->menuAction()->setVisible(supported_column);
// Setting breakpoints while nothing is being emulated is discouraged in the UI.
m_mnu_set_breakpoint->setEnabled(core_initialized);
if (core_initialized && supported_column)
{
qsizetype bp_break_count = 0, bp_log_count = 0, bp_both_count = 0;
for (auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
const QModelIndex& idx : m_index_list_temp)
{
if (const TBreakPoint* bp = breakpoints.GetRegularBreakpoint(
m_table_proxy->data(idx, UserRole::ClickRole).value<u32>()))
{
if (bp->break_on_hit && bp->log_on_hit)
{
bp_both_count += 1;
continue;
}
bp_break_count += bp->break_on_hit;
bp_log_count += bp->log_on_hit;
}
}
m_act_break_on_hit->setIconVisibleInMenu(bp_break_count != 0);
m_act_break_on_hit->setIcon(bp_break_count == m_index_list_temp.size() ? m_icn_full :
m_icn_partial);
m_act_log_on_hit->setIconVisibleInMenu(bp_log_count != 0);
m_act_log_on_hit->setIcon(bp_log_count == m_index_list_temp.size() ? m_icn_full :
m_icn_partial);
m_act_both_on_hit->setIconVisibleInMenu(bp_both_count != 0);
m_act_both_on_hit->setIcon(bp_both_count == m_index_list_temp.size() ? m_icn_full :
m_icn_partial);
}
return m_mnu_table_context;
}

View file

@ -7,14 +7,16 @@
#include <string>
#include <QDialog>
#include <QIcon>
#include <QModelIndexList>
#include "Core/Core.h"
#include "Common/CommonTypes.h"
namespace Core
{
class BranchWatch;
class CPUThreadGuard;
enum class State;
class System;
} // namespace Core
class PPCSymbolDB;
@ -54,6 +56,11 @@ public:
QWidget* parent = nullptr);
~BranchWatchDialog() override;
BranchWatchDialog(const BranchWatchDialog&) = delete;
BranchWatchDialog(BranchWatchDialog&&) = delete;
BranchWatchDialog& operator=(const BranchWatchDialog&) = delete;
BranchWatchDialog& operator=(BranchWatchDialog&&) = delete;
protected:
void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;
@ -73,6 +80,7 @@ private:
void OnWipeInspection();
void OnTimeout();
void OnEmulationStateChanged(Core::State new_state);
void OnThemeChanged();
void OnHelp();
void OnToggleAutoSave(bool checked);
void OnHideShowControls(bool checked);
@ -81,11 +89,14 @@ private:
void OnTableClicked(const QModelIndex& index);
void OnTableContextMenu(const QPoint& pos);
void OnTableHeaderContextMenu(const QPoint& pos);
void OnTableDelete(QModelIndexList index_list);
void OnTableDelete();
void OnTableDeleteKeypress();
void OnTableSetBLR(QModelIndexList index_list);
void OnTableSetNOP(QModelIndexList index_list);
void OnTableCopyAddress(QModelIndexList index_list);
void OnTableSetBLR();
void OnTableSetNOP();
void OnTableCopyAddress();
void OnTableSetBreakpointBreak();
void OnTableSetBreakpointLog();
void OnTableSetBreakpointBoth();
void SaveSettings();
@ -95,9 +106,14 @@ public:
private:
void UpdateStatus();
void UpdateIcons();
void Save(const Core::CPUThreadGuard& guard, const std::string& filepath);
void Load(const Core::CPUThreadGuard& guard, const std::string& filepath);
void AutoSave(const Core::CPUThreadGuard& guard);
void SetStubPatches(u32 value) const;
void SetBreakpoints(bool break_on_hit, bool log_on_hit) const;
[[nodiscard]] QMenu* GetTableContextMenu(const QModelIndex& index);
Core::System& m_system;
Core::BranchWatch& m_branch_watch;
@ -106,6 +122,14 @@ private:
QPushButton *m_btn_start_pause, *m_btn_clear_watch, *m_btn_path_was_taken, *m_btn_path_not_taken,
*m_btn_was_overwritten, *m_btn_not_overwritten, *m_btn_wipe_recent_hits;
QAction* m_act_autosave;
QAction* m_act_insert_nop;
QAction* m_act_insert_blr;
QAction* m_act_copy_address;
QMenu* m_mnu_set_breakpoint;
QAction* m_act_break_on_hit;
QAction* m_act_log_on_hit;
QAction* m_act_both_on_hit;
QMenu* m_mnu_table_context = nullptr;
QMenu* m_mnu_column_visibility;
QToolBar* m_control_toolbar;
@ -115,5 +139,8 @@ private:
QStatusBar* m_status_bar;
QTimer* m_timer;
QIcon m_icn_full, m_icn_partial;
QModelIndexList m_index_list_temp;
std::optional<std::string> m_autosave_filepath;
};

View file

@ -11,6 +11,7 @@
#include "Common/Assert.h"
#include "Common/GekkoDisassembler.h"
#include "Common/Unreachable.h"
#include "Core/Debugger/BranchWatch.h"
#include "Core/PowerPC/PPCSymbolDB.h"
@ -144,18 +145,6 @@ void BranchWatchTableModel::OnWipeInspection()
roles);
}
void BranchWatchTableModel::OnDelete(QModelIndexList index_list)
{
std::sort(index_list.begin(), index_list.end());
// TODO C++20: std::ranges::reverse_view
for (auto iter = index_list.rbegin(); iter != index_list.rend(); ++iter)
{
if (!iter->isValid())
continue;
removeRow(iter->row());
}
}
void BranchWatchTableModel::Save(const Core::CPUThreadGuard& guard, std::FILE* file) const
{
m_branch_watch.Save(guard, file);
@ -355,7 +344,8 @@ QVariant BranchWatchTableModel::DisplayRoleData(const QModelIndex& index) const
case Column::TotalHits:
return QString::number(kv->second.total_hits);
}
return QVariant();
static_assert(Column::NumberOfColumns == 8);
Common::Unreachable();
}
QVariant BranchWatchTableModel::FontRoleData(const QModelIndex& index) const
@ -400,7 +390,8 @@ QVariant BranchWatchTableModel::TextAlignmentRoleData(const QModelIndex& index)
case Column::DestinSymbol:
return QVariant::fromValue(Qt::AlignLeft | Qt::AlignVCenter);
}
return QVariant();
static_assert(Column::NumberOfColumns == 8);
Common::Unreachable();
}
QVariant BranchWatchTableModel::ForegroundRoleData(const QModelIndex& index) const
@ -498,5 +489,6 @@ QVariant BranchWatchTableModel::SortRoleData(const QModelIndex& index) const
case Column::TotalHits:
return qulonglong{kv->second.total_hits};
}
return QVariant();
static_assert(Column::NumberOfColumns == 8);
Common::Unreachable();
}

View file

@ -75,6 +75,13 @@ public:
m_ppc_symbol_db(ppc_symbol_db)
{
}
~BranchWatchTableModel() override = default;
BranchWatchTableModel(const BranchWatchTableModel&) = delete;
BranchWatchTableModel(BranchWatchTableModel&&) = delete;
BranchWatchTableModel& operator=(const BranchWatchTableModel&) = delete;
BranchWatchTableModel& operator=(BranchWatchTableModel&&) = delete;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
@ -90,7 +97,6 @@ public:
void OnBranchNotOverwritten(const Core::CPUThreadGuard& guard);
void OnWipeRecentHits();
void OnWipeInspection();
void OnDelete(QModelIndexList index_list);
void Save(const Core::CPUThreadGuard& guard, std::FILE* file) const;
void Load(const Core::CPUThreadGuard& guard, std::FILE* file);

View file

@ -289,7 +289,7 @@ void BreakpointDialog::accept()
return;
}
m_parent->AddBP(address, false, do_break, do_log, condition);
m_parent->AddBP(address, do_break, do_log, condition);
}
else
{

View file

@ -3,9 +3,15 @@
#include "DolphinQt/Debugger/BreakpointWidget.h"
#include <QApplication>
#include <QHeaderView>
#include <QInputDialog>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QSignalBlocker>
#include <QStyleOptionViewItem>
#include <QStyledItemDelegate>
#include <QTableWidget>
#include <QToolBar>
#include <QVBoxLayout>
@ -34,7 +40,55 @@ enum CustomRole
ADDRESS_ROLE = Qt::UserRole,
IS_MEMCHECK_ROLE
};
}
enum TableColumns
{
ENABLED_COLUMN = 0,
TYPE_COLUMN = 1,
SYMBOL_COLUMN = 2,
ADDRESS_COLUMN = 3,
END_ADDRESS_COLUMN = 4,
BREAK_COLUMN = 5,
LOG_COLUMN = 6,
READ_COLUMN = 7,
WRITE_COLUMN = 8,
CONDITION_COLUMN = 9,
};
} // namespace
// Fix icons not centering properly in a QTableWidget.
class CustomDelegate : public QStyledItemDelegate
{
public:
CustomDelegate(BreakpointWidget* parent) : QStyledItemDelegate(parent) {}
private:
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
Q_ASSERT(index.isValid());
// Fetch normal drawing logic.
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
// Disable drawing icon the normal way.
opt.icon = QIcon();
opt.decorationSize = QSize(0, 0);
// Default draw command for paint.
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0);
// Draw pixmap at the center of the tablewidget cell
QPixmap pix = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
if (!pix.isNull())
{
const QRect r = option.rect;
const QSize size = pix.deviceIndependentSize().toSize();
const QPoint p = QPoint((r.width() - size.width()) / 2, (r.height() - size.height()) / 2);
painter->drawPixmap(r.topLeft() + p, pix);
}
}
};
BreakpointWidget::BreakpointWidget(QWidget* parent)
: QDockWidget(parent), m_system(Core::System::GetInstance())
@ -62,6 +116,8 @@ BreakpointWidget::BreakpointWidget(QWidget* parent)
Update();
});
connect(m_table, &QTableWidget::itemChanged, this, &BreakpointWidget::OnItemChanged);
connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged, this,
[this](bool visible) { setHidden(!visible); });
@ -69,7 +125,11 @@ BreakpointWidget::BreakpointWidget(QWidget* parent)
setHidden(!enabled || !Settings::Instance().IsBreakpointsVisible());
});
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &BreakpointWidget::UpdateIcons);
connect(&Settings::Instance(), &Settings::ThemeChanged, this, [this]() {
UpdateIcons();
Update();
});
UpdateIcons();
}
@ -88,14 +148,14 @@ void BreakpointWidget::CreateWidgets()
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
m_table = new QTableWidget;
m_table->setItemDelegate(new CustomDelegate(this));
m_table->setTabKeyNavigation(false);
m_table->setContentsMargins(0, 0, 0, 0);
m_table->setColumnCount(6);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->setColumnCount(10);
m_table->setSelectionMode(QAbstractItemView::NoSelection);
m_table->verticalHeader()->hide();
connect(m_table, &QTableWidget::itemClicked, this, &BreakpointWidget::OnClicked);
connect(m_table, &QTableWidget::customContextMenuRequested, this,
&BreakpointWidget::OnContextMenu);
@ -109,13 +169,11 @@ void BreakpointWidget::CreateWidgets()
layout->setSpacing(0);
m_new = m_toolbar->addAction(tr("New"), this, &BreakpointWidget::OnNewBreakpoint);
m_delete = m_toolbar->addAction(tr("Delete"), this, &BreakpointWidget::OnDelete);
m_clear = m_toolbar->addAction(tr("Clear"), this, &BreakpointWidget::OnClear);
m_load = m_toolbar->addAction(tr("Load"), this, &BreakpointWidget::OnLoad);
m_save = m_toolbar->addAction(tr("Save"), this, &BreakpointWidget::OnSave);
m_new->setEnabled(false);
m_load->setEnabled(false);
m_save->setEnabled(false);
@ -128,7 +186,6 @@ void BreakpointWidget::CreateWidgets()
void BreakpointWidget::UpdateIcons()
{
m_new->setIcon(Resources::GetThemeIcon("debugger_add_breakpoint"));
m_delete->setIcon(Resources::GetThemeIcon("debugger_delete"));
m_clear->setIcon(Resources::GetThemeIcon("debugger_clear"));
m_load->setIcon(Resources::GetThemeIcon("debugger_load"));
m_save->setIcon(Resources::GetThemeIcon("debugger_save"));
@ -145,13 +202,59 @@ void BreakpointWidget::showEvent(QShowEvent* event)
Update();
}
void BreakpointWidget::OnClicked(QTableWidgetItem* item)
{
if (!item)
return;
if (item->column() == SYMBOL_COLUMN || item->column() == ADDRESS_COLUMN ||
item->column() == END_ADDRESS_COLUMN)
{
return;
}
const u32 address = static_cast<u32>(m_table->item(item->row(), 0)->data(ADDRESS_ROLE).toUInt());
if (item->column() == ENABLED_COLUMN)
{
if (item->data(IS_MEMCHECK_ROLE).toBool())
m_system.GetPowerPC().GetMemChecks().ToggleEnable(address);
else
m_system.GetPowerPC().GetBreakPoints().ToggleEnable(address);
emit BreakpointsChanged();
Update();
return;
}
std::optional<QString> string = std::nullopt;
if (item->column() == CONDITION_COLUMN)
{
bool ok;
QString new_text = QInputDialog::getMultiLineText(
this, tr("Edit Conditional"), tr("Edit conditional expression"), item->text(), &ok);
if (!ok || item->text() == new_text)
return;
// If new_text is empty, leaving string as nullopt will clear the conditional.
if (!new_text.isEmpty())
string = new_text;
}
if (m_table->item(item->row(), 0)->data(IS_MEMCHECK_ROLE).toBool())
EditMBP(address, item->column(), string);
else
EditBreakpoint(address, item->column(), string);
}
void BreakpointWidget::UpdateButtonsEnabled()
{
if (!isVisible())
return;
const bool is_initialised = Core::GetState(m_system) != Core::State::Uninitialized;
m_new->setEnabled(is_initialised);
m_load->setEnabled(is_initialised);
m_save->setEnabled(is_initialised);
}
@ -161,20 +264,43 @@ void BreakpointWidget::Update()
if (!isVisible())
return;
m_table->clear();
const QSignalBlocker blocker(m_table);
m_table->setHorizontalHeaderLabels(
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags"), tr("Condition")});
m_table->clear();
m_table->setHorizontalHeaderLabels({tr("Active"), tr("Type"), tr("Function"), tr("Address"),
tr("End Addr"), tr("Break"), tr("Log"), tr("Read"),
tr("Write"), tr("Condition")});
m_table->horizontalHeader()->setStretchLastSection(true);
// Get row height for icons
m_table->setRowCount(1);
const int height = m_table->rowHeight(0);
int i = 0;
m_table->setRowCount(i);
// Create icon based on row height, downscaled for whitespace padding.
const int downscale = static_cast<int>(0.8 * height);
QPixmap enabled_icon =
Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(downscale, downscale));
const auto create_item = [](const QString& string = {}) {
QTableWidgetItem* item = new QTableWidgetItem(string);
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
return item;
};
QTableWidgetItem empty_item = QTableWidgetItem();
empty_item.setFlags(Qt::NoItemFlags);
QTableWidgetItem icon_item = QTableWidgetItem();
icon_item.setData(Qt::DecorationRole, enabled_icon);
QTableWidgetItem disabled_item = QTableWidgetItem();
disabled_item.setFlags(Qt::NoItemFlags);
const QColor disabled_color =
Settings::Instance().IsThemeDark() ? QColor(75, 75, 75) : QColor(225, 225, 225);
disabled_item.setBackground(disabled_color);
auto& power_pc = m_system.GetPowerPC();
auto& breakpoints = power_pc.GetBreakPoints();
auto& memchecks = power_pc.GetMemChecks();
@ -185,111 +311,114 @@ void BreakpointWidget::Update()
{
m_table->setRowCount(i + 1);
auto* active = create_item(bp.is_enabled ? tr("on") : tr("off"));
auto* active = create_item();
active->setData(ADDRESS_ROLE, bp.address);
active->setData(IS_MEMCHECK_ROLE, false);
if (bp.is_enabled)
active->setData(Qt::DecorationRole, enabled_icon);
m_table->setItem(i, 0, active);
m_table->setItem(i, 1, create_item(QStringLiteral("BP")));
m_table->setItem(i, ENABLED_COLUMN, active);
m_table->setItem(i, TYPE_COLUMN, create_item(QStringLiteral("BP")));
auto* symbol_item = create_item();
if (const Common::Symbol* const symbol = ppc_symbol_db.GetSymbolFromAddr(bp.address))
m_table->setItem(i, 2, create_item(QString::fromStdString(symbol->name)));
symbol_item->setText(
QString::fromStdString(symbol->name).simplified().remove(QStringLiteral(" ")));
m_table->setItem(i, 3,
create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0'))));
m_table->setItem(i, SYMBOL_COLUMN, symbol_item);
QString flags;
auto* address_item = create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0')));
address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
if (bp.break_on_hit)
flags.append(QLatin1Char{'b'});
m_table->setItem(i, ADDRESS_COLUMN, address_item);
m_table->setItem(i, BREAK_COLUMN, bp.break_on_hit ? icon_item.clone() : empty_item.clone());
m_table->setItem(i, LOG_COLUMN, bp.log_on_hit ? icon_item.clone() : empty_item.clone());
if (bp.log_on_hit)
flags.append(QLatin1Char{'l'});
m_table->setItem(i, END_ADDRESS_COLUMN, disabled_item.clone());
m_table->setItem(i, READ_COLUMN, disabled_item.clone());
m_table->setItem(i, WRITE_COLUMN, disabled_item.clone());
m_table->setItem(i, 4, create_item(flags));
m_table->setItem(
i, CONDITION_COLUMN,
create_item(bp.condition ? QString::fromStdString(bp.condition->GetText()) : QString()));
QString condition;
if (bp.condition)
condition = QString::fromStdString(bp.condition->GetText());
m_table->setItem(i, 5, create_item(condition));
// Color rows that are effectively disabled.
if (!bp.is_enabled || (!bp.log_on_hit && !bp.break_on_hit))
{
for (int col = 0; col < m_table->columnCount(); col++)
m_table->item(i, col)->setBackground(disabled_color);
}
i++;
}
m_table->sortItems(ADDRESS_COLUMN);
// Memory Breakpoints
for (const auto& mbp : memchecks.GetMemChecks())
{
m_table->setRowCount(i + 1);
auto* active =
create_item(mbp.is_enabled && (mbp.break_on_hit || mbp.log_on_hit) ? tr("on") : tr("off"));
auto* active = create_item();
active->setData(ADDRESS_ROLE, mbp.start_address);
active->setData(IS_MEMCHECK_ROLE, true);
if (mbp.is_enabled)
active->setData(Qt::DecorationRole, enabled_icon);
m_table->setItem(i, 0, active);
m_table->setItem(i, 1, create_item(QStringLiteral("MBP")));
m_table->setItem(i, ENABLED_COLUMN, active);
m_table->setItem(i, TYPE_COLUMN, create_item(QStringLiteral("MBP")));
auto* symbol_item = create_item();
if (const Common::Symbol* const symbol = ppc_symbol_db.GetSymbolFromAddr(mbp.start_address))
m_table->setItem(i, 2, create_item(QString::fromStdString(symbol->name)));
if (mbp.is_ranged)
{
m_table->setItem(i, 3,
create_item(QStringLiteral("%1 - %2")
.arg(mbp.start_address, 8, 16, QLatin1Char('0'))
.arg(mbp.end_address, 8, 16, QLatin1Char('0'))));
}
else
{
m_table->setItem(
i, 3, create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0'))));
symbol_item->setText(
QString::fromStdString(symbol->name).simplified().remove(QStringLiteral(" ")));
}
QString flags;
m_table->setItem(i, SYMBOL_COLUMN, symbol_item);
if (mbp.is_break_on_read)
flags.append(QLatin1Char{'r'});
auto* start_address_item =
create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0')));
start_address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
if (mbp.is_break_on_write)
flags.append(QLatin1Char{'w'});
m_table->setItem(i, ADDRESS_COLUMN, start_address_item);
m_table->setItem(i, 4, create_item(flags));
auto* end_address_item =
create_item(QStringLiteral("%1").arg(mbp.end_address, 8, 16, QLatin1Char('0')));
end_address_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
end_address_item->setData(ADDRESS_ROLE, mbp.end_address);
QString condition;
m_table->setItem(i, END_ADDRESS_COLUMN, end_address_item);
if (mbp.condition)
condition = QString::fromStdString(mbp.condition->GetText());
m_table->setItem(i, BREAK_COLUMN, mbp.break_on_hit ? icon_item.clone() : empty_item.clone());
m_table->setItem(i, LOG_COLUMN, mbp.log_on_hit ? icon_item.clone() : empty_item.clone());
m_table->setItem(i, READ_COLUMN, mbp.is_break_on_read ? icon_item.clone() : empty_item.clone());
m_table->setItem(i, WRITE_COLUMN,
mbp.is_break_on_write ? icon_item.clone() : empty_item.clone());
m_table->setItem(i, 5, create_item(condition));
m_table->setItem(
i, CONDITION_COLUMN,
create_item(mbp.condition ? QString::fromStdString(mbp.condition->GetText()) : QString()));
// Color rows that are effectively disabled.
if (!mbp.is_enabled || (!mbp.is_break_on_write && !mbp.is_break_on_read) ||
(!mbp.break_on_hit && !mbp.log_on_hit))
{
for (int col = 0; col < m_table->columnCount(); col++)
m_table->item(i, col)->setBackground(disabled_color);
}
i++;
}
}
void BreakpointWidget::OnDelete()
{
const auto selected_items = m_table->selectedItems();
if (selected_items.empty())
return;
const auto item = selected_items.constFirst();
const auto address = item->data(ADDRESS_ROLE).toUInt();
const bool is_memcheck = item->data(IS_MEMCHECK_ROLE).toBool();
if (is_memcheck)
{
const QSignalBlocker blocker(Settings::Instance());
m_system.GetPowerPC().GetMemChecks().Remove(address);
}
else
{
m_system.GetPowerPC().GetBreakPoints().Remove(address);
}
emit BreakpointsChanged();
Update();
m_table->resizeColumnToContents(ENABLED_COLUMN);
m_table->resizeColumnToContents(TYPE_COLUMN);
m_table->resizeColumnToContents(BREAK_COLUMN);
m_table->resizeColumnToContents(LOG_COLUMN);
m_table->resizeColumnToContents(READ_COLUMN);
m_table->resizeColumnToContents(WRITE_COLUMN);
}
void BreakpointWidget::OnClear()
@ -318,8 +447,8 @@ void BreakpointWidget::OnEditBreakpoint(u32 address, bool is_instruction_bp)
{
if (is_instruction_bp)
{
auto* dialog =
new BreakpointDialog(this, m_system.GetPowerPC().GetBreakPoints().GetBreakpoint(address));
auto* dialog = new BreakpointDialog(
this, m_system.GetPowerPC().GetBreakPoints().GetRegularBreakpoint(address));
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
SetQWidgetWindowDecorations(dialog);
dialog->exec();
@ -377,15 +506,13 @@ void BreakpointWidget::OnSave()
ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
}
void BreakpointWidget::OnContextMenu()
void BreakpointWidget::OnContextMenu(const QPoint& pos)
{
const auto& selected_items = m_table->selectedItems();
if (selected_items.isEmpty())
{
const auto row = m_table->rowAt(pos.y());
const auto& selected_item = m_table->item(row, 0);
if (selected_item == nullptr)
return;
}
const auto& selected_item = selected_items.constFirst();
const auto bp_address = static_cast<u32>(selected_item->data(ADDRESS_ROLE).toUInt());
const auto is_memory_breakpoint = selected_item->data(IS_MEMCHECK_ROLE).toBool();
@ -402,9 +529,9 @@ void BreakpointWidget::OnContextMenu()
return;
menu->addAction(tr("Show in Code"), [this, bp_address] { emit ShowCode(bp_address); });
menu->addAction(bp_iter->is_enabled ? tr("Disable") : tr("Enable"), [this, &bp_address]() {
m_system.GetPowerPC().GetBreakPoints().ToggleBreakPoint(bp_address);
menu->addAction(tr("Edit..."), [this, bp_address] { OnEditBreakpoint(bp_address, true); });
menu->addAction(tr("Delete"), [this, &bp_address]() {
m_system.GetPowerPC().GetBreakPoints().Remove(bp_address);
emit BreakpointsChanged();
Update();
});
@ -419,36 +546,114 @@ void BreakpointWidget::OnContextMenu()
return;
menu->addAction(tr("Show in Memory"), [this, bp_address] { emit ShowMemory(bp_address); });
menu->addAction(mb_iter->is_enabled ? tr("Disable") : tr("Enable"), [this, &bp_address]() {
m_system.GetPowerPC().GetMemChecks().ToggleBreakPoint(bp_address);
menu->addAction(tr("Edit..."), [this, bp_address] { OnEditBreakpoint(bp_address, false); });
menu->addAction(tr("Delete"), [this, &bp_address]() {
const QSignalBlocker blocker(Settings::Instance());
m_system.GetPowerPC().GetMemChecks().Remove(bp_address);
emit BreakpointsChanged();
Update();
});
}
menu->addAction(tr("Edit..."), [this, bp_address, is_memory_breakpoint] {
OnEditBreakpoint(bp_address, !is_memory_breakpoint);
});
menu->exec(QCursor::pos());
}
void BreakpointWidget::AddBP(u32 addr)
void BreakpointWidget::OnItemChanged(QTableWidgetItem* item)
{
AddBP(addr, false, true, true, {});
if (item->column() != ADDRESS_COLUMN && item->column() != END_ADDRESS_COLUMN)
return;
bool ok;
const u32 new_address = item->text().toUInt(&ok, 16);
if (!ok)
return;
const bool is_code_bp = !m_table->item(item->row(), 0)->data(IS_MEMCHECK_ROLE).toBool();
const u32 base_address =
static_cast<u32>(m_table->item(item->row(), 0)->data(ADDRESS_ROLE).toUInt());
if (is_code_bp)
{
if (item->column() != ADDRESS_COLUMN || new_address == base_address)
return;
EditBreakpoint(base_address, item->column(), item->text());
}
else
{
const u32 end_address = static_cast<u32>(
m_table->item(item->row(), END_ADDRESS_COLUMN)->data(ADDRESS_ROLE).toUInt());
// Need to check that the start/base address is always <= end_address.
if ((item->column() == ADDRESS_COLUMN && new_address == base_address) ||
(item->column() == END_ADDRESS_COLUMN && new_address == end_address))
{
return;
}
if ((item->column() == ADDRESS_COLUMN && new_address <= end_address) ||
(item->column() == END_ADDRESS_COLUMN && new_address >= base_address))
{
EditMBP(base_address, item->column(), item->text());
}
else
{
// Removes invalid text from cell.
Update();
}
}
}
void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit,
const QString& condition)
void BreakpointWidget::AddBP(u32 addr)
{
AddBP(addr, true, true, {});
}
void BreakpointWidget::AddBP(u32 addr, bool break_on_hit, bool log_on_hit, const QString& condition)
{
m_system.GetPowerPC().GetBreakPoints().Add(
addr, temp, break_on_hit, log_on_hit,
addr, break_on_hit, log_on_hit,
!condition.isEmpty() ? Expression::TryParse(condition.toUtf8().constData()) : std::nullopt);
emit BreakpointsChanged();
Update();
}
void BreakpointWidget::EditBreakpoint(u32 address, int edit, std::optional<QString> string)
{
TBreakPoint bp;
const TBreakPoint* old_bp = m_system.GetPowerPC().GetBreakPoints().GetRegularBreakpoint(address);
bp.is_enabled = edit == ENABLED_COLUMN ? !old_bp->is_enabled : old_bp->is_enabled;
bp.log_on_hit = edit == LOG_COLUMN ? !old_bp->log_on_hit : old_bp->log_on_hit;
bp.break_on_hit = edit == BREAK_COLUMN ? !old_bp->break_on_hit : old_bp->break_on_hit;
if (edit == ADDRESS_COLUMN && string.has_value())
{
bool ok;
const u32 new_address = string.value().toUInt(&ok, 16);
if (!ok)
return;
bp.address = new_address;
}
else
{
bp.address = address;
}
if (edit == CONDITION_COLUMN && string.has_value())
bp.condition = Expression::TryParse(string.value().toUtf8().constData());
else if (old_bp->condition.has_value() && edit != CONDITION_COLUMN)
bp.condition = Expression::TryParse(old_bp->condition.value().GetText());
// Unlike MBPs it Add() for TBreakpoint doesn't check to see if it already exists.
m_system.GetPowerPC().GetBreakPoints().Remove(address);
m_system.GetPowerPC().GetBreakPoints().Add(std::move(bp));
emit BreakpointsChanged();
Update();
}
void BreakpointWidget::AddAddressMBP(u32 addr, bool on_read, bool on_write, bool do_log,
bool do_break, const QString& condition)
{
@ -494,3 +699,61 @@ void BreakpointWidget::AddRangedMBP(u32 from, u32 to, bool on_read, bool on_writ
emit BreakpointsChanged();
Update();
}
void BreakpointWidget::EditMBP(u32 address, int edit, std::optional<QString> string)
{
bool address_changed = false;
TMemCheck mbp;
const TMemCheck* old_mbp = m_system.GetPowerPC().GetMemChecks().GetMemCheck(address);
mbp.is_enabled = edit == ENABLED_COLUMN ? !old_mbp->is_enabled : old_mbp->is_enabled;
mbp.log_on_hit = edit == LOG_COLUMN ? !old_mbp->log_on_hit : old_mbp->log_on_hit;
mbp.break_on_hit = edit == BREAK_COLUMN ? !old_mbp->break_on_hit : old_mbp->break_on_hit;
mbp.is_break_on_read =
edit == READ_COLUMN ? !old_mbp->is_break_on_read : old_mbp->is_break_on_read;
mbp.is_break_on_write =
edit == WRITE_COLUMN ? !old_mbp->is_break_on_write : old_mbp->is_break_on_write;
if ((edit == ADDRESS_COLUMN || edit == END_ADDRESS_COLUMN) && string.has_value())
{
bool ok;
const u32 new_address = string.value().toUInt(&ok, 16);
if (!ok)
return;
if (edit == ADDRESS_COLUMN)
{
mbp.start_address = new_address;
mbp.end_address = old_mbp->end_address;
address_changed = true;
}
else if (edit == END_ADDRESS_COLUMN)
{
// Will update existing mbp, so does not use address_changed bool.
mbp.start_address = old_mbp->start_address;
mbp.end_address = new_address;
}
}
else
{
mbp.start_address = old_mbp->start_address;
mbp.end_address = old_mbp->end_address;
}
mbp.is_ranged = mbp.start_address != mbp.end_address;
if (edit == CONDITION_COLUMN && string.has_value())
mbp.condition = Expression::TryParse(string.value().toUtf8().constData());
else if (old_mbp->condition.has_value() && edit != CONDITION_COLUMN)
mbp.condition = Expression::TryParse(old_mbp->condition.value().GetText());
{
const QSignalBlocker blocker(Settings::Instance());
m_system.GetPowerPC().GetMemChecks().Add(std::move(mbp));
if (address_changed)
m_system.GetPowerPC().GetMemChecks().Remove(address);
}
emit BreakpointsChanged();
Update();
}

View file

@ -3,20 +3,29 @@
#pragma once
#include <optional>
#include <QDockWidget>
#include <QString>
#include "Common/CommonTypes.h"
class QAction;
class QCloseEvent;
class QPoint;
class QShowEvent;
class QTableWidget;
class QTableWidgetItem;
class QToolBar;
class QWidget;
namespace Core
{
class System;
}
class CustomDelegate;
class BreakpointWidget : public QDockWidget
{
Q_OBJECT
@ -25,7 +34,7 @@ public:
~BreakpointWidget();
void AddBP(u32 addr);
void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition);
void AddBP(u32 addr, bool break_on_hit, bool log_on_hit, const QString& condition);
void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true,
bool do_break = true, const QString& condition = {});
void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true,
@ -45,14 +54,17 @@ protected:
private:
void CreateWidgets();
void OnDelete();
void EditBreakpoint(u32 address, int edit, std::optional<QString> = std::nullopt);
void EditMBP(u32 address, int edit, std::optional<QString> = std::nullopt);
void OnClear();
void OnClicked(QTableWidgetItem* item);
void OnNewBreakpoint();
void OnEditBreakpoint(u32 address, bool is_instruction_bp);
void OnLoad();
void OnSave();
void OnContextMenu();
void OnContextMenu(const QPoint& pos);
void OnItemChanged(QTableWidgetItem* item);
void UpdateIcons();
Core::System& m_system;
@ -60,7 +72,6 @@ private:
QToolBar* m_toolbar;
QTableWidget* m_table;
QAction* m_new;
QAction* m_delete;
QAction* m_clear;
QAction* m_load;
QAction* m_save;

View file

@ -156,6 +156,7 @@ CodeViewWidget::CodeViewWidget()
horizontalHeader()->setStretchLastSection(true);
setHorizontalHeaderItem(CODE_VIEW_COLUMN_BREAKPOINT, new QTableWidgetItem());
setHorizontalHeaderItem(CODE_VIEW_COLUMN_ADDRESS, new QTableWidgetItem(tr("Address")));
// i18n: Short for "Instruction"
setHorizontalHeaderItem(CODE_VIEW_COLUMN_INSTRUCTION, new QTableWidgetItem(tr("Instr.")));
setHorizontalHeaderItem(CODE_VIEW_COLUMN_PARAMETERS, new QTableWidgetItem(tr("Parameters")));
setHorizontalHeaderItem(CODE_VIEW_COLUMN_DESCRIPTION, new QTableWidgetItem(tr("Symbols")));
@ -382,10 +383,11 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard)
if (ins == "blr")
ins_item->setForeground(dark_theme ? QColor(0xa0FFa0) : Qt::darkGreen);
if (debug_interface.IsBreakpoint(addr))
const TBreakPoint* bp = power_pc.GetBreakPoints().GetRegularBreakpoint(addr);
if (bp != nullptr)
{
auto icon = Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(rowh - 2, rowh - 2));
if (!power_pc.GetBreakPoints().IsBreakPointEnable(addr))
if (!bp->is_enabled)
{
QPixmap disabled_icon(icon.size());
disabled_icon.fill(Qt::transparent);
@ -568,44 +570,44 @@ void CodeViewWidget::OnContextMenu()
const bool has_symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr);
auto* follow_branch_action =
menu->addAction(tr("Follow &branch"), this, &CodeViewWidget::OnFollowBranch);
menu->addAction(tr("Follow &Branch"), this, &CodeViewWidget::OnFollowBranch);
menu->addSeparator();
menu->addAction(tr("&Copy address"), this, &CodeViewWidget::OnCopyAddress);
menu->addAction(tr("&Copy Address"), this, &CodeViewWidget::OnCopyAddress);
auto* copy_address_action =
menu->addAction(tr("Copy &function"), this, &CodeViewWidget::OnCopyFunction);
menu->addAction(tr("Copy &Function"), this, &CodeViewWidget::OnCopyFunction);
auto* copy_line_action =
menu->addAction(tr("Copy code &line"), this, &CodeViewWidget::OnCopyCode);
auto* copy_hex_action = menu->addAction(tr("Copy &hex"), this, &CodeViewWidget::OnCopyHex);
menu->addAction(tr("Copy Code &Line"), this, &CodeViewWidget::OnCopyCode);
auto* copy_hex_action = menu->addAction(tr("Copy &Hex"), this, &CodeViewWidget::OnCopyHex);
menu->addAction(tr("Show in &memory"), this, &CodeViewWidget::OnShowInMemory);
menu->addAction(tr("Show in &Memory"), this, &CodeViewWidget::OnShowInMemory);
auto* show_target_memory =
menu->addAction(tr("Show target in memor&y"), this, &CodeViewWidget::OnShowTargetInMemory);
menu->addAction(tr("Show Target in Memor&y"), this, &CodeViewWidget::OnShowTargetInMemory);
auto* copy_target_memory =
menu->addAction(tr("Copy tar&get address"), this, &CodeViewWidget::OnCopyTargetAddress);
menu->addAction(tr("Copy Tar&get Address"), this, &CodeViewWidget::OnCopyTargetAddress);
menu->addSeparator();
auto* symbol_rename_action =
menu->addAction(tr("&Rename symbol"), this, &CodeViewWidget::OnRenameSymbol);
menu->addAction(tr("&Rename Symbol"), this, &CodeViewWidget::OnRenameSymbol);
auto* symbol_size_action =
menu->addAction(tr("Set symbol &size"), this, &CodeViewWidget::OnSetSymbolSize);
menu->addAction(tr("Set Symbol &Size"), this, &CodeViewWidget::OnSetSymbolSize);
auto* symbol_end_action =
menu->addAction(tr("Set symbol &end address"), this, &CodeViewWidget::OnSetSymbolEndAddress);
menu->addAction(tr("Set Symbol &End Address"), this, &CodeViewWidget::OnSetSymbolEndAddress);
menu->addSeparator();
menu->addAction(tr("Run &To Here"), this, &CodeViewWidget::OnRunToHere);
auto* run_to_action = menu->addAction(tr("Run &to Here"), this, &CodeViewWidget::OnRunToHere);
auto* function_action =
menu->addAction(tr("&Add function"), this, &CodeViewWidget::OnAddFunction);
menu->addAction(tr("&Add Function"), this, &CodeViewWidget::OnAddFunction);
auto* ppc_action = menu->addAction(tr("PPC vs Host"), this, &CodeViewWidget::OnPPCComparison);
auto* insert_blr_action = menu->addAction(tr("&Insert blr"), this, &CodeViewWidget::OnInsertBLR);
auto* insert_nop_action = menu->addAction(tr("Insert &nop"), this, &CodeViewWidget::OnInsertNOP);
auto* insert_blr_action = menu->addAction(tr("&Insert BLR"), this, &CodeViewWidget::OnInsertBLR);
auto* insert_nop_action = menu->addAction(tr("Insert &NOP"), this, &CodeViewWidget::OnInsertNOP);
auto* replace_action =
menu->addAction(tr("Re&place instruction"), this, &CodeViewWidget::OnReplaceInstruction);
menu->addAction(tr("Re&place Instruction"), this, &CodeViewWidget::OnReplaceInstruction);
auto* assemble_action =
menu->addAction(tr("Assemble instruction"), this, &CodeViewWidget::OnAssembleInstruction);
menu->addAction(tr("Assemble Instruction"), this, &CodeViewWidget::OnAssembleInstruction);
auto* restore_action =
menu->addAction(tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction);
menu->addAction(tr("Restore Instruction"), this, &CodeViewWidget::OnRestoreInstruction);
QString target;
bool valid_load_store = false;
@ -630,14 +632,14 @@ void CodeViewWidget::OnContextMenu()
follow_branch_enabled = GetBranchFromAddress(guard, addr);
}
auto* run_until_menu = menu->addMenu(tr("Run until (ignoring breakpoints)"));
// i18n: One of the options shown below "Run until (ignoring breakpoints)"
auto* run_until_menu = menu->addMenu(tr("Run Until (Ignoring Breakpoints)"));
// i18n: One of the options shown below "Run Until (Ignoring Breakpoints)"
run_until_menu->addAction(tr("%1's value is hit").arg(target), this,
[this] { AutoStep(CodeTrace::AutoStop::Always); });
// i18n: One of the options shown below "Run until (ignoring breakpoints)"
// i18n: One of the options shown below "Run Until (Ignoring Breakpoints)"
run_until_menu->addAction(tr("%1's value is used").arg(target), this,
[this] { AutoStep(CodeTrace::AutoStop::Used); });
// i18n: One of the options shown below "Run until (ignoring breakpoints)"
// i18n: One of the options shown below "Run Until (Ignoring Breakpoints)"
run_until_menu->addAction(tr("%1's value is changed").arg(target),
[this] { AutoStep(CodeTrace::AutoStop::Changed); });
@ -645,8 +647,8 @@ void CodeViewWidget::OnContextMenu()
follow_branch_action->setEnabled(follow_branch_enabled);
for (auto* action :
{copy_address_action, copy_line_action, copy_hex_action, function_action, ppc_action,
insert_blr_action, insert_nop_action, replace_action, assemble_action})
{copy_address_action, copy_line_action, copy_hex_action, function_action, run_to_action,
ppc_action, insert_blr_action, insert_nop_action, replace_action, assemble_action})
{
action->setEnabled(running);
}
@ -676,7 +678,7 @@ void CodeViewWidget::AutoStep(CodeTrace::AutoStop option)
CodeTrace code_trace;
bool repeat = false;
QMessageBox msgbox(QMessageBox::NoIcon, tr("Run until"), {}, QMessageBox::Cancel);
QMessageBox msgbox(QMessageBox::NoIcon, tr("Run Until"), {}, QMessageBox::Cancel);
QPushButton* run_button = msgbox.addButton(tr("Keep Running"), QMessageBox::AcceptRole);
// Not sure if we want default to be cancel. Spacebar can let you quickly continue autostepping if
// Yes.
@ -726,7 +728,7 @@ void CodeViewWidget::AutoStep(CodeTrace::AutoStop option)
for (u32 i = 1; i <= 3; i++)
{
if (results.mem_tracked.count(address + i))
if (results.mem_tracked.contains(address + i))
iter++;
else
break;
@ -869,9 +871,7 @@ void CodeViewWidget::OnRunToHere()
{
const u32 addr = GetContextAddress();
m_system.GetPowerPC().GetDebugInterface().SetBreakpoint(addr);
m_system.GetPowerPC().GetDebugInterface().RunToBreakpoint();
Update();
m_system.GetPowerPC().GetDebugInterface().RunTo(addr);
}
void CodeViewWidget::OnPPCComparison()
@ -931,7 +931,7 @@ void CodeViewWidget::OnRenameSymbol()
bool good;
const QString name =
QInputDialog::getText(this, tr("Rename symbol"), tr("Symbol name:"), QLineEdit::Normal,
QInputDialog::getText(this, tr("Rename Symbol"), tr("Symbol Name:"), QLineEdit::Normal,
QString::fromStdString(symbol->name), &good, Qt::WindowCloseButtonHint);
if (good && !name.isEmpty())
@ -964,10 +964,9 @@ void CodeViewWidget::OnSetSymbolSize()
return;
bool good;
const int size =
QInputDialog::getInt(this, tr("Rename symbol"),
tr("Set symbol size (%1):").arg(QString::fromStdString(symbol->name)),
symbol->size, 1, 0xFFFF, 1, &good, Qt::WindowCloseButtonHint);
const int size = QInputDialog::getInt(
this, tr("Rename Symbol"), tr("Symbol Size (%1):").arg(QString::fromStdString(symbol->name)),
symbol->size, 1, 0xFFFF, 1, &good, Qt::WindowCloseButtonHint);
if (!good)
return;
@ -989,8 +988,8 @@ void CodeViewWidget::OnSetSymbolEndAddress()
bool good;
const QString name = QInputDialog::getText(
this, tr("Set symbol end address"),
tr("Symbol (%1) end address:").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal,
this, tr("Set Symbol End Address"),
tr("Symbol End Address (%1):").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal,
QStringLiteral("%1").arg(addr + symbol->size, 8, 16, QLatin1Char('0')), &good,
Qt::WindowCloseButtonHint);
@ -1137,11 +1136,7 @@ void CodeViewWidget::showEvent(QShowEvent* event)
void CodeViewWidget::ToggleBreakpoint()
{
auto& power_pc = m_system.GetPowerPC();
if (power_pc.GetDebugInterface().IsBreakpoint(GetContextAddress()))
power_pc.GetBreakPoints().Remove(GetContextAddress());
else
power_pc.GetBreakPoints().Add(GetContextAddress());
m_system.GetPowerPC().GetBreakPoints().ToggleBreakPoint(GetContextAddress());
emit BreakpointsChanged();
Update();

View file

@ -206,6 +206,7 @@ void CodeWidget::OnBranchWatchDialog()
m_branch_watch_dialog = new BranchWatchDialog(m_system, m_system.GetPowerPC().GetBranchWatch(),
m_ppc_symbol_db, this, this);
}
SetQWidgetWindowDecorations(m_branch_watch_dialog);
m_branch_watch_dialog->show();
m_branch_watch_dialog->raise();
m_branch_watch_dialog->activateWindow();
@ -454,7 +455,6 @@ void CodeWidget::Step()
auto& power_pc = m_system.GetPowerPC();
PowerPC::CoreMode old_mode = power_pc.GetMode();
power_pc.SetMode(PowerPC::CoreMode::Interpreter);
power_pc.GetBreakPoints().ClearAllTemporary();
cpu.StepOpcode(&sync_event);
sync_event.WaitFor(std::chrono::milliseconds(20));
power_pc.SetMode(old_mode);
@ -481,9 +481,8 @@ void CodeWidget::StepOver()
if (inst.LK)
{
auto& breakpoints = m_system.GetPowerPC().GetBreakPoints();
breakpoints.ClearAllTemporary();
breakpoints.Add(m_system.GetPPCState().pc + 4, true);
cpu.EnableStepping(false);
breakpoints.SetTemporary(m_system.GetPPCState().pc + 4);
cpu.SetStepping(false);
Core::DisplayMessage(tr("Step over in progress...").toStdString(), 2000);
}
else
@ -518,12 +517,9 @@ void CodeWidget::StepOut()
auto& power_pc = m_system.GetPowerPC();
auto& ppc_state = power_pc.GetPPCState();
auto& breakpoints = power_pc.GetBreakPoints();
{
Core::CPUThreadGuard guard(m_system);
breakpoints.ClearAllTemporary();
PowerPC::CoreMode old_mode = power_pc.GetMode();
power_pc.SetMode(PowerPC::CoreMode::Interpreter);
@ -546,8 +542,7 @@ void CodeWidget::StepOut()
do
{
power_pc.SingleStep();
} while (ppc_state.pc != next_pc && clock::now() < timeout &&
!breakpoints.IsAddressBreakPoint(ppc_state.pc));
} while (ppc_state.pc != next_pc && clock::now() < timeout && !power_pc.CheckBreakPoints());
}
else
{
@ -555,14 +550,14 @@ void CodeWidget::StepOut()
}
inst = PowerPC::MMU::HostRead_Instruction(guard, ppc_state.pc);
} while (clock::now() < timeout && !breakpoints.IsAddressBreakPoint(ppc_state.pc));
} while (clock::now() < timeout && !power_pc.CheckBreakPoints());
power_pc.SetMode(old_mode);
}
emit Host::GetInstance()->UpdateDisasmDialog();
if (breakpoints.IsAddressBreakPoint(ppc_state.pc))
if (power_pc.CheckBreakPoints())
Core::DisplayMessage(tr("Breakpoint encountered! Step out aborted.").toStdString(), 2000);
else if (clock::now() >= timeout)
Core::DisplayMessage(tr("Step out timed out!").toStdString(), 2000);

View file

@ -227,7 +227,7 @@ void NetworkWidget::ConnectWidgets()
connect(m_dump_bba_checkbox, &QCheckBox::stateChanged, [](int state) {
Config::SetBaseOrCurrent(Config::MAIN_NETWORK_DUMP_BBA, state == Qt::Checked);
});
connect(m_open_dump_folder, &QPushButton::pressed, [] {
connect(m_open_dump_folder, &QPushButton::clicked, [] {
const std::string location = File::GetUserPath(D_DUMPSSL_IDX);
const QUrl url = QUrl::fromLocalFile(QString::fromStdString(location));
QDesktopServices::openUrl(url);

View file

@ -18,7 +18,7 @@ namespace Core
{
class CPUThreadGuard;
class System;
}; // namespace Core
} // namespace Core
class WatchWidget : public QDockWidget
{

View file

@ -13,8 +13,5 @@
<!-- This is needed to use adhoc signed linked libraries -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- Allows the Steam overlay library to be injected into our process -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View file

@ -13,9 +13,6 @@
<!-- This is needed to use adhoc signed linked libraries -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- Allows the Steam overlay library to be injected into our process -->
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<!-- This is needed to attach a debugger to the process -->
<key>com.apple.security.get-task-allow</key>
<true/>

View file

@ -214,7 +214,7 @@ void FIFOPlayerWindow::AddDescriptions()
void FIFOPlayerWindow::LoadRecording()
{
QString path = DolphinFileDialog::getOpenFileName(this, tr("Open FIFO log"), QString(),
QString path = DolphinFileDialog::getOpenFileName(this, tr("Open FIFO Log"), QString(),
tr("Dolphin FIFO Log (*.dff)"));
if (path.isEmpty())
@ -225,7 +225,7 @@ void FIFOPlayerWindow::LoadRecording()
void FIFOPlayerWindow::SaveRecording()
{
QString path = DolphinFileDialog::getSaveFileName(this, tr("Save FIFO log"), QString(),
QString path = DolphinFileDialog::getSaveFileName(this, tr("Save FIFO Log"), QString(),
tr("Dolphin FIFO Log (*.dff)"));
if (path.isEmpty())

View file

@ -65,7 +65,7 @@ Slot OtherSlot(Slot slot)
{
return slot == Slot::A ? Slot::B : Slot::A;
}
}; // namespace
} // namespace
struct GCMemcardManager::IconAnimationData
{
@ -356,8 +356,8 @@ void GCMemcardManager::SetSlotFileInteractive(Slot slot)
{
QString path = QDir::toNativeSeparators(
DolphinFileDialog::getOpenFileName(this,
slot == Slot::A ? tr("Set memory card file for Slot A") :
tr("Set memory card file for Slot B"),
slot == Slot::A ? tr("Set Memory Card File for Slot A") :
tr("Set Memory Card File for Slot B"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
QStringLiteral("%1 (*.raw *.gcp);;%2 (*)")
.arg(tr("GameCube Memory Cards"), tr("All Files"))));

View file

@ -299,16 +299,14 @@ void GameList::MakeEmptyView()
size_policy.setRetainSizeWhenHidden(true);
m_empty->setSizePolicy(size_policy);
connect(&Settings::Instance(), &Settings::GameListRefreshRequested, this,
[this, refreshing_msg = refreshing_msg] {
m_empty->setText(refreshing_msg);
m_empty->setEnabled(false);
});
connect(&Settings::Instance(), &Settings::GameListRefreshCompleted, this,
[this, empty_msg = empty_msg] {
m_empty->setText(empty_msg);
m_empty->setEnabled(true);
});
connect(&Settings::Instance(), &Settings::GameListRefreshRequested, this, [this, refreshing_msg] {
m_empty->setText(refreshing_msg);
m_empty->setEnabled(false);
});
connect(&Settings::Instance(), &Settings::GameListRefreshCompleted, this, [this, empty_msg] {
m_empty->setText(empty_msg);
m_empty->setEnabled(true);
});
}
void GameList::resizeEvent(QResizeEvent* event)
@ -805,15 +803,11 @@ bool GameList::AddShortcutToDesktop()
std::string game_name = game->GetName(Core::TitleDatabase());
// Sanitize the string by removing all characters that cannot be used in NTFS file names
game_name.erase(std::remove_if(game_name.begin(), game_name.end(),
[](char ch) {
static constexpr char illegal_characters[] = {
'<', '>', ':', '\"', '/', '\\', '|', '?', '*'};
return std::find(std::begin(illegal_characters),
std::end(illegal_characters),
ch) != std::end(illegal_characters);
}),
game_name.end());
std::erase_if(game_name, [](char ch) {
static constexpr char illegal_characters[] = {'<', '>', ':', '\"', '/', '\\', '|', '?', '*'};
return std::find(std::begin(illegal_characters), std::end(illegal_characters), ch) !=
std::end(illegal_characters);
});
std::wstring desktop_path = std::wstring(desktop.get()) + UTF8ToTStr("\\" + game_name + ".lnk");
auto persist_file = shell_link.try_query<IPersistFile>();

View file

@ -162,6 +162,11 @@ bool Host::GetGBAFocus()
#endif
}
bool Host::GetTASInputFocus() const
{
return m_tas_input_focus;
}
bool Host::GetRenderFullscreen()
{
return m_render_fullscreen;
@ -177,6 +182,11 @@ void Host::SetRenderFullscreen(bool fullscreen)
}
}
void Host::SetTASInputFocus(const bool focus)
{
m_tas_input_focus = focus;
}
void Host::ResizeSurface(int new_width, int new_height)
{
if (g_presenter)
@ -228,6 +238,11 @@ bool Host_RendererIsFullscreen()
return Host::GetInstance()->GetRenderFullscreen();
}
bool Host_TASInputHasFocus()
{
return Host::GetInstance()->GetTASInputFocus();
}
void Host_YieldToUI()
{
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
@ -235,6 +250,9 @@ void Host_YieldToUI()
void Host_UpdateDisasmDialog()
{
if (Settings::Instance().GetIsContinuouslyFrameStepping())
return;
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->UpdateDisasmDialog(); });
}

View file

@ -25,12 +25,14 @@ public:
bool GetRenderFullFocus();
bool GetRenderFullscreen();
bool GetGBAFocus();
bool GetTASInputFocus() const;
void SetMainWindowHandle(void* handle);
void SetRenderHandle(void* handle);
void SetRenderFocus(bool focus);
void SetRenderFullFocus(bool focus);
void SetRenderFullscreen(bool fullscreen);
void SetTASInputFocus(bool focus);
void ResizeSurface(int new_width, int new_height);
signals:
@ -49,4 +51,5 @@ private:
std::atomic<bool> m_render_focus{false};
std::atomic<bool> m_render_full_focus{false};
std::atomic<bool> m_render_fullscreen{false};
std::atomic<bool> m_tas_input_focus{false};
};

View file

@ -113,6 +113,8 @@ static void HandleFrameStepHotkeys()
if ((frame_step_count == 0 || frame_step_count == FRAME_STEP_DELAY) && !frame_step_hold)
{
if (frame_step_count > 0)
Settings::Instance().SetIsContinuouslyFrameStepping(true);
Core::QueueHostJob([](auto& system) { Core::DoFrameStep(system); });
frame_step_hold = true;
}
@ -138,6 +140,8 @@ static void HandleFrameStepHotkeys()
frame_step_count = 0;
frame_step_hold = false;
frame_step_delay_count = 0;
Settings::Instance().SetIsContinuouslyFrameStepping(false);
emit Settings::Instance().EmulationStateChanged(Core::GetState(Core::System::GetInstance()));
}
}
@ -159,7 +163,8 @@ void HotkeyScheduler::Run()
if (!HotkeyManagerEmu::IsEnabled())
continue;
if (Core::GetState(Core::System::GetInstance()) != Core::State::Stopping)
Core::System& system = Core::System::GetInstance();
if (Core::GetState(system) != Core::State::Stopping)
{
// Obey window focus (config permitting) before checking hotkeys.
Core::UpdateInputGate(Config::Get(Config::MAIN_FOCUSED_HOTKEYS));
@ -187,7 +192,12 @@ void HotkeyScheduler::Run()
if (IsHotkey(HK_EXIT))
emit ExitHotkey();
if (!Core::IsRunningAndStarted())
#ifdef USE_RETRO_ACHIEVEMENTS
if (IsHotkey(HK_OPEN_ACHIEVEMENTS))
emit OpenAchievements();
#endif // USE_RETRO_ACHIEVEMENTS
if (!Core::IsRunning(system))
{
// Only check for Play Recording hotkey when no game is running
if (IsHotkey(HK_PLAY_RECORDING))
@ -472,9 +482,14 @@ void HotkeyScheduler::Run()
auto ShowEmulationSpeed = []() {
const float emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
OSD::AddMessage(emulation_speed <= 0 ?
"Speed Limit: Unlimited" :
fmt::format("Speed Limit: {}%", std::lround(emulation_speed * 100.f)));
if (!AchievementManager::GetInstance().IsHardcoreModeActive() ||
Config::Get(Config::MAIN_EMULATION_SPEED) >= 1.0f ||
Config::Get(Config::MAIN_EMULATION_SPEED) <= 0.0f)
{
OSD::AddMessage(emulation_speed <= 0 ? "Speed Limit: Unlimited" :
fmt::format("Speed Limit: {}%",
std::lround(emulation_speed * 100.f)));
}
};
if (IsHotkey(HK_DECREASE_EMULATION_SPEED))
@ -589,15 +604,12 @@ void HotkeyScheduler::Run()
{
const bool new_value = !Config::Get(Config::FREE_LOOK_ENABLED);
Config::SetCurrent(Config::FREE_LOOK_ENABLED, new_value);
#ifdef USE_RETRO_ACHIEVEMENTS
const bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
if (hardcore)
OSD::AddMessage("Free Look is Disabled in Hardcore Mode");
else
OSD::AddMessage(fmt::format("Free Look: {}", new_value ? "Enabled" : "Disabled"));
#else // USE_RETRO_ACHIEVEMENTS
OSD::AddMessage(fmt::format("Free Look: {}", new_value ? "Enabled" : "Disabled"));
#endif // USE_RETRO_ACHIEVEMENTS
}
// Savestates

View file

@ -53,6 +53,9 @@ signals:
void ExportRecording();
void ToggleReadOnlyMode();
void ConnectWiiRemote(int id);
#ifdef USE_RETRO_ACHIEVEMENTS
void OpenAchievements();
#endif // USE_RETRO_ACHIEVEMENTS
void Step();
void StepOver();

View file

@ -33,6 +33,8 @@
// static variable to ensure we open at the most recent figure file location
static QString s_last_figure_path;
using FigureUIPosition = IOS::HLE::USB::FigureUIPosition;
InfinityBaseWindow::InfinityBaseWindow(QWidget* parent) : QWidget(parent)
{
// i18n: Window for managing Disney Infinity figures
@ -48,7 +50,7 @@ InfinityBaseWindow::InfinityBaseWindow(QWidget* parent) : QWidget(parent)
installEventFilter(this);
OnEmulationStateChanged(Core::GetState(Core::System::GetInstance()));
};
}
InfinityBaseWindow::~InfinityBaseWindow() = default;
@ -77,19 +79,23 @@ void InfinityBaseWindow::CreateMainWindow()
auto* vbox_group = new QVBoxLayout();
auto* scroll_area = new QScrollArea();
AddFigureSlot(vbox_group, tr("Play Set/Power Disc"), 0);
AddFigureSlot(vbox_group, tr("Play Set/Power Disc"), FigureUIPosition::HexagonDiscOne);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player One"), 1);
AddFigureSlot(vbox_group, tr("Power Disc Two"), FigureUIPosition::HexagonDiscTwo);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player One Ability One"), 3);
AddFigureSlot(vbox_group, tr("Power Disc Three"), FigureUIPosition::HexagonDiscThree);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player One Ability Two"), 5);
AddFigureSlot(vbox_group, tr("Player One"), FigureUIPosition::PlayerOne);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player Two"), 2);
AddFigureSlot(vbox_group, tr("Player One Ability One"), FigureUIPosition::P1AbilityOne);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player Two Ability One"), 4);
AddFigureSlot(vbox_group, tr("Player One Ability Two"), FigureUIPosition::P1AbilityTwo);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player Two Ability Two"), 6);
AddFigureSlot(vbox_group, tr("Player Two"), FigureUIPosition::PlayerTwo);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player Two Ability One"), FigureUIPosition::P2AbilityOne);
add_line(vbox_group);
AddFigureSlot(vbox_group, tr("Player Two Ability Two"), FigureUIPosition::P2AbilityTwo);
m_group_figures->setLayout(vbox_group);
scroll_area->setWidget(m_group_figures);
@ -99,7 +105,7 @@ void InfinityBaseWindow::CreateMainWindow()
setLayout(main_layout);
}
void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 slot)
void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, FigureUIPosition slot)
{
auto* hbox_infinity = new QHBoxLayout();
@ -108,16 +114,16 @@ void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8
auto* clear_btn = new QPushButton(tr("Clear"));
auto* create_btn = new QPushButton(tr("Create"));
auto* load_btn = new QPushButton(tr("Load"));
m_edit_figures[slot] = new QLineEdit();
m_edit_figures[slot]->setEnabled(false);
m_edit_figures[slot]->setText(tr("None"));
m_edit_figures[static_cast<u8>(slot)] = new QLineEdit();
m_edit_figures[static_cast<u8>(slot)]->setEnabled(false);
m_edit_figures[static_cast<u8>(slot)]->setText(tr("None"));
connect(clear_btn, &QAbstractButton::clicked, this, [this, slot] { ClearFigure(slot); });
connect(create_btn, &QAbstractButton::clicked, this, [this, slot] { CreateFigure(slot); });
connect(load_btn, &QAbstractButton::clicked, this, [this, slot] { LoadFigure(slot); });
hbox_infinity->addWidget(label_skyname);
hbox_infinity->addWidget(m_edit_figures[slot]);
hbox_infinity->addWidget(m_edit_figures[static_cast<u8>(slot)]);
hbox_infinity->addWidget(clear_btn);
hbox_infinity->addWidget(create_btn);
hbox_infinity->addWidget(load_btn);
@ -125,15 +131,15 @@ void InfinityBaseWindow::AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8
vbox_group->addLayout(hbox_infinity);
}
void InfinityBaseWindow::ClearFigure(u8 slot)
void InfinityBaseWindow::ClearFigure(FigureUIPosition slot)
{
auto& system = Core::System::GetInstance();
m_edit_figures[slot]->setText(tr("None"));
m_edit_figures[static_cast<u8>(slot)]->setText(tr("None"));
system.GetInfinityBase().RemoveFigure(slot);
}
void InfinityBaseWindow::LoadFigure(u8 slot)
void InfinityBaseWindow::LoadFigure(FigureUIPosition slot)
{
const QString file_path =
DolphinFileDialog::getOpenFileName(this, tr("Select Figure File"), s_last_figure_path,
@ -148,7 +154,7 @@ void InfinityBaseWindow::LoadFigure(u8 slot)
LoadFigurePath(slot, file_path);
}
void InfinityBaseWindow::CreateFigure(u8 slot)
void InfinityBaseWindow::CreateFigure(FigureUIPosition slot)
{
CreateFigureDialog create_dlg(this, slot);
SetQWidgetWindowDecorations(&create_dlg);
@ -158,7 +164,7 @@ void InfinityBaseWindow::CreateFigure(u8 slot)
}
}
void InfinityBaseWindow::LoadFigurePath(u8 slot, const QString& path)
void InfinityBaseWindow::LoadFigurePath(FigureUIPosition slot, const QString& path)
{
File::IOFile inf_file(path.toStdString(), "r+b");
if (!inf_file)
@ -183,11 +189,11 @@ void InfinityBaseWindow::LoadFigurePath(u8 slot, const QString& path)
auto& system = Core::System::GetInstance();
system.GetInfinityBase().RemoveFigure(slot);
m_edit_figures[slot]->setText(QString::fromStdString(
m_edit_figures[static_cast<u8>(slot)]->setText(QString::fromStdString(
system.GetInfinityBase().LoadFigure(file_data, std::move(inf_file), slot)));
}
CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(parent)
CreateFigureDialog::CreateFigureDialog(QWidget* parent, FigureUIPosition slot) : QDialog(parent)
{
setWindowTitle(tr("Infinity Figure Creator"));
setObjectName(QStringLiteral("infinity_creator"));
@ -201,10 +207,14 @@ CreateFigureDialog::CreateFigureDialog(QWidget* parent, u8 slot) : QDialog(paren
{
const auto figure = entry.second;
// Only display entry if it is a piece appropriate for the slot
if ((slot == 0 &&
if ((slot == FigureUIPosition::HexagonDiscOne &&
((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) ||
((slot == 1 || slot == 2) && figure < 0x1E847F) ||
((slot == 3 || slot == 4 || slot == 5 || slot == 6) &&
((slot == FigureUIPosition::HexagonDiscTwo || slot == FigureUIPosition::HexagonDiscThree) &&
(figure > 0x3D0900 && figure < 0x4C4B3F)) ||
((slot == FigureUIPosition::PlayerOne || slot == FigureUIPosition::PlayerTwo) &&
figure < 0x1E847F) ||
((slot == FigureUIPosition::P1AbilityOne || slot == FigureUIPosition::P1AbilityTwo ||
slot == FigureUIPosition::P2AbilityOne || slot == FigureUIPosition::P2AbilityTwo) &&
(figure > 0x2DC6C0 && figure < 0x3D08FF)))
{
const auto figure_name = QString::fromStdString(entry.first);

View file

@ -20,6 +20,11 @@ namespace Core
enum class State;
}
namespace IOS::HLE::USB
{
enum class FigureUIPosition : u8;
}
class InfinityBaseWindow : public QWidget
{
Q_OBJECT
@ -28,17 +33,17 @@ public:
~InfinityBaseWindow() override;
protected:
std::array<QLineEdit*, 7> m_edit_figures;
std::array<QLineEdit*, 9> m_edit_figures;
private:
void CreateMainWindow();
void AddFigureSlot(QVBoxLayout* vbox_group, QString name, u8 slot);
void AddFigureSlot(QVBoxLayout* vbox_group, QString name, IOS::HLE::USB::FigureUIPosition slot);
void OnEmulationStateChanged(Core::State state);
void EmulateBase(bool emulate);
void ClearFigure(u8 slot);
void LoadFigure(u8 slot);
void CreateFigure(u8 slot);
void LoadFigurePath(u8 slot, const QString& path);
void ClearFigure(IOS::HLE::USB::FigureUIPosition slot);
void LoadFigure(IOS::HLE::USB::FigureUIPosition slot);
void CreateFigure(IOS::HLE::USB::FigureUIPosition slot);
void LoadFigurePath(IOS::HLE::USB::FigureUIPosition slot, const QString& path);
QCheckBox* m_checkbox;
QGroupBox* m_group_figures;
@ -49,7 +54,7 @@ class CreateFigureDialog : public QDialog
Q_OBJECT
public:
explicit CreateFigureDialog(QWidget* parent, u8 slot);
explicit CreateFigureDialog(QWidget* parent, IOS::HLE::USB::FigureUIPosition slot);
QString GetFilePath() const;
protected:

View file

@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleExecutable</key>
<string>Dolphin</string>
<string>DolphinQt</string>
<key>CFBundleIconFile</key>
<string>Dolphin.icns</string>
<key>CFBundleIdentifier</key>

View file

@ -259,7 +259,6 @@ int main(int argc, char* argv[])
Settings::Instance().ApplyStyle();
MainWindow win{std::move(boot), static_cast<const char*>(options.get("movie"))};
win.Show();

View file

@ -229,8 +229,6 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
setAcceptDrops(true);
setAttribute(Qt::WA_NativeWindow);
InitControllers();
CreateComponents();
ConnectGameList();
@ -239,6 +237,17 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
ConnectRenderWidget();
ConnectStack();
ConnectMenuBar();
QSettings& settings = Settings::GetQSettings();
restoreState(settings.value(QStringLiteral("mainwindow/state")).toByteArray());
restoreGeometry(settings.value(QStringLiteral("mainwindow/geometry")).toByteArray());
if (!Settings::Instance().IsBatchModeEnabled())
{
SetQWidgetWindowDecorations(this);
show();
}
InitControllers();
ConnectHotkeys();
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
@ -291,11 +300,6 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
m_state_slot =
std::clamp(Settings::Instance().GetStateSlot(), 1, static_cast<int>(State::NUM_STATES));
QSettings& settings = Settings::GetQSettings();
restoreState(settings.value(QStringLiteral("mainwindow/state")).toByteArray());
restoreGeometry(settings.value(QStringLiteral("mainwindow/geometry")).toByteArray());
m_render_widget_geometry = settings.value(QStringLiteral("renderwidget/geometry")).toByteArray();
// Restoring of window states can sometimes go wrong, resulting in widgets being visible when they
@ -321,6 +325,12 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
}
Host::GetInstance()->SetMainWindowHandle(reinterpret_cast<void*>(winId()));
if (m_pending_boot != nullptr)
{
StartGame(std::move(m_pending_boot));
m_pending_boot.reset();
}
}
MainWindow::~MainWindow()
@ -347,8 +357,11 @@ MainWindow::~MainWindow()
QSettings& settings = Settings::GetQSettings();
settings.setValue(QStringLiteral("mainwindow/state"), saveState());
settings.setValue(QStringLiteral("mainwindow/geometry"), saveGeometry());
if (!Settings::Instance().IsBatchModeEnabled())
{
settings.setValue(QStringLiteral("mainwindow/state"), saveState());
settings.setValue(QStringLiteral("mainwindow/geometry"), saveGeometry());
}
settings.setValue(QStringLiteral("renderwidget/geometry"), m_render_widget_geometry);
@ -645,6 +658,10 @@ void MainWindow::ConnectHotkeys()
movie.SetReadOnly(read_only);
emit ReadOnlyModeChanged(read_only);
});
#ifdef USE_RETRO_ACHIEVEMENTS
connect(m_hotkey_scheduler, &HotkeyScheduler::OpenAchievements, this,
&MainWindow::ShowAchievementsWindow, Qt::QueuedConnection);
#endif // USE_RETRO_ACHIEVEMENTS
connect(m_hotkey_scheduler, &HotkeyScheduler::Step, m_code_widget, &CodeWidget::Step);
connect(m_hotkey_scheduler, &HotkeyScheduler::StepOver, m_code_widget, &CodeWidget::StepOver);
@ -1910,7 +1927,7 @@ void MainWindow::OnImportNANDBackup()
return;
QString file =
DolphinFileDialog::getOpenFileName(this, tr("Select the save file"), QDir::currentPath(),
DolphinFileDialog::getOpenFileName(this, tr("Select NAND Backup"), QDir::currentPath(),
tr("BootMii NAND backup file (*.bin);;"
"All Files (*)"));
@ -1936,7 +1953,7 @@ void MainWindow::OnImportNANDBackup()
[this] {
std::optional<std::string> keys_file = RunOnObject(this, [this] {
return DolphinFileDialog::getOpenFileName(
this, tr("Select the keys file (OTP/SEEPROM dump)"), QDir::currentPath(),
this, tr("Select Keys File (OTP/SEEPROM Dump)"), QDir::currentPath(),
tr("BootMii keys file (*.bin);;"
"All Files (*)"))
.toStdString();
@ -1985,7 +2002,7 @@ void MainWindow::OnStartRecording()
{
auto& system = Core::System::GetInstance();
auto& movie = system.GetMovie();
if ((!Core::IsRunningAndStarted() && Core::IsRunning(system)) || movie.IsRecordingInput() ||
if (Core::GetState(system) == Core::State::Starting || movie.IsRecordingInput() ||
movie.IsPlayingInput())
{
return;
@ -2175,19 +2192,3 @@ void MainWindow::ShowRiivolutionBootWidget(const UICommon::GameFile& game)
AddRiivolutionPatches(boot_params.get(), std::move(w.GetPatches()));
StartGame(std::move(boot_params));
}
void MainWindow::Show()
{
if (!Settings::Instance().IsBatchModeEnabled())
{
SetQWidgetWindowDecorations(this);
QWidget::show();
}
// If the booting of a game was requested on start up, do that now
if (m_pending_boot != nullptr)
{
StartGame(std::move(m_pending_boot));
m_pending_boot.reset();
}
}

View file

@ -78,7 +78,6 @@ public:
const std::string& movie_path);
~MainWindow();
void Show();
WindowSystemInfo GetWindowSystemInfo() const;
bool eventFilter(QObject* object, QEvent* event) override;

View file

@ -128,14 +128,9 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_screenshot_action->setEnabled(running);
m_state_save_menu->setEnabled(running);
#ifdef USE_RETRO_ACHIEVEMENTS
const bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
m_state_load_menu->setEnabled(running && !hardcore);
m_frame_advance_action->setEnabled(running && !hardcore);
#else // USE_RETRO_ACHIEVEMENTS
m_state_load_menu->setEnabled(running);
m_frame_advance_action->setEnabled(running);
#endif // USE_RETRO_ACHIEVEMENTS
// Movie
m_recording_read_only->setEnabled(running);
@ -145,11 +140,7 @@ void MenuBar::OnEmulationStateChanged(Core::State state)
m_recording_export->setEnabled(false);
}
m_recording_play->setEnabled(m_game_selected && !running);
#ifdef USE_RETRO_ACHIEVEMENTS
m_recording_play->setEnabled(m_game_selected && !running && !hardcore);
#else // USE_RETRO_ACHIEVEMENTS
m_recording_play->setEnabled(m_game_selected && !running);
#endif // USE_RETRO_ACHIEVEMENTS
m_recording_start->setEnabled((m_game_selected || running) &&
!Core::System::GetInstance().GetMovie().IsPlayingInput());
@ -272,12 +263,9 @@ void MenuBar::AddToolsMenu()
tools_menu->addSeparator();
#ifdef USE_RETRO_ACHIEVEMENTS
if (Config::Get(Config::RA_ENABLED))
{
tools_menu->addAction(tr("Achievements"), this, [this] { emit ShowAchievementsWindow(); });
tools_menu->addAction(tr("Achievements"), this, [this] { emit ShowAchievementsWindow(); });
tools_menu->addSeparator();
}
tools_menu->addSeparator();
#endif // USE_RETRO_ACHIEVEMENTS
QMenu* gc_ipl = tools_menu->addMenu(tr("Load GameCube Main Menu"));
@ -416,6 +404,10 @@ void MenuBar::AddStateSlotMenu(QMenu* emu_menu)
action->setChecked(true);
connect(action, &QAction::triggered, this, [=, this]() { emit SetStateSlot(i); });
connect(this, &MenuBar::SetStateSlot, [action, i](const int slot) {
if (slot == i)
action->setChecked(true);
});
}
}
@ -1088,8 +1080,8 @@ void MenuBar::UpdateToolsMenu(bool emulation_started)
void MenuBar::InstallWAD()
{
QString wad_file = DolphinFileDialog::getOpenFileName(
this, tr("Select a title to install to NAND"), QString(), tr("WAD files (*.wad)"));
QString wad_file = DolphinFileDialog::getOpenFileName(this, tr("Select Title to Install to NAND"),
QString(), tr("WAD files (*.wad)"));
if (wad_file.isEmpty())
return;
@ -1109,7 +1101,7 @@ void MenuBar::InstallWAD()
void MenuBar::ImportWiiSave()
{
QString file =
DolphinFileDialog::getOpenFileName(this, tr("Select the save file"), QDir::currentPath(),
DolphinFileDialog::getOpenFileName(this, tr("Select Save File"), QDir::currentPath(),
tr("Wii save files (*.bin);;"
"All Files (*)"));
@ -1577,7 +1569,7 @@ void MenuBar::SaveSymbolMap()
void MenuBar::LoadOtherSymbolMap()
{
const QString file = DolphinFileDialog::getOpenFileName(
this, tr("Load map file"), QString::fromStdString(File::GetUserPath(D_MAPS_IDX)),
this, tr("Load Map File"), QString::fromStdString(File::GetUserPath(D_MAPS_IDX)),
tr("Dolphin Map File (*.map)"));
if (file.isEmpty())
@ -1594,7 +1586,7 @@ void MenuBar::LoadOtherSymbolMap()
void MenuBar::LoadBadSymbolMap()
{
const QString file = DolphinFileDialog::getOpenFileName(
this, tr("Load map file"), QString::fromStdString(File::GetUserPath(D_MAPS_IDX)),
this, tr("Load Map File"), QString::fromStdString(File::GetUserPath(D_MAPS_IDX)),
tr("Dolphin Map File (*.map)"));
if (file.isEmpty())
@ -1612,7 +1604,7 @@ void MenuBar::SaveSymbolMapAs()
{
const std::string& title_id_str = SConfig::GetInstance().m_debugger_game_id;
const QString file = DolphinFileDialog::getSaveFileName(
this, tr("Save map file"),
this, tr("Save Map File"),
QString::fromStdString(File::GetUserPath(D_MAPS_IDX) + "/" + title_id_str + ".map"),
tr("Dolphin Map File (*.map)"));
@ -1668,7 +1660,7 @@ void MenuBar::CreateSignatureFile()
this, tr("Input"), tr("Only export symbols with prefix:\n(Blank for all symbols)"),
QLineEdit::Normal, QString{}, nullptr, Qt::WindowCloseButtonHint);
const QString file = DolphinFileDialog::getSaveFileName(this, tr("Save signature file"),
const QString file = DolphinFileDialog::getSaveFileName(this, tr("Save Signature File"),
QDir::homePath(), GetSignatureSelector());
if (file.isEmpty())
return;
@ -1693,7 +1685,7 @@ void MenuBar::AppendSignatureFile()
this, tr("Input"), tr("Only append symbols with prefix:\n(Blank for all symbols)"),
QLineEdit::Normal, QString{}, nullptr, Qt::WindowCloseButtonHint);
const QString file = DolphinFileDialog::getSaveFileName(this, tr("Append signature to"),
const QString file = DolphinFileDialog::getSaveFileName(this, tr("Append Signature To"),
QDir::homePath(), GetSignatureSelector());
if (file.isEmpty())
return;
@ -1716,7 +1708,7 @@ void MenuBar::AppendSignatureFile()
void MenuBar::ApplySignatureFile()
{
const QString file = DolphinFileDialog::getOpenFileName(this, tr("Apply signature file"),
const QString file = DolphinFileDialog::getOpenFileName(this, tr("Apply Signature File"),
QDir::homePath(), GetSignatureSelector());
if (file.isEmpty())
@ -1736,17 +1728,17 @@ void MenuBar::ApplySignatureFile()
void MenuBar::CombineSignatureFiles()
{
const QString priorityFile = DolphinFileDialog::getOpenFileName(
this, tr("Choose priority input file"), QDir::homePath(), GetSignatureSelector());
this, tr("Choose Priority Input File"), QDir::homePath(), GetSignatureSelector());
if (priorityFile.isEmpty())
return;
const QString secondaryFile = DolphinFileDialog::getOpenFileName(
this, tr("Choose secondary input file"), QDir::homePath(), GetSignatureSelector());
this, tr("Choose Secondary Input File"), QDir::homePath(), GetSignatureSelector());
if (secondaryFile.isEmpty())
return;
const QString saveFile = DolphinFileDialog::getSaveFileName(
this, tr("Save combined output file as"), QDir::homePath(), GetSignatureSelector());
this, tr("Save Combined Output File As"), QDir::homePath(), GetSignatureSelector());
if (saveFile.isEmpty())
return;

View file

@ -24,7 +24,7 @@ enum class State;
namespace DiscIO
{
enum class Region;
};
}
namespace UICommon
{

View file

@ -125,7 +125,7 @@ void ChunkedProgressDialog::SetProgress(const int pid, const u64 progress)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
if (!m_status_labels.contains(pid))
return;
const float acquired = progress / 1024.0f / 1024.0f;

View file

@ -122,7 +122,7 @@ void GameDigestDialog::SetProgress(int pid, int progress)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
if (!m_status_labels.contains(pid))
return;
m_status_labels[pid]->setText(
@ -134,7 +134,7 @@ void GameDigestDialog::SetResult(int pid, const std::string& result)
{
QString player_name = GetPlayerNameFromPID(pid);
if (!m_status_labels.count(pid))
if (!m_status_labels.contains(pid))
return;
m_status_labels[pid]->setText(

View file

@ -665,7 +665,7 @@ void NetPlayDialog::UpdateGUI()
auto* name_item = new QTableWidgetItem(QString::fromStdString(p->name));
name_item->setToolTip(name_item->text());
const auto& status_info = player_status.count(p->game_status) ?
const auto& status_info = player_status.contains(p->game_status) ?
player_status.at(p->game_status) :
std::make_pair(QStringLiteral("?"), QStringLiteral("?"));
auto* status_item = new QTableWidgetItem(status_info.first);

View file

@ -17,7 +17,7 @@ void ClearLayoutRecursively(QLayout* layout)
if (child->widget())
{
layout->removeWidget(child->widget());
delete child->widget();
child->widget()->deleteLater();
}
else if (child->layout())
{

View file

@ -504,7 +504,7 @@ bool RenderWidget::event(QEvent* event)
void RenderWidget::PassEventToPresenter(const QEvent* event)
{
if (!Core::IsRunningAndStarted())
if (!Core::IsRunning(Core::System::GetInstance()))
return;
switch (event->type())

View file

@ -59,7 +59,12 @@ Settings::Settings()
{
qRegisterMetaType<Core::State>();
Core::AddOnStateChangedCallback([this](Core::State new_state) {
QueueOnObject(this, [this, new_state] { emit EmulationStateChanged(new_state); });
QueueOnObject(this, [this, new_state] {
// Avoid signal spam while continuously frame stepping. Will still send a signal for the first
// and last framestep.
if (!m_continuously_frame_stepping)
emit EmulationStateChanged(new_state);
});
});
Config::AddConfigChangedCallback([this] {
@ -118,9 +123,8 @@ QSettings& Settings::GetQSettings()
return settings;
}
void Settings::SetThemeName(const QString& theme_name)
void Settings::TriggerThemeChanged()
{
Config::SetBaseOrCurrent(Config::MAIN_THEME_NAME, theme_name.toStdString());
emit ThemeChanged();
}
@ -355,11 +359,6 @@ void Settings::NotifyRefreshGameListComplete()
emit GameListRefreshCompleted();
}
void Settings::RefreshMetadata()
{
emit MetadataRefreshRequested();
}
void Settings::NotifyMetadataRefreshComplete()
{
emit MetadataRefreshCompleted();
@ -419,23 +418,11 @@ void Settings::SetStateSlot(int slot)
GetQSettings().setValue(QStringLiteral("Emulation/StateSlot"), slot);
}
void Settings::SetCursorVisibility(Config::ShowCursor hideCursor)
{
Config::SetBaseOrCurrent(Config::MAIN_SHOW_CURSOR, hideCursor);
emit CursorVisibilityChanged();
}
Config::ShowCursor Settings::GetCursorVisibility() const
{
return Config::Get(Config::MAIN_SHOW_CURSOR);
}
void Settings::SetLockCursor(bool lock_cursor)
{
Config::SetBaseOrCurrent(Config::MAIN_LOCK_CURSOR, lock_cursor);
emit LockCursorChanged();
}
bool Settings::GetLockCursor() const
{
return Config::Get(Config::MAIN_LOCK_CURSOR);
@ -446,7 +433,6 @@ void Settings::SetKeepWindowOnTop(bool top)
if (IsKeepWindowOnTopEnabled() == top)
return;
Config::SetBaseOrCurrent(Config::MAIN_KEEP_WINDOW_ON_TOP, top);
emit KeepWindowOnTopChanged(top);
}
@ -553,21 +539,10 @@ bool Settings::GetCheatsEnabled() const
return Config::Get(Config::MAIN_ENABLE_CHEATS);
}
void Settings::SetCheatsEnabled(bool enabled)
{
if (Config::Get(Config::MAIN_ENABLE_CHEATS) != enabled)
{
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, enabled);
emit EnableCheatsChanged(enabled);
}
}
void Settings::SetDebugModeEnabled(bool enabled)
{
#ifdef USE_RETRO_ACHIEVEMENTS
if (AchievementManager::GetInstance().IsHardcoreModeActive())
enabled = false;
#endif // USE_RETRO_ACHIEVEMENTS
if (IsDebugModeEnabled() != enabled)
{
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_DEBUGGING, enabled);
@ -848,3 +823,13 @@ void Settings::SetUSBKeyboardConnected(bool connected)
emit USBKeyboardConnectionChanged(connected);
}
}
void Settings::SetIsContinuouslyFrameStepping(bool is_stepping)
{
m_continuously_frame_stepping = is_stepping;
}
bool Settings::GetIsContinuouslyFrameStepping() const
{
return m_continuously_frame_stepping;
}

View file

@ -51,7 +51,7 @@ public:
static QSettings& GetQSettings();
// UI
void SetThemeName(const QString& theme_name);
void TriggerThemeChanged();
void InitDefaultPalette();
void UpdateSystemDark();
void SetSystemDark(bool dark);
@ -104,7 +104,6 @@ public:
void RefreshGameList();
void NotifyRefreshGameListStarted();
void NotifyRefreshGameListComplete();
void RefreshMetadata();
void NotifyMetadataRefreshComplete();
void ReloadTitleDB();
bool IsAutoRefreshEnabled() const;
@ -121,10 +120,11 @@ public:
bool IsUSBKeyboardConnected() const;
void SetUSBKeyboardConnected(bool connected);
void SetIsContinuouslyFrameStepping(bool is_stepping);
bool GetIsContinuouslyFrameStepping() const;
// Graphics
void SetCursorVisibility(Config::ShowCursor hideCursor);
Config::ShowCursor GetCursorVisibility() const;
void SetLockCursor(bool lock_cursor);
bool GetLockCursor() const;
void SetKeepWindowOnTop(bool top);
bool IsKeepWindowOnTopEnabled() const;
@ -145,7 +145,6 @@ public:
// Cheats
bool GetCheatsEnabled() const;
void SetCheatsEnabled(bool enabled);
// Debug
void SetDebugModeEnabled(bool enabled);
@ -226,11 +225,14 @@ signals:
void SDCardInsertionChanged(bool inserted);
void USBKeyboardConnectionChanged(bool connected);
void EnableGfxModsChanged(bool enabled);
void HardcoreStateChanged();
private:
Settings();
bool m_batch = false;
std::atomic<bool> m_continuously_frame_stepping = false;
std::shared_ptr<NetPlay::NetPlayClient> m_client;
std::shared_ptr<NetPlay::NetPlayServer> m_server;
ControllerInterface::HotplugCallbackHandle m_hotplug_callback_handle;

View file

@ -100,7 +100,7 @@ void AdvancedPane::CreateLayout()
clock_override_layout->addLayout(cpu_clock_override_slider_layout);
m_cpu_clock_override_slider = new QSlider(Qt::Horizontal);
m_cpu_clock_override_slider->setRange(0, 150);
m_cpu_clock_override_slider->setRange(1, 400);
cpu_clock_override_slider_layout->addWidget(m_cpu_clock_override_slider);
m_cpu_clock_override_slider_label = new QLabel();
@ -203,8 +203,7 @@ void AdvancedPane::ConnectLayout()
});
connect(m_cpu_clock_override_slider, &QSlider::valueChanged, [this](int oc_factor) {
// Vaguely exponential scaling?
const float factor = std::exp2f((m_cpu_clock_override_slider->value() - 100.f) / 25.f);
const float factor = m_cpu_clock_override_slider->value() / 100.f;
Config::SetBaseOrCurrent(Config::MAIN_OVERCLOCK, factor);
Update();
});
@ -271,8 +270,8 @@ void AdvancedPane::Update()
{
const QSignalBlocker blocker(m_cpu_clock_override_slider);
m_cpu_clock_override_slider->setValue(static_cast<int>(
std::round(std::log2f(Config::Get(Config::MAIN_OVERCLOCK)) * 25.f + 100.f)));
m_cpu_clock_override_slider->setValue(
static_cast<int>(std::round(Config::Get(Config::MAIN_OVERCLOCK) * 100.f)));
}
m_cpu_clock_override_slider_label->setText([] {

View file

@ -434,7 +434,7 @@ void GameCubePane::BrowseMemcard(ExpansionInterface::Slot slot)
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
const QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open or create"),
this, tr("Choose a File to Open or Create"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
tr("GameCube Memory Cards (*.raw *.gcp)"), nullptr, QFileDialog::DontConfirmOverwrite);
@ -538,8 +538,7 @@ void GameCubePane::BrowseGCIFolder(ExpansionInterface::Slot slot)
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
const QString path = DolphinFileDialog::getExistingDirectory(
this, tr("Choose the GCI base folder"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)));
this, tr("Choose GCI Base Folder"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)));
if (!path.isEmpty())
SetGCIFolder(slot, path);
@ -645,7 +644,7 @@ void GameCubePane::BrowseAGPRom(ExpansionInterface::Slot slot)
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
this, tr("Choose a File to Open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
tr("Game Boy Advance Carts (*.gba)"), nullptr, QFileDialog::DontConfirmOverwrite);
if (!filename.isEmpty())

View file

@ -24,8 +24,11 @@
#include "Core/PowerPC/PowerPC.h"
#include "Core/System.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipPushButton.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/NonDefaultQPushButton.h"
#include "DolphinQt/QtUtils/SetWindowDecorations.h"
#include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h"
@ -52,6 +55,7 @@ GeneralPane::GeneralPane(QWidget* parent) : QWidget(parent)
{
CreateLayout();
LoadConfig();
AddDescriptions();
ConnectLayout();
@ -81,14 +85,10 @@ void GeneralPane::CreateLayout()
void GeneralPane::OnEmulationStateChanged(Core::State state)
{
const bool running = state != Core::State::Uninitialized;
const bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
m_checkbox_dualcore->setEnabled(!running);
#ifdef USE_RETRO_ACHIEVEMENTS
bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
m_checkbox_cheats->setEnabled(!running && !hardcore);
#else // USE_RETRO_ACHIEVEMENTS
m_checkbox_cheats->setEnabled(!running);
#endif // USE_RETRO_ACHIEVEMENTS
m_checkbox_override_region_settings->setEnabled(!running);
#ifdef USE_DISCORD_PRESENCE
m_checkbox_discord_presence->setEnabled(!running);
@ -134,17 +134,18 @@ void GeneralPane::CreateBasic()
basic_group->setLayout(basic_group_layout);
m_main_layout->addWidget(basic_group);
m_checkbox_dualcore = new QCheckBox(tr("Enable Dual Core (speedup)"));
m_checkbox_dualcore = new ConfigBool(tr("Enable Dual Core (speedhack)"), Config::MAIN_CPU_THREAD);
basic_group_layout->addWidget(m_checkbox_dualcore);
m_checkbox_override_region_settings = new QCheckBox(tr("Allow Mismatched Region Settings"));
basic_group_layout->addWidget(m_checkbox_override_region_settings);
m_checkbox_auto_disc_change = new QCheckBox(tr("Change Discs Automatically"));
m_checkbox_auto_disc_change =
new ConfigBool(tr("Change Discs Automatically"), Config::MAIN_AUTO_DISC_CHANGE);
basic_group_layout->addWidget(m_checkbox_auto_disc_change);
#ifdef USE_DISCORD_PRESENCE
m_checkbox_discord_presence = new QCheckBox(tr("Show Current Game on Discord"));
m_checkbox_discord_presence = new ToolTipCheckBox(tr("Show Current Game on Discord"));
basic_group_layout->addWidget(m_checkbox_discord_presence);
#endif
@ -153,7 +154,7 @@ void GeneralPane::CreateBasic()
speed_limit_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
basic_group_layout->addLayout(speed_limit_layout);
m_combobox_speedlimit = new QComboBox();
m_combobox_speedlimit = new ToolTipComboBox();
m_combobox_speedlimit->addItem(tr("Unlimited"));
for (int i = 10; i <= 200; i += 10) // from 10% to 200%
@ -182,17 +183,11 @@ void GeneralPane::CreateFallbackRegion()
fallback_region_dropdown_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
fallback_region_group_layout->addLayout(fallback_region_dropdown_layout);
m_combobox_fallback_region = new QComboBox(this);
m_combobox_fallback_region = new ToolTipComboBox();
fallback_region_dropdown_layout->addRow(tr("Fallback Region:"), m_combobox_fallback_region);
for (const QString& option : {tr("NTSC-J"), tr("NTSC-U"), tr("PAL"), tr("NTSC-K")})
m_combobox_fallback_region->addItem(option);
auto* fallback_region_description =
new QLabel(tr("Dolphin will use this for titles whose region cannot be determined "
"automatically."));
fallback_region_description->setWordWrap(true);
fallback_region_group_layout->addWidget(fallback_region_description);
}
#if defined(USE_ANALYTICS) && USE_ANALYTICS
@ -203,9 +198,8 @@ void GeneralPane::CreateAnalytics()
analytics_group->setLayout(analytics_group_layout);
m_main_layout->addWidget(analytics_group);
m_checkbox_enable_analytics = new QCheckBox(tr("Enable Usage Statistics Reporting"));
m_button_generate_new_identity =
new NonDefaultQPushButton(tr("Generate a New Statistics Identity"));
m_checkbox_enable_analytics = new ToolTipCheckBox(tr("Enable Usage Statistics Reporting"));
m_button_generate_new_identity = new ToolTipPushButton(tr("Generate a New Statistics Identity"));
analytics_group_layout->addWidget(m_checkbox_enable_analytics);
analytics_group_layout->addWidget(m_button_generate_new_identity);
}
@ -340,12 +334,6 @@ void GeneralPane::OnSaveConfig()
Settings::Instance().SetAnalyticsEnabled(m_checkbox_enable_analytics->isChecked());
DolphinAnalytics::Instance().ReloadConfig();
#endif
Config::SetBaseOrCurrent(Config::MAIN_CPU_THREAD, m_checkbox_dualcore->isChecked());
Settings::Instance().SetCheatsEnabled(m_checkbox_cheats->isChecked());
Config::SetBaseOrCurrent(Config::MAIN_OVERRIDE_REGION_SETTINGS,
m_checkbox_override_region_settings->isChecked());
Config::SetBase(Config::MAIN_AUTO_DISC_CHANGE, m_checkbox_auto_disc_change->isChecked());
Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, m_checkbox_cheats->isChecked());
Settings::Instance().SetFallbackRegion(
UpdateFallbackRegionFromIndex(m_combobox_fallback_region->currentIndex()));
@ -371,4 +359,107 @@ void GeneralPane::OnCodeHandlerChanged(int index)
int code_handler_value = m_combobox_codehandler->itemData(index).toInt();
Config::SetBaseOrCurrent(Config::MAIN_CODE_HANDLER, code_handler_value);
Config::Save();
void GeneralPane::AddDescriptions()
{
static constexpr char TR_DUALCORE_DESCRIPTION[] =
QT_TR_NOOP("Separates CPU and GPU emulation work to separate threads. Reduces single-thread "
"burden by spreading Dolphin's heaviest load across two cores, which usually "
"improves performance. However, it can result in glitches and crashes."
"<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_CHEATS_DESCRIPTION[] = QT_TR_NOOP(
"Enables the use of AR and Gecko cheat codes which can be used to modify games' behavior. "
"These codes can be configured with the Cheats Manager in the Tools menu."
"<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_OVERRIDE_REGION_SETTINGS_DESCRIPTION[] =
QT_TR_NOOP("Lets you use languages and other region-related settings that the game may not "
"be designed for. May cause various crashes and bugs."
"<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_AUTO_DISC_CHANGE_DESCRIPTION[] = QT_TR_NOOP(
"Automatically changes the game disc when requested by games with two discs. This feature "
"requires the game to be launched in one of the following ways:"
"<br>- From the game list, with both discs being present in the game list."
"<br>- With File > Open or the command line interface, with the paths to both discs being "
"provided."
"<br>- By launching an M3U file with File > Open or the command line interface."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
#ifdef USE_DISCORD_PRESENCE
static constexpr char TR_DISCORD_PRESENCE_DESCRIPTION[] =
QT_TR_NOOP("Shows which game is active and the duration of your current play session in your "
"Discord status."
"<br><br>This setting cannot be changed while emulation is active."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
#endif
static constexpr char TR_SPEEDLIMIT_DESCRIPTION[] =
QT_TR_NOOP("Controls how fast emulation runs relative to the original hardware."
"<br><br>Values higher than 100% will emulate faster than the original hardware "
"can run, if your hardware is able to keep up. Values lower than 100% will slow "
"emulation instead. Unlimited will emulate as fast as your hardware is able to."
"<br><br><dolphin_emphasis>If unsure, select 100%.</dolphin_emphasis>");
static constexpr char TR_UPDATE_TRACK_DESCRIPTION[] = QT_TR_NOOP(
"Selects which update track Dolphin uses when checking for updates at startup. If a new "
"update is available, Dolphin will show a list of changes made since your current version "
"and ask you if you want to update."
"<br><br>The Dev track has the latest version of Dolphin which often updates multiple times "
"per day. Select this track if you want the newest features and fixes."
"<br><br>The Releases track has an update every few months. Some reasons you might prefer to "
"use this track:"
"<br>- You prefer using versions that have had additional testing."
"<br>- NetPlay requires players to have the same Dolphin version, and the latest Release "
"version will have the most players to match with."
"<br>- You frequently use Dolphin's savestate system, which doesn't guarantee backward "
"compatibility of savestates between Dolphin versions. If this applies to you, make sure you "
"make an in-game save before updating (i.e. save your game in the same way you would on a "
"physical GameCube or Wii), then load the in-game save after updating Dolphin and before "
"making any new savestates."
"<br><br>Selecting \"Don't Update\" will prevent Dolphin from automatically checking for "
"updates."
"<br><br><dolphin_emphasis>If unsure, select Releases.</dolphin_emphasis>");
static constexpr char TR_FALLBACK_REGION_DESCRIPTION[] =
QT_TR_NOOP("Sets the region used for titles whose region cannot be determined automatically."
"<br><br>This setting cannot be changed while emulation is active.");
#if defined(USE_ANALYTICS) && USE_ANALYTICS
static constexpr char TR_ENABLE_ANALYTICS_DESCRIPTION[] = QT_TR_NOOP(
"If selected, Dolphin can collect data on its performance, feature usage, emulated games, "
"and configuration, as well as data on your system's hardware and operating system."
"<br><br>No private data is ever collected. This data helps us understand how people and "
"emulated games use Dolphin and prioritize our efforts. It also helps us identify rare "
"configurations that are causing bugs, performance and stability issues.");
static constexpr char TR_GENERATE_NEW_IDENTITY_DESCRIPTION[] =
QT_TR_NOOP("Generate a new anonymous ID for your usage statistics. This will cause any "
"future statistics to be unassociated with your previous statistics.");
#endif
m_checkbox_dualcore->SetDescription(tr(TR_DUALCORE_DESCRIPTION));
m_checkbox_cheats->SetDescription(tr(TR_CHEATS_DESCRIPTION));
m_checkbox_override_region_settings->SetDescription(tr(TR_OVERRIDE_REGION_SETTINGS_DESCRIPTION));
m_checkbox_auto_disc_change->SetDescription(tr(TR_AUTO_DISC_CHANGE_DESCRIPTION));
#ifdef USE_DISCORD_PRESENCE
m_checkbox_discord_presence->SetDescription(tr(TR_DISCORD_PRESENCE_DESCRIPTION));
#endif
m_combobox_speedlimit->SetTitle(tr("Speed Limit"));
m_combobox_speedlimit->SetDescription(tr(TR_SPEEDLIMIT_DESCRIPTION));
if (AutoUpdateChecker::SystemSupportsAutoUpdates())
{
m_combobox_update_track->SetTitle(tr("Auto Update"));
m_combobox_update_track->SetDescription(tr(TR_UPDATE_TRACK_DESCRIPTION));
}
m_combobox_fallback_region->SetTitle(tr("Fallback Region"));
m_combobox_fallback_region->SetDescription(tr(TR_FALLBACK_REGION_DESCRIPTION));
#if defined(USE_ANALYTICS) && USE_ANALYTICS
m_checkbox_enable_analytics->SetDescription(tr(TR_ENABLE_ANALYTICS_DESCRIPTION));
m_button_generate_new_identity->SetTitle(tr("Generate a New Statistics Identity"));
m_button_generate_new_identity->SetDescription(tr(TR_GENERATE_NEW_IDENTITY_DESCRIPTION));
#endif
}

View file

@ -5,6 +5,7 @@
#include <QWidget>
class ConfigBool;
class QCheckBox;
class QComboBox;
class QLabel;
@ -12,6 +13,9 @@ class QPushButton;
class QRadioButton;
class QSlider;
class QVBoxLayout;
class ToolTipCheckBox;
class ToolTipComboBox;
class ToolTipPushButton;
namespace Core
{
@ -30,6 +34,8 @@ private:
void CreateBasic();
//void CreateAutoUpdate();
void CreateFallbackRegion();
void AddDescriptions();
void LoadConfig();
void OnSaveConfig();
void OnEmulationStateChanged(Core::State state);
@ -38,25 +44,23 @@ private:
// Widgets
QVBoxLayout* m_main_layout;
QComboBox* m_combobox_speedlimit;
QComboBox* m_combobox_update_track;
QComboBox* m_combobox_fallback_region;
QCheckBox* m_checkbox_dualcore;
QCheckBox* m_checkbox_cheats;
QCheckBox* m_checkbox_override_region_settings;
QCheckBox* m_checkbox_auto_disc_change;
QComboBox* m_combobox_codehandler;
ToolTipComboBox* m_combobox_speedlimit;
ToolTipComboBox* m_combobox_update_track;
ToolTipComboBox* m_combobox_fallback_region;
ConfigBool* m_checkbox_dualcore;
ConfigBool* m_checkbox_cheats;
ConfigBool* m_checkbox_override_region_settings;
ConfigBool* m_checkbox_auto_disc_change;
#ifdef USE_DISCORD_PRESENCE
QCheckBox* m_checkbox_discord_presence;
ToolTipCheckBox* m_checkbox_discord_presence;
#endif
QLabel* m_label_speedlimit;
// Analytics related
#if defined(USE_ANALYTICS) && USE_ANALYTICS
void CreateAnalytics();
void GenerateNewIdentity();
QPushButton* m_button_generate_new_identity;
QCheckBox* m_checkbox_enable_analytics;
ToolTipPushButton* m_button_generate_new_identity;
ToolTipCheckBox* m_checkbox_enable_analytics;
#endif
};

View file

@ -24,55 +24,60 @@
#include "Core/Config/MainSettings.h"
#include "Core/Config/UISettings.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Config/ConfigControls/ConfigChoice.h"
#include "DolphinQt/Config/ConfigControls/ConfigRadio.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
#include "DolphinQt/Config/ToolTipControls/ToolTipComboBox.h"
#include "DolphinQt/QtUtils/ModalMessageBox.h"
#include "DolphinQt/QtUtils/SignalBlocking.h"
#include "DolphinQt/Settings.h"
#include "UICommon/GameFile.h"
static QComboBox* MakeLanguageComboBox()
static ConfigStringChoice* MakeLanguageComboBox()
{
static const struct
{
const QString name;
const char* id;
} languages[] = {
{QStringLiteral(u"Bahasa Melayu"), "ms"}, // Malay
{QStringLiteral(u"Catal\u00E0"), "ca"}, // Catalan
{QStringLiteral(u"\u010Ce\u0161tina"), "cs"}, // Czech
{QStringLiteral(u"Dansk"), "da"}, // Danish
{QStringLiteral(u"Deutsch"), "de"}, // German
{QStringLiteral(u"English"), "en"}, // English
{QStringLiteral(u"Espa\u00F1ol"), "es"}, // Spanish
{QStringLiteral(u"Fran\u00E7ais"), "fr"}, // French
{QStringLiteral(u"Hrvatski"), "hr"}, // Croatian
{QStringLiteral(u"Italiano"), "it"}, // Italian
{QStringLiteral(u"Magyar"), "hu"}, // Hungarian
{QStringLiteral(u"Nederlands"), "nl"}, // Dutch
{QStringLiteral(u"Norsk bokm\u00E5l"), "nb"}, // Norwegian
{QStringLiteral(u"Polski"), "pl"}, // Polish
{QStringLiteral(u"Portugu\u00EAs"), "pt"}, // Portuguese
{QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_BR"}, // Portuguese (Brazil)
{QStringLiteral(u"Rom\u00E2n\u0103"), "ro"}, // Romanian
{QStringLiteral(u"Srpski"), "sr"}, // Serbian
{QStringLiteral(u"Suomi"), "fi"}, // Finnish
{QStringLiteral(u"Svenska"), "sv"}, // Swedish
{QStringLiteral(u"T\u00FCrk\u00E7e"), "tr"}, // Turkish
{QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"}, // Greek
{QStringLiteral(u"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "ru"}, // Russian
{QStringLiteral(u"\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "ar"}, // Arabic
{QStringLiteral(u"\u0641\u0627\u0631\u0633\u06CC"), "fa"}, // Farsi
{QStringLiteral(u"\uD55C\uAD6D\uC5B4"), "ko"}, // Korean
{QStringLiteral(u"\u65E5\u672C\u8A9E"), "ja"}, // Japanese
{QStringLiteral(u"\u7B80\u4F53\u4E2D\u6587"), "zh_CN"}, // Simplified Chinese
{QStringLiteral(u"\u7E41\u9AD4\u4E2D\u6587"), "zh_TW"}, // Traditional Chinese
using QPair = std::pair<QString, QString>;
std::vector<QPair> languages = {
QPair{QObject::tr("<System Language>"), QString{}},
QPair{QStringLiteral(u"Bahasa Melayu"), QStringLiteral("ms")}, // Malay
QPair{QStringLiteral(u"Catal\u00E0"), QStringLiteral("ca")}, // Catalan
QPair{QStringLiteral(u"\u010Ce\u0161tina"), QStringLiteral("cs")}, // Czech
QPair{QStringLiteral(u"Dansk"), QStringLiteral("da")}, // Danish
QPair{QStringLiteral(u"Deutsch"), QStringLiteral("de")}, // German
QPair{QStringLiteral(u"English"), QStringLiteral("en")}, // English
QPair{QStringLiteral(u"Espa\u00F1ol"), QStringLiteral("es")}, // Spanish
QPair{QStringLiteral(u"Fran\u00E7ais"), QStringLiteral("fr")}, // French
QPair{QStringLiteral(u"Hrvatski"), QStringLiteral("hr")}, // Croatian
QPair{QStringLiteral(u"Italiano"), QStringLiteral("it")}, // Italian
QPair{QStringLiteral(u"Magyar"), QStringLiteral("hu")}, // Hungarian
QPair{QStringLiteral(u"Nederlands"), QStringLiteral("nl")}, // Dutch
QPair{QStringLiteral(u"Norsk bokm\u00E5l"), QStringLiteral("nb")}, // Norwegian
QPair{QStringLiteral(u"Polski"), QStringLiteral("pl")}, // Polish
QPair{QStringLiteral(u"Portugu\u00EAs"), QStringLiteral("pt")}, // Portuguese
QPair{QStringLiteral(u"Portugu\u00EAs (Brasil)"),
QStringLiteral("pt_BR")}, // Portuguese (Brazil)
QPair{QStringLiteral(u"Rom\u00E2n\u0103"), QStringLiteral("ro")}, // Romanian
QPair{QStringLiteral(u"Srpski"), QStringLiteral("sr")}, // Serbian
QPair{QStringLiteral(u"Suomi"), QStringLiteral("fi")}, // Finnish
QPair{QStringLiteral(u"Svenska"), QStringLiteral("sv")}, // Swedish
QPair{QStringLiteral(u"T\u00FCrk\u00E7e"), QStringLiteral("tr")}, // Turkish
QPair{QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"),
QStringLiteral("el")}, // Greek
QPair{QStringLiteral(u"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"),
QStringLiteral("ru")}, // Russian
QPair{QStringLiteral(u"\u0627\u0644\u0639\u0631\u0628\u064A\u0629"),
QStringLiteral("ar")}, // Arabic
QPair{QStringLiteral(u"\u0641\u0627\u0631\u0633\u06CC"), QStringLiteral("fa")}, // Farsi
QPair{QStringLiteral(u"\uD55C\uAD6D\uC5B4"), QStringLiteral("ko")}, // Korean
QPair{QStringLiteral(u"\u65E5\u672C\u8A9E"), QStringLiteral("ja")}, // Japanese
QPair{QStringLiteral(u"\u7B80\u4F53\u4E2D\u6587"),
QStringLiteral("zh_CN")}, // Simplified Chinese
QPair{QStringLiteral(u"\u7E41\u9AD4\u4E2D\u6587"),
QStringLiteral("zh_TW")}, // Traditional Chinese
};
auto* combobox = new QComboBox();
combobox->addItem(QObject::tr("<System Language>"), QString{});
for (const auto& lang : languages)
combobox->addItem(lang.name, QString::fromLatin1(lang.id));
auto* const combobox = new ConfigStringChoice(languages, Config::MAIN_INTERFACE_LANGUAGE);
// The default, QComboBox::AdjustToContentsOnFirstShow, causes a noticeable pause when opening the
// SettingWindow for the first time. The culprit seems to be non-Latin graphemes in the above
@ -85,11 +90,12 @@ static QComboBox* MakeLanguageComboBox()
InterfacePane::InterfacePane(QWidget* parent) : QWidget(parent)
{
CreateLayout();
LoadConfig();
UpdateShowDebuggingCheckbox();
LoadUserStyle();
ConnectLayout();
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
&InterfacePane::LoadConfig);
&InterfacePane::UpdateShowDebuggingCheckbox);
}
void InterfacePane::CreateLayout()
@ -98,6 +104,7 @@ void InterfacePane::CreateLayout()
// Create layout here
CreateUI();
CreateInGame();
AddDescriptions();
m_main_layout->addStretch(1);
setLayout(m_main_layout);
@ -119,20 +126,20 @@ void InterfacePane::CreateUI()
m_combobox_language = MakeLanguageComboBox();
combobox_layout->addRow(tr("&Language:"), m_combobox_language);
// List avalable themes
auto theme_paths =
Common::DoFileSearch({File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR});
std::vector<std::string> theme_names;
theme_names.reserve(theme_paths.size());
std::transform(theme_paths.cbegin(), theme_paths.cend(), std::back_inserter(theme_names),
PathToFileName);
// Theme Combobox
m_combobox_theme = new QComboBox;
m_combobox_theme = new ConfigStringChoice(theme_names, Config::MAIN_THEME_NAME);
combobox_layout->addRow(tr("&Theme:"), m_combobox_theme);
// List avalable themes
auto theme_search_results =
Common::DoFileSearch({File::GetUserPath(D_THEMES_IDX), File::GetSysDirectory() + THEMES_DIR});
for (const std::string& path : theme_search_results)
{
const QString qt_name = QString::fromStdString(PathToFileName(path));
m_combobox_theme->addItem(qt_name);
}
// User Style Combobox
m_combobox_userstyle = new QComboBox;
m_combobox_userstyle = new ToolTipComboBox;
m_label_userstyle = new QLabel(tr("Style:"));
combobox_layout->addRow(m_label_userstyle, m_combobox_userstyle);
auto userstyle_search_results = Common::DoFileSearch({File::GetUserPath(D_STYLES_IDX)});
@ -154,12 +161,16 @@ void InterfacePane::CreateUI()
}
// Checkboxes
m_checkbox_use_builtin_title_database = new QCheckBox(tr("Use Built-In Database of Game Names"));
m_checkbox_use_builtin_title_database = new ConfigBool(tr("Use Built-In Database of Game Names"),
Config::MAIN_USE_BUILT_IN_TITLE_DATABASE);
m_checkbox_use_covers =
new QCheckBox(tr("Download Game Covers from GameTDB.com for Use in Grid Mode"));
new ConfigBool(tr("Download Game Covers from GameTDB.com for Use in Grid Mode"),
Config::MAIN_USE_GAME_COVERS);
m_checkbox_show_debugging_ui = new ToolTipCheckBox(tr("Enable Debugging UI"));
m_checkbox_focused_hotkeys = new QCheckBox(tr("Hotkeys Require Window Focus"));
m_checkbox_disable_screensaver = new QCheckBox(tr("Inhibit Screensaver During Emulation"));
m_checkbox_focused_hotkeys =
new ConfigBool(tr("Hotkeys Require Window Focus"), Config::MAIN_FOCUSED_HOTKEYS);
m_checkbox_disable_screensaver =
new ConfigBool(tr("Inhibit Screensaver During Emulation"), Config::MAIN_DISABLE_SCREENSAVER);
groupbox_layout->addWidget(m_checkbox_use_builtin_title_database);
groupbox_layout->addWidget(m_checkbox_use_covers);
@ -175,34 +186,36 @@ void InterfacePane::CreateInGame()
groupbox->setLayout(groupbox_layout);
m_main_layout->addWidget(groupbox);
m_checkbox_top_window = new QCheckBox(tr("Keep Window on Top"));
m_checkbox_confirm_on_stop = new QCheckBox(tr("Confirm on Stop"));
m_checkbox_use_panic_handlers = new QCheckBox(tr("Use Panic Handlers"));
m_checkbox_enable_osd = new QCheckBox(tr("Show On-Screen Display Messages"));
m_checkbox_show_active_title = new QCheckBox(tr("Show Active Title in Window Title"));
m_checkbox_pause_on_focus_lost = new QCheckBox(tr("Pause on Focus Loss"));
m_checkbox_top_window = new ConfigBool(tr("Keep Window on Top"), Config::MAIN_KEEP_WINDOW_ON_TOP);
m_checkbox_confirm_on_stop = new ConfigBool(tr("Confirm on Stop"), Config::MAIN_CONFIRM_ON_STOP);
m_checkbox_use_panic_handlers =
new ConfigBool(tr("Use Panic Handlers"), Config::MAIN_USE_PANIC_HANDLERS);
m_checkbox_enable_osd =
new ConfigBool(tr("Show On-Screen Display Messages"), Config::MAIN_OSD_MESSAGES);
m_checkbox_show_active_title =
new ConfigBool(tr("Show Active Title in Window Title"), Config::MAIN_SHOW_ACTIVE_TITLE);
m_checkbox_pause_on_focus_lost =
new ConfigBool(tr("Pause on Focus Loss"), Config::MAIN_PAUSE_ON_FOCUS_LOST);
auto* mouse_groupbox = new QGroupBox(tr("Mouse Cursor Visibility"));
auto* m_vboxlayout_hide_mouse = new QVBoxLayout;
mouse_groupbox->setLayout(m_vboxlayout_hide_mouse);
m_radio_cursor_visible_movement = new QRadioButton(tr("On Movement"));
m_radio_cursor_visible_movement->setToolTip(
tr("Mouse Cursor hides after inactivity and returns upon Mouse Cursor movement."));
m_radio_cursor_visible_never = new QRadioButton(tr("Never"));
m_radio_cursor_visible_never->setToolTip(
tr("Mouse Cursor will never be visible while a game is running."));
m_radio_cursor_visible_always = new QRadioButton(tr("Always"));
m_radio_cursor_visible_always->setToolTip(tr("Mouse Cursor will always be visible."));
m_radio_cursor_visible_movement =
new ConfigRadioInt(tr("On Movement"), Config::MAIN_SHOW_CURSOR,
static_cast<int>(Config::ShowCursor::OnMovement));
m_radio_cursor_visible_never = new ConfigRadioInt(tr("Never"), Config::MAIN_SHOW_CURSOR,
static_cast<int>(Config::ShowCursor::Never));
m_radio_cursor_visible_always = new ConfigRadioInt(
tr("Always"), Config::MAIN_SHOW_CURSOR, static_cast<int>(Config::ShowCursor::Constantly));
m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_movement);
m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_never);
m_vboxlayout_hide_mouse->addWidget(m_radio_cursor_visible_always);
m_checkbox_lock_mouse = new ConfigBool(tr("Lock Mouse Cursor"), Config::MAIN_LOCK_CURSOR);
// this ends up not being managed unless _WIN32, so lets not leak
m_checkbox_lock_mouse = new QCheckBox(tr("Lock Mouse Cursor"), this);
m_checkbox_lock_mouse->setToolTip(tr("Will lock the Mouse Cursor to the Render Widget as long as "
"it has focus. You can set a hotkey to unlock it."));
m_checkbox_lock_mouse->setParent(this);
mouse_groupbox->setLayout(m_vboxlayout_hide_mouse);
groupbox_layout->addWidget(m_checkbox_top_window);
@ -221,62 +234,58 @@ void InterfacePane::CreateInGame()
void InterfacePane::ConnectLayout()
{
connect(m_checkbox_use_builtin_title_database, &QCheckBox::toggled, this,
&InterfacePane::OnSaveConfig);
connect(m_checkbox_use_covers, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_disable_screensaver, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_show_debugging_ui, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_focused_hotkeys, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_combobox_theme, &QComboBox::currentIndexChanged, this, [this](int index) {
Settings::Instance().SetThemeName(m_combobox_theme->itemText(index));
});
connect(m_checkbox_use_builtin_title_database, &QCheckBox::toggled, &Settings::Instance(),
&Settings::GameListRefreshRequested);
connect(m_checkbox_use_covers, &QCheckBox::toggled, &Settings::Instance(),
&Settings::MetadataRefreshRequested);
connect(m_checkbox_show_debugging_ui, &QCheckBox::toggled, &Settings::Instance(),
&Settings::SetDebugModeEnabled);
connect(m_combobox_theme, &QComboBox::currentIndexChanged, &Settings::Instance(),
&Settings::ThemeChanged);
connect(m_combobox_userstyle, &QComboBox::currentIndexChanged, this,
&InterfacePane::OnSaveConfig);
connect(m_combobox_language, &QComboBox::currentIndexChanged, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_top_window, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_confirm_on_stop, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_use_panic_handlers, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_show_active_title, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_enable_osd, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_checkbox_pause_on_focus_lost, &QCheckBox::toggled, this, &InterfacePane::OnSaveConfig);
connect(m_radio_cursor_visible_movement, &QRadioButton::toggled, this,
&InterfacePane::OnCursorVisibleMovement);
connect(m_radio_cursor_visible_never, &QRadioButton::toggled, this,
&InterfacePane::OnCursorVisibleNever);
connect(m_radio_cursor_visible_always, &QRadioButton::toggled, this,
&InterfacePane::OnCursorVisibleAlways);
&InterfacePane::OnUserStyleChanged);
connect(m_combobox_language, &QComboBox::currentIndexChanged, this,
&InterfacePane::OnLanguageChanged);
connect(m_checkbox_top_window, &QCheckBox::toggled, &Settings::Instance(),
&Settings::KeepWindowOnTopChanged);
connect(m_radio_cursor_visible_movement, &ConfigRadioInt::OnSelected, &Settings::Instance(),
&Settings::CursorVisibilityChanged);
connect(m_radio_cursor_visible_never, &ConfigRadioInt::OnSelected, &Settings::Instance(),
&Settings::CursorVisibilityChanged);
connect(m_radio_cursor_visible_always, &ConfigRadioInt::OnSelected, &Settings::Instance(),
&Settings::CursorVisibilityChanged);
connect(m_checkbox_lock_mouse, &QCheckBox::toggled, &Settings::Instance(),
&Settings::SetLockCursor);
&Settings::LockCursorChanged);
}
void InterfacePane::LoadConfig()
void InterfacePane::UpdateShowDebuggingCheckbox()
{
SignalBlocking(m_checkbox_use_builtin_title_database)
->setChecked(Config::Get(Config::MAIN_USE_BUILT_IN_TITLE_DATABASE));
SignalBlocking(m_checkbox_show_debugging_ui)
->setChecked(Settings::Instance().IsDebugModeEnabled());
#ifdef USE_RETRO_ACHIEVEMENTS
static constexpr char TR_SHOW_DEBUGGING_UI_DESCRIPTION[] = QT_TR_NOOP(
"Shows Dolphin's debugging user interface. This lets you view and modify a game's code and "
"memory contents, set debugging breakpoints, examine network requests, and more."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_DISABLED_IN_HARDCORE_DESCRIPTION[] =
QT_TR_NOOP("<dolphin_emphasis>Disabled in Hardcore Mode.</dolphin_emphasis>");
bool hardcore = AchievementManager::GetInstance().IsHardcoreModeActive();
SignalBlocking(m_checkbox_show_debugging_ui)->setEnabled(!hardcore);
if (hardcore)
{
m_checkbox_show_debugging_ui->SetDescription(
tr("<dolphin_emphasis>Disabled in Hardcore Mode.</dolphin_emphasis>"));
m_checkbox_show_debugging_ui->SetDescription(tr("%1<br><br>%2")
.arg(tr(TR_SHOW_DEBUGGING_UI_DESCRIPTION))
.arg(tr(TR_DISABLED_IN_HARDCORE_DESCRIPTION)));
}
else
{
m_checkbox_show_debugging_ui->SetDescription({});
m_checkbox_show_debugging_ui->SetDescription(tr(TR_SHOW_DEBUGGING_UI_DESCRIPTION));
}
#endif // USE_RETRO_ACHIEVEMENTS
SignalBlocking(m_combobox_language)
->setCurrentIndex(m_combobox_language->findData(
QString::fromStdString(Config::Get(Config::MAIN_INTERFACE_LANGUAGE))));
SignalBlocking(m_combobox_theme)
->setCurrentIndex(
m_combobox_theme->findText(QString::fromStdString(Config::Get(Config::MAIN_THEME_NAME))));
}
void InterfacePane::LoadUserStyle()
{
const Settings::StyleType style_type = Settings::Instance().GetStyleType();
const QString userstyle = Settings::Instance().GetUserStyleName();
const int index = style_type == Settings::StyleType::User ?
@ -285,37 +294,10 @@ void InterfacePane::LoadConfig()
if (index > 0)
SignalBlocking(m_combobox_userstyle)->setCurrentIndex(index);
// Render Window Options
SignalBlocking(m_checkbox_top_window)
->setChecked(Settings::Instance().IsKeepWindowOnTopEnabled());
SignalBlocking(m_checkbox_confirm_on_stop)->setChecked(Config::Get(Config::MAIN_CONFIRM_ON_STOP));
SignalBlocking(m_checkbox_use_panic_handlers)
->setChecked(Config::Get(Config::MAIN_USE_PANIC_HANDLERS));
SignalBlocking(m_checkbox_enable_osd)->setChecked(Config::Get(Config::MAIN_OSD_MESSAGES));
SignalBlocking(m_checkbox_show_active_title)
->setChecked(Config::Get(Config::MAIN_SHOW_ACTIVE_TITLE));
SignalBlocking(m_checkbox_pause_on_focus_lost)
->setChecked(Config::Get(Config::MAIN_PAUSE_ON_FOCUS_LOST));
SignalBlocking(m_checkbox_use_covers)->setChecked(Config::Get(Config::MAIN_USE_GAME_COVERS));
SignalBlocking(m_checkbox_focused_hotkeys)->setChecked(Config::Get(Config::MAIN_FOCUSED_HOTKEYS));
SignalBlocking(m_radio_cursor_visible_movement)
->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::OnMovement);
SignalBlocking(m_radio_cursor_visible_always)
->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::Constantly);
SignalBlocking(m_radio_cursor_visible_never)
->setChecked(Settings::Instance().GetCursorVisibility() == Config::ShowCursor::Never);
SignalBlocking(m_checkbox_lock_mouse)->setChecked(Settings::Instance().GetLockCursor());
SignalBlocking(m_checkbox_disable_screensaver)
->setChecked(Config::Get(Config::MAIN_DISABLE_SCREENSAVER));
}
void InterfacePane::OnSaveConfig()
void InterfacePane::OnUserStyleChanged()
{
Config::SetBase(Config::MAIN_USE_BUILT_IN_TITLE_DATABASE,
m_checkbox_use_builtin_title_database->isChecked());
Settings::Instance().SetDebugModeEnabled(m_checkbox_show_debugging_ui->isChecked());
const auto selected_style = m_combobox_userstyle->currentData();
bool is_builtin_type = false;
const int style_type_int = selected_style.toInt(&is_builtin_type);
@ -325,49 +307,114 @@ void InterfacePane::OnSaveConfig()
if (!is_builtin_type)
Settings::Instance().SetUserStyleName(selected_style.toString());
Settings::Instance().ApplyStyle();
// Render Window Options
Settings::Instance().SetKeepWindowOnTop(m_checkbox_top_window->isChecked());
Config::SetBase(Config::MAIN_CONFIRM_ON_STOP, m_checkbox_confirm_on_stop->isChecked());
Config::SetBase(Config::MAIN_USE_PANIC_HANDLERS, m_checkbox_use_panic_handlers->isChecked());
Config::SetBase(Config::MAIN_OSD_MESSAGES, m_checkbox_enable_osd->isChecked());
Config::SetBase(Config::MAIN_SHOW_ACTIVE_TITLE, m_checkbox_show_active_title->isChecked());
Config::SetBase(Config::MAIN_PAUSE_ON_FOCUS_LOST, m_checkbox_pause_on_focus_lost->isChecked());
auto new_language = m_combobox_language->currentData().toString().toStdString();
if (new_language != Config::Get(Config::MAIN_INTERFACE_LANGUAGE))
{
Config::SetBase(Config::MAIN_INTERFACE_LANGUAGE, new_language);
ModalMessageBox::information(
this, tr("Restart Required"),
tr("You must restart Dolphin in order for the change to take effect."));
}
const bool use_covers = m_checkbox_use_covers->isChecked();
if (use_covers != Config::Get(Config::MAIN_USE_GAME_COVERS))
{
Config::SetBase(Config::MAIN_USE_GAME_COVERS, use_covers);
Settings::Instance().RefreshMetadata();
}
Config::SetBase(Config::MAIN_FOCUSED_HOTKEYS, m_checkbox_focused_hotkeys->isChecked());
Config::SetBase(Config::MAIN_DISABLE_SCREENSAVER, m_checkbox_disable_screensaver->isChecked());
Config::Save();
}
void InterfacePane::OnCursorVisibleMovement()
void InterfacePane::OnLanguageChanged()
{
Settings::Instance().SetCursorVisibility(Config::ShowCursor::OnMovement);
ModalMessageBox::information(
this, tr("Restart Required"),
tr("You must restart Dolphin in order for the change to take effect."));
}
void InterfacePane::OnCursorVisibleNever()
void InterfacePane::AddDescriptions()
{
Settings::Instance().SetCursorVisibility(Config::ShowCursor::Never);
}
static constexpr char TR_TITLE_DATABASE_DESCRIPTION[] = QT_TR_NOOP(
"Uses Dolphin's database of properly formatted names in the game list's Title column."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_THEME_DESCRIPTION[] =
QT_TR_NOOP("Changes the appearance and color of Dolphin's buttons."
"<br><br><dolphin_emphasis>If unsure, select Clean.</dolphin_emphasis>");
static constexpr char TR_TOP_WINDOW_DESCRIPTION[] =
QT_TR_NOOP("Forces the render window to stay on top of other windows and applications."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_LANGUAGE_DESCRIPTION[] = QT_TR_NOOP(
"Sets the language displayed by Dolphin's user interface."
"<br><br>Changes to this setting only take effect once Dolphin is restarted."
"<br><br><dolphin_emphasis>If unsure, select &lt;System Language&gt;.</dolphin_emphasis>");
static constexpr char TR_FOCUSED_HOTKEYS_DESCRIPTION[] =
QT_TR_NOOP("Requires the render window to be focused for hotkeys to take effect."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_USE_COVERS_DESCRIPTION[] =
QT_TR_NOOP("Downloads full game covers from GameTDB.com to display in the game list's Grid "
"View. If this setting is unchecked, the game list displays a banner from the "
"game's save data, and if the game has no save file, displays a generic "
"banner instead."
"<br><br>List View will always use the save file banners."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_DISABLE_SCREENSAVER_DESCRIPTION[] =
QT_TR_NOOP("Disables your screensaver while running a game."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_CONFIRM_ON_STOP_DESCRIPTION[] =
QT_TR_NOOP("Prompts you to confirm that you want to end emulation when you press Stop."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_USE_PANIC_HANDLERS_DESCRIPTION[] =
QT_TR_NOOP("In the event of an error, Dolphin will halt to inform you of the error and "
"present choices on how to proceed. With this option disabled, Dolphin will "
"\"ignore\" all errors. Emulation will not be halted and you will not be notified."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_ENABLE_OSD_DESCRIPTION[] =
QT_TR_NOOP("Shows on-screen display messages over the render window. These messages "
"disappear after several seconds."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_SHOW_ACTIVE_TITLE_DESCRIPTION[] =
QT_TR_NOOP("Shows the active game title in the render window's title bar."
"<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
static constexpr char TR_PAUSE_ON_FOCUS_LOST_DESCRIPTION[] =
QT_TR_NOOP("Pauses the game whenever the render window isn't focused."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_LOCK_MOUSE_DESCRIPTION[] =
QT_TR_NOOP("Locks the mouse cursor to the Render Widget as long as it has focus. You can "
"set a hotkey to unlock it."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
static constexpr char TR_CURSOR_VISIBLE_MOVEMENT_DESCRIPTION[] =
QT_TR_NOOP("Shows the mouse cursor briefly whenever it has recently moved, then hides it."
"<br><br><dolphin_emphasis>If unsure, select this mode.</dolphin_emphasis>");
static constexpr char TR_CURSOR_VISIBLE_NEVER_DESCRIPTION[] = QT_TR_NOOP(
"Hides the mouse cursor whenever it is inside the render window and the render window is "
"focused."
"<br><br><dolphin_emphasis>If unsure, select &quot;On Movement&quot;.</dolphin_emphasis>");
static constexpr char TR_CURSOR_VISIBLE_ALWAYS_DESCRIPTION[] = QT_TR_NOOP(
"Shows the mouse cursor at all times."
"<br><br><dolphin_emphasis>If unsure, select &quot;On Movement&quot;.</dolphin_emphasis>");
static constexpr char TR_USER_STYLE_DESCRIPTION[] =
QT_TR_NOOP("Sets the style of Dolphin's user interface. Any custom styles that you have "
"added will be presented here, allowing you to switch to them."
"<br><br><dolphin_emphasis>If unsure, select (System).</dolphin_emphasis>");
void InterfacePane::OnCursorVisibleAlways()
{
Settings::Instance().SetCursorVisibility(Config::ShowCursor::Constantly);
m_checkbox_use_builtin_title_database->SetDescription(tr(TR_TITLE_DATABASE_DESCRIPTION));
m_combobox_theme->SetTitle(tr("Theme"));
m_combobox_theme->SetDescription(tr(TR_THEME_DESCRIPTION));
m_checkbox_top_window->SetDescription(tr(TR_TOP_WINDOW_DESCRIPTION));
m_combobox_language->SetTitle(tr("Language"));
m_combobox_language->SetDescription(tr(TR_LANGUAGE_DESCRIPTION));
m_checkbox_focused_hotkeys->SetDescription(tr(TR_FOCUSED_HOTKEYS_DESCRIPTION));
m_checkbox_use_covers->SetDescription(tr(TR_USE_COVERS_DESCRIPTION));
m_checkbox_disable_screensaver->SetDescription(tr(TR_DISABLE_SCREENSAVER_DESCRIPTION));
m_checkbox_confirm_on_stop->SetDescription(tr(TR_CONFIRM_ON_STOP_DESCRIPTION));
m_checkbox_use_panic_handlers->SetDescription(tr(TR_USE_PANIC_HANDLERS_DESCRIPTION));
m_checkbox_enable_osd->SetDescription(tr(TR_ENABLE_OSD_DESCRIPTION));
m_checkbox_show_active_title->SetDescription(tr(TR_SHOW_ACTIVE_TITLE_DESCRIPTION));
m_checkbox_pause_on_focus_lost->SetDescription(tr(TR_PAUSE_ON_FOCUS_LOST_DESCRIPTION));
m_checkbox_lock_mouse->SetDescription(tr(TR_LOCK_MOUSE_DESCRIPTION));
m_radio_cursor_visible_movement->SetDescription(tr(TR_CURSOR_VISIBLE_MOVEMENT_DESCRIPTION));
m_radio_cursor_visible_never->SetDescription(tr(TR_CURSOR_VISIBLE_NEVER_DESCRIPTION));
m_radio_cursor_visible_always->SetDescription(tr(TR_CURSOR_VISIBLE_ALWAYS_DESCRIPTION));
m_combobox_userstyle->SetTitle(tr("Style"));
m_combobox_userstyle->SetDescription(tr(TR_USER_STYLE_DESCRIPTION));
}

View file

@ -5,12 +5,13 @@
#include <QWidget>
class QCheckBox;
class QComboBox;
class ConfigBool;
class ConfigRadioInt;
class ConfigStringChoice;
class QLabel;
class QRadioButton;
class QVBoxLayout;
class ToolTipCheckBox;
class ToolTipComboBox;
class InterfacePane final : public QWidget
{
@ -22,34 +23,33 @@ private:
void CreateLayout();
void CreateUI();
void CreateInGame();
void AddDescriptions();
void ConnectLayout();
void LoadConfig();
void OnSaveConfig();
void OnCursorVisibleMovement();
void OnCursorVisibleNever();
void OnCursorVisibleAlways();
void UpdateShowDebuggingCheckbox();
void LoadUserStyle();
void OnUserStyleChanged();
void OnLanguageChanged();
QVBoxLayout* m_main_layout;
QComboBox* m_combobox_language;
ConfigStringChoice* m_combobox_language;
QComboBox* m_combobox_theme;
QComboBox* m_combobox_userstyle;
ConfigStringChoice* m_combobox_theme;
ToolTipComboBox* m_combobox_userstyle;
QLabel* m_label_userstyle;
QCheckBox* m_checkbox_top_window;
QCheckBox* m_checkbox_use_builtin_title_database;
QCheckBox* m_checkbox_use_userstyle;
ConfigBool* m_checkbox_top_window;
ConfigBool* m_checkbox_use_builtin_title_database;
ToolTipCheckBox* m_checkbox_show_debugging_ui;
QCheckBox* m_checkbox_focused_hotkeys;
QCheckBox* m_checkbox_use_covers;
QCheckBox* m_checkbox_disable_screensaver;
ConfigBool* m_checkbox_focused_hotkeys;
ConfigBool* m_checkbox_use_covers;
ConfigBool* m_checkbox_disable_screensaver;
QCheckBox* m_checkbox_confirm_on_stop;
QCheckBox* m_checkbox_use_panic_handlers;
QCheckBox* m_checkbox_enable_osd;
QCheckBox* m_checkbox_show_active_title;
QCheckBox* m_checkbox_pause_on_focus_lost;
QRadioButton* m_radio_cursor_visible_movement;
QRadioButton* m_radio_cursor_visible_never;
QRadioButton* m_radio_cursor_visible_always;
QCheckBox* m_checkbox_lock_mouse;
ConfigBool* m_checkbox_confirm_on_stop;
ConfigBool* m_checkbox_use_panic_handlers;
ConfigBool* m_checkbox_enable_osd;
ConfigBool* m_checkbox_show_active_title;
ConfigBool* m_checkbox_pause_on_focus_lost;
ConfigRadioInt* m_radio_cursor_visible_movement;
ConfigRadioInt* m_radio_cursor_visible_never;
ConfigRadioInt* m_radio_cursor_visible_always;
ConfigBool* m_checkbox_lock_mouse;
};

View file

@ -113,7 +113,7 @@ void USBDeviceAddToWhitelistDialog::RefreshDeviceList()
auto whitelist = Config::GetUSBDeviceWhitelist();
for (const auto& device : current_devices)
{
if (whitelist.count({device.first.first, device.first.second}) != 0)
if (whitelist.contains({device.first.first, device.first.second}))
continue;
usb_inserted_devices_list->addItem(QString::fromStdString(device.second));
}

View file

@ -503,7 +503,7 @@ void WiiPane::PopulateUSBPassthroughListWidget()
void WiiPane::BrowseSDRaw()
{
QString file = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName(
this, tr("Select a SD Card Image"),
this, tr("Select SD Card Image"),
QString::fromStdString(Config::Get(Config::MAIN_WII_SD_CARD_IMAGE_PATH)),
tr("SD Card Image (*.raw);;"
"All Files (*)")));
@ -520,7 +520,7 @@ void WiiPane::SetSDRaw(const QString& path)
void WiiPane::BrowseSDSyncFolder()
{
QString file = QDir::toNativeSeparators(DolphinFileDialog::getExistingDirectory(
this, tr("Select a Folder to sync with the SD Card Image"),
this, tr("Select a Folder to Sync with the SD Card Image"),
QString::fromStdString(Config::Get(Config::MAIN_WII_SD_CARD_SYNC_FOLDER_PATH))));
if (!file.isEmpty())
SetSDSyncFolder(file);

View file

@ -81,7 +81,7 @@ SkylanderPortalWindow::SkylanderPortalWindow(QWidget* parent) : QWidget(parent)
m_collection_path = QDir::toNativeSeparators(skylanders_folder.path()) + QDir::separator();
m_last_skylander_path = m_collection_path;
m_path_edit->setText(m_collection_path);
};
}
SkylanderPortalWindow::~SkylanderPortalWindow() = default;

View file

@ -493,3 +493,10 @@ QTableCornerButton::section {
border-left: 0px;
border-bottom: 0px;
}
QProgressBar {
border: 2px solid grey;
border-radius: 5px;
background-color: #202020;
}

View file

@ -6,7 +6,9 @@
#include <cmath>
#include <utility>
#include <QApplication>
#include <QCheckBox>
#include <QEvent>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
@ -17,6 +19,7 @@
#include "Common/CommonTypes.h"
#include "DolphinQt/Host.h"
#include "DolphinQt/QtUtils/AspectRatioWidget.h"
#include "DolphinQt/QtUtils/QueueOnObject.h"
#include "DolphinQt/Resources.h"
@ -268,3 +271,16 @@ std::optional<ControlState> TASInputWindow::GetSpinBox(TASSpinBox* spin, int zer
return (spin->GetValue() - zero) / scale;
}
void TASInputWindow::changeEvent(QEvent* const event)
{
if (event->type() == QEvent::ActivationChange)
{
const bool active_window_is_tas_input =
qobject_cast<TASInputWindow*>(QApplication::activeWindow()) != nullptr;
// Switching between TAS Input windows will call SetTASInputFocus(true) twice, but that's fine.
Host::GetInstance()->SetTASInputFocus(active_window_is_tas_input);
}
QDialog::changeEvent(event);
}

View file

@ -18,6 +18,7 @@
class QBoxLayout;
class QCheckBox;
class QDialog;
class QEvent;
class QGroupBox;
class QSpinBox;
class QString;
@ -68,6 +69,8 @@ protected:
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget);
void changeEvent(QEvent* event) override;
QGroupBox* m_settings_box;
QCheckBox* m_use_controller;
QSpinBox* m_turbo_press_frames;