diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c0dcb9bfcd..fac0ea5b92 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -230,36 +230,28 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) // RPCS3 Updater - QMenu* download_menu = new QMenu(tr("Update Available!")); - - QAction* download_action = new QAction(tr("Download Update"), download_menu); - connect(download_action, &QAction::triggered, this, [this] + connect(ui->actionDownload_Update, &QAction::triggered, this, [this] { m_updater.update(false); }); - download_menu->addAction(download_action); - #ifdef _WIN32 // Use a menu at the top right corner to indicate the new version. - QMenuBar *corner_bar = new QMenuBar(ui->menuBar); - m_download_menu_action = corner_bar->addMenu(download_menu); + // Some distros just can't handle corner widgets at the moment. + QMenuBar* corner_bar = new QMenuBar(ui->menuBar); + corner_bar->addMenu(ui->menuUpdate_Available); ui->menuBar->setCornerWidget(corner_bar); ui->menuBar->cornerWidget()->setVisible(false); -#else - // Append a menu to the right of the regular menus to indicate the new version. - // Some distros just can't handle corner widgets at the moment. - m_download_menu_action = ui->menuBar->addMenu(download_menu); + ui->menuBar->removeAction(ui->menuUpdate_Available->menuAction()); #endif - ensure(m_download_menu_action); - m_download_menu_action->setVisible(false); + ui->menuUpdate_Available->setVisible(false); connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available) { - if (m_download_menu_action) + if (ui->menuUpdate_Available) { - m_download_menu_action->setVisible(update_available); + ui->menuUpdate_Available->setVisible(update_available); } if (ui->menuBar && ui->menuBar->cornerWidget()) { @@ -1933,9 +1925,11 @@ void main_window::OnEmuRun(bool /*start_playtime*/) EnableMenus(true); update_gui_pad_thread(); + + m_system_state = system_state::running; } -void main_window::OnEmuResume() const +void main_window::OnEmuResume() { const QString title = GetCurrentTitle(); const QString restart_tooltip = tr("Restart %0").arg(title); @@ -1948,9 +1942,11 @@ void main_window::OnEmuResume() const ui->toolbar_start->setText(tr("Pause")); ui->toolbar_start->setToolTip(pause_tooltip); ui->toolbar_stop->setToolTip(stop_tooltip); + + m_system_state = system_state::starting; // Let's just use this state to distinguish between resumed and running } -void main_window::OnEmuPause() const +void main_window::OnEmuPause() { const QString title = GetCurrentTitle(); const QString resume_tooltip = tr("Resume %0").arg(title); @@ -1966,6 +1962,8 @@ void main_window::OnEmuPause() const { m_game_list_frame->Refresh(); } + + m_system_state = system_state::paused; } void main_window::OnEmuStop() @@ -2026,9 +2024,11 @@ void main_window::OnEmuStop() } update_gui_pad_thread(); + + m_system_state = system_state::stopped; } -void main_window::OnEmuReady() const +void main_window::OnEmuReady() { const QString title = GetCurrentTitle(); const QString play_tooltip = tr("Play %0").arg(title); @@ -2054,6 +2054,8 @@ void main_window::OnEmuReady() const ui->removeAllCachesAct->setEnabled(false); ui->removeSavestatesAct->setEnabled(false); ui->cleanUpGameListAct->setEnabled(false); + + m_system_state = system_state::ready; } void main_window::EnableMenus(bool enabled) const @@ -2340,6 +2342,20 @@ void main_window::RetranslateUI(const QStringList& language_codes, const QString ui->retranslateUi(this); + // Update menu bar size (needed if the corner widget changes its size) + ui->menuBar->adjustSize(); + + // Update toolbar elements + switch (m_system_state) + { + case system_state::running: OnEmuRun(false); break; + case system_state::stopped: OnEmuStop(); break; + case system_state::paused: OnEmuPause(); break; + case system_state::starting: OnEmuResume(); break; + case system_state::ready: OnEmuReady(); break; + default: break; + } + if (m_game_list_frame) { m_game_list_frame->Refresh(true); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 4e5b498587..960a70c722 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -11,6 +11,7 @@ #include "settings.h" #include "shortcut_handler.h" #include "Emu/config_mode.h" +#include "Emu/System.h" #include @@ -88,9 +89,9 @@ Q_SIGNALS: public Q_SLOTS: void OnEmuStop(); void OnEmuRun(bool start_playtime); - void OnEmuResume() const; - void OnEmuPause() const; - void OnEmuReady() const; + void OnEmuResume(); + void OnEmuPause(); + void OnEmuReady(); void OnEnableDiscEject(bool enabled) const; void OnEnableDiscInsert(bool enabled) const; void OnAddBreakpoint(u32 addr) const; @@ -196,9 +197,10 @@ private: std::shared_ptr m_persistent_settings; update_manager m_updater; - QAction* m_download_menu_action = nullptr; shortcut_handler* m_shortcut_handler = nullptr; std::unique_ptr m_gui_pad_thread; + + system_state m_system_state = system_state::stopped; }; diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 5ef0b98f25..72861a5d72 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -412,6 +412,12 @@ + + + Update Available! + + + @@ -419,6 +425,7 @@ + @@ -1448,6 +1455,11 @@ Sound Effects + + + Download Update + + diff --git a/rpcs3/rpcs3qt/movie_item.cpp b/rpcs3/rpcs3qt/movie_item.cpp index 3cfb269709..eee10cc3ba 100644 --- a/rpcs3/rpcs3qt/movie_item.cpp +++ b/rpcs3/rpcs3qt/movie_item.cpp @@ -3,12 +3,15 @@ movie_item::movie_item() : QTableWidgetItem(), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QString& text, int type) : QTableWidgetItem(text, type), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QIcon& icon, const QString& text, int type) : QTableWidgetItem(icon, text, type), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp index 44620bd24c..ed664e1b65 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp @@ -85,6 +85,7 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr m_savestate_table->horizontalScrollBar()->setSingleStep(20); m_savestate_table->setItemDelegate(new table_item_delegate(m_savestate_table, false)); m_savestate_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_savestate_table->setSelectionMode(QAbstractItemView::SingleSelection); m_savestate_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_savestate_table->setColumnCount(static_cast(gui::savestate_list_columns::count)); m_savestate_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index 8b07693e34..6929e08318 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -56,8 +56,7 @@ void update_manager::check_for_updates(bool automatic, bool check_only, bool aut { update_log.notice("Checking for updates: automatic=%d, check_only=%d, auto_accept=%d", automatic, check_only, auto_accept); - m_update_message.clear(); - m_changelog.clear(); + m_update_info = {}; if (automatic) { @@ -103,7 +102,7 @@ void update_manager::check_for_updates(bool automatic, bool check_only, bool aut } } - Q_EMIT signal_update_available(result_json && !m_update_message.isEmpty()); + Q_EMIT signal_update_available(result_json && m_update_info.update_found); }); const utils::OS_version os = utils::get_OS_version(); @@ -121,7 +120,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce const QJsonObject json_data = QJsonDocument::fromJson(data).object(); const int return_code = json_data["return_code"].toInt(-255); - bool hash_found = true; + m_update_info.hash_found = true; if (return_code < 0) { @@ -143,7 +142,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce // If a user clicks "Check for Updates" with a custom build ask him if he's sure he wants to update to latest version if (!automatic && return_code == -1) { - hash_found = false; + m_update_info.hash_found = false; } else { @@ -187,7 +186,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce check_json(latest["version"].isString(), "Node 'latest_build: version' not found or not a string") && check_json(latest["datetime"].isString(), "Node 'latest_build: datetime' not found or not a string") ) || - (hash_found && !( + (m_update_info.hash_found && !( check_json(current.isObject(), "JSON doesn't contain current_build section") && check_json(current["version"].isString(), "Node 'current_build: datetime' not found or not a string") && check_json(current["datetime"].isString(), "Node 'current_build: version' not found or not a string") @@ -196,7 +195,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce return false; } - if (hash_found && return_code == 0) + if (m_update_info.hash_found && return_code == 0) { update_log.success("RPCS3 is up to date!"); m_downloader->close_progress_dialog(); @@ -210,59 +209,26 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce // Calculate how old the build is const QString date_fmt = QStringLiteral("yyyy-MM-dd hh:mm:ss"); - const QDateTime cur_date = hash_found ? QDateTime::fromString(current["datetime"].toString(), date_fmt) : QDateTime::currentDateTimeUtc(); + const QDateTime cur_date = m_update_info.hash_found ? QDateTime::fromString(current["datetime"].toString(), date_fmt) : QDateTime::currentDateTimeUtc(); const QDateTime lts_date = QDateTime::fromString(latest["datetime"].toString(), date_fmt); - const QString cur_str = cur_date.toString(date_fmt); - const QString lts_str = lts_date.toString(date_fmt); + m_update_info.update_found = true; + m_update_info.cur_date = cur_date.toString(date_fmt); + m_update_info.lts_date = lts_date.toString(date_fmt); + m_update_info.diff_msec = cur_date.msecsTo(lts_date); + m_update_info.new_version = latest["version"].toString(); - const qint64 diff_msec = cur_date.msecsTo(lts_date); - - update_log.notice("Current: %s, latest: %s, difference: %lld ms", cur_str, lts_str, diff_msec); - - const Localized localized; - - const QString new_version = latest["version"].toString(); - m_new_version = new_version.toStdString(); - const QString support_message = tr("
You can empower our project at RPCS3 Patreon.
"); - - if (hash_found) + if (m_update_info.hash_found) { - const QString old_version = current["version"].toString(); - m_old_version = old_version.toStdString(); - - if (diff_msec < 0) - { - // This usually means that the current version was marked as broken and won't be shipped anymore, so we need to downgrade to avoid certain bugs. - m_update_message = tr("A better version of RPCS3 is available!

Current version: %0 (%1)
Better version: %2 (%3)
%4
Do you want to update?") - .arg(old_version) - .arg(cur_str) - .arg(new_version) - .arg(lts_str) - .arg(support_message); - } - else - { - m_update_message = tr("A new version of RPCS3 is available!

Current version: %0 (%1)
Latest version: %2 (%3)
Your version is %4 behind.
%5
Do you want to update?") - .arg(old_version) - .arg(cur_str) - .arg(new_version) - .arg(lts_str) - .arg(localized.GetVerboseTimeByMs(diff_msec, true)) - .arg(support_message); - } + m_update_info.old_version = current["version"].toString(); } else { - m_old_version = fmt::format("%s-%s-%s", rpcs3::get_full_branch(), rpcs3::get_branch(), rpcs3::get_version().to_string()); - - m_update_message = tr("You're currently using a custom or PR build.

Latest version: %0 (%1)
The latest version is %2 old.
%3
Do you want to update to the latest official RPCS3 version?") - .arg(new_version) - .arg(lts_str) - .arg(localized.GetVerboseTimeByMs(std::abs(diff_msec), true)) - .arg(support_message); + m_update_info.old_version = QString::fromStdString(fmt::format("%s-%s-%s", rpcs3::get_full_branch(), rpcs3::get_branch(), rpcs3::get_version().to_string())); } + update_log.notice("Current: %s, latest: %s, difference: %lld ms", m_update_info.cur_date, m_update_info.lts_date, m_update_info.diff_msec); + m_request_url = latest[os]["download"].toString().toStdString(); m_expected_hash = latest[os]["checksum"].toString().toStdString(); m_expected_size = latest[os]["size"].toInt(); @@ -277,9 +243,9 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce if (!auto_accept) { - if (automatic && m_gui_settings->GetValue(gui::ib_skip_version).toString() == new_version) + if (automatic && m_gui_settings->GetValue(gui::ib_skip_version).toString() == m_update_info.new_version) { - update_log.notice("Skipping automatic update notification for version '%s' due to user preference", new_version); + update_log.notice("Skipping automatic update notification for version '%s' due to user preference", m_update_info.new_version); m_downloader->close_progress_dialog(); return true; } @@ -300,7 +266,6 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce } else { - entry.version = tr("N/A"); update_log.notice("JSON changelog entry does not contain a version string."); } @@ -310,11 +275,10 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce } else { - entry.title = tr("N/A"); update_log.notice("JSON changelog entry does not contain a title string."); } - m_changelog.push_back(entry); + m_update_info.changelog.push_back(std::move(entry)); } else { @@ -351,25 +315,61 @@ void update_manager::update(bool auto_accept) if (!auto_accept) { - if (m_update_message.isEmpty()) + if (!m_update_info.update_found) { // This can happen if we abort the check_for_updates download. Just check again in this case. - update_log.notice("Aborting update: Update message is empty. Trying again..."); + update_log.notice("Aborting update: Update not found. Trying again..."); m_downloader->close_progress_dialog(); check_for_updates(false, false, false, m_parent); return; } + const Localized localized; + const QString support_message = tr("
You can empower our project at RPCS3 Patreon.
"); + QString update_message; + + if (m_update_info.hash_found) + { + if (m_update_info.diff_msec < 0) + { + // This usually means that the current version was marked as broken and won't be shipped anymore, so we need to downgrade to avoid certain bugs. + update_message = tr("A better version of RPCS3 is available!

Current version: %0 (%1)
Better version: %2 (%3)
%4
Do you want to update?") + .arg(m_update_info.old_version) + .arg(m_update_info.cur_date) + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(support_message); + } + else + { + update_message = tr("A new version of RPCS3 is available!

Current version: %0 (%1)
Latest version: %2 (%3)
Your version is %4 behind.
%5
Do you want to update?") + .arg(m_update_info.old_version) + .arg(m_update_info.cur_date) + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(localized.GetVerboseTimeByMs(m_update_info.diff_msec, true)) + .arg(support_message); + } + } + else + { + update_message = tr("You're currently using a custom or PR build.

Latest version: %0 (%1)
The latest version is %2 old.
%3
Do you want to update to the latest official RPCS3 version?") + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(localized.GetVerboseTimeByMs(std::abs(m_update_info.diff_msec), true)) + .arg(support_message); + } + QString changelog_content; - for (const changelog_data& entry : m_changelog) + for (const changelog_data& entry : m_update_info.changelog) { if (!changelog_content.isEmpty()) changelog_content.append('\n'); - changelog_content.append(tr("• %0: %1").arg(entry.version, entry.title)); + changelog_content.append(tr("• %0: %1").arg(entry.version.isEmpty() ? tr("N/A") : entry.version, entry.title.isEmpty() ? tr("N/A") : entry.title)); } - QMessageBox mb(QMessageBox::Icon::Question, tr("Update Available"), m_update_message, QMessageBox::Yes | QMessageBox::No, m_downloader->get_progress_dialog() ? m_downloader->get_progress_dialog() : m_parent); + QMessageBox mb(QMessageBox::Icon::Question, tr("Update Available"), update_message, QMessageBox::Yes | QMessageBox::No, m_downloader->get_progress_dialog() ? m_downloader->get_progress_dialog() : m_parent); mb.setTextFormat(Qt::RichText); mb.setCheckBox(new QCheckBox(tr("Don't show again for this version"))); @@ -380,16 +380,16 @@ void update_manager::update(bool auto_accept) // Smartass hack to make the unresizeable message box wide enough for the changelog const int changelog_width = QLabel(changelog_content).sizeHint().width(); - if (QLabel(m_update_message).sizeHint().width() < changelog_width) + if (QLabel(update_message).sizeHint().width() < changelog_width) { - m_update_message += "  "; - while (QLabel(m_update_message).sizeHint().width() < changelog_width) + update_message += "  "; + while (QLabel(update_message).sizeHint().width() < changelog_width) { - m_update_message += " "; + update_message += " "; } } - mb.setText(m_update_message); + mb.setText(update_message); } update_log.notice("Asking user for permission to update..."); @@ -400,8 +400,8 @@ void update_manager::update(bool auto_accept) if (mb.checkBox()->isChecked()) { - update_log.notice("User requested to skip further automatic update notifications for version '%s'", m_new_version); - m_gui_settings->SetValue(gui::ib_skip_version, QString::fromStdString(m_new_version)); + update_log.notice("User requested to skip further automatic update notifications for version '%s'", m_update_info.new_version); + m_gui_settings->SetValue(gui::ib_skip_version, m_update_info.new_version); } m_downloader->close_progress_dialog(); @@ -751,7 +751,7 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept) if (fs::file update_file{fs::get_config_dir() + "update_history.log", fs::create + fs::write + fs::append}) { const std::string update_time = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss").toStdString(); - const std::string entry = fmt::format("%s: Updated from \"%s\" to \"%s\"", update_time, m_old_version, m_new_version); + const std::string entry = fmt::format("%s: Updated from \"%s\" to \"%s\"", update_time, m_update_info.old_version, m_update_info.new_version); update_file.write(fmt::format("%s\n", entry)); update_log.notice("Added entry '%s' to update_history.log", entry); } diff --git a/rpcs3/rpcs3qt/update_manager.h b/rpcs3/rpcs3qt/update_manager.h index f6a534b0b2..8e7d9e8eb0 100644 --- a/rpcs3/rpcs3qt/update_manager.h +++ b/rpcs3/rpcs3qt/update_manager.h @@ -19,20 +19,28 @@ private: std::shared_ptr m_gui_settings; - // This message is empty if there is no download available - QString m_update_message; - struct changelog_data { QString version; QString title; }; - std::vector m_changelog; + + struct update_info + { + bool update_found = false; + bool hash_found = false; + qint64 diff_msec = 0; + QString cur_date; + QString lts_date; + QString old_version; + QString new_version; + std::vector changelog; + }; + + update_info m_update_info {}; std::string m_request_url; std::string m_expected_hash; - std::string m_old_version; - std::string m_new_version; u64 m_expected_size = 0; bool handle_json(bool automatic, bool check_only, bool auto_accept, const QByteArray& data);