Qt: Make main toolbar editable

This commit is contained in:
Stenzek 2025-01-14 14:34:09 +10:00
parent 6a6d36267d
commit 1aa1b5a7ec
No known key found for this signature in database
4 changed files with 180 additions and 23 deletions

View File

@ -1,9 +1,11 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "types.h"
#include <algorithm>
#include <array>
#include <charconv>
#include <cstddef>
@ -320,6 +322,45 @@ void StripWhitespace(std::string* str);
[[nodiscard]] std::vector<std::string> SplitNewString(const std::string_view str, char delimiter,
bool skip_empty = true);
/// Returns true if the given string is found in the string list container.
template<typename T>
static inline bool IsInStringList(const T& list, const std::string_view str)
{
return std::any_of(std::begin(list), std::end(list), [&str](const auto& it) { return (str == it); });
}
/// Adds a string to a string list container. No append is performed if the string already exists.
template<typename T>
static inline bool AddToStringList(T& list, const std::string_view str)
{
if (IsInStringList(list, str))
return false;
list.emplace_back(str);
return true;
}
/// Removes a string from a string list container.
template<typename T>
static inline bool RemoveFromStringList(T& list, const std::string_view str)
{
bool removed = false;
for (auto iter = std::begin(list); iter != std::end(list);)
{
if (str == *iter)
{
iter = list.erase(iter);
removed = true;
}
else
{
++iter;
}
}
return removed;
}
/// Joins a string together using the specified delimiter.
template<typename T>
static inline std::string JoinString(const T& start, const T& end, char delimiter)
@ -334,6 +375,11 @@ static inline std::string JoinString(const T& start, const T& end, char delimite
return ret;
}
template<typename T>
static inline std::string JoinString(const T& list, char delimiter)
{
return JoinString(std::begin(list), std::end(list), delimiter);
}
template<typename T>
static inline std::string JoinString(const T& start, const T& end, const std::string_view delimiter)
{
std::string ret;
@ -345,6 +391,11 @@ static inline std::string JoinString(const T& start, const T& end, const std::st
}
return ret;
}
template<typename T>
static inline std::string JoinString(const T& list, const std::string_view delimiter)
{
return JoinString(std::begin(list), std::end(list), delimiter);
}
/// Replaces all instances of search in subject with replacement.
[[nodiscard]] std::string ReplaceAll(const std::string_view subject, const std::string_view search,

View File

@ -38,6 +38,7 @@
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/string_util.h"
#include <QtCore/QDebug>
#include <QtCore/QFile>
@ -63,6 +64,33 @@
LOG_CHANNEL(Host);
static constexpr std::pair<const char*, QAction * Ui::MainWindow::*> s_toolbar_actions[] = {
{"StartFile", &Ui::MainWindow::actionStartFile},
{"StartBIOS", &Ui::MainWindow::actionStartBios},
{"StartDisc", &Ui::MainWindow::actionStartDisc},
{"FullscreenUI", &Ui::MainWindow::actionStartFullscreenUI2},
{nullptr, nullptr},
{"PowerOff", &Ui::MainWindow::actionPowerOff},
{"PowerOffWithoutSaving", &Ui::MainWindow::actionPowerOffWithoutSaving},
{"Reset", &Ui::MainWindow::actionReset},
{"Pause", &Ui::MainWindow::actionPause},
{"ChangeDisc", &Ui::MainWindow::actionChangeDisc},
{"Cheats", &Ui::MainWindow::actionCheatsToolbar},
{"Screenshot", &Ui::MainWindow::actionScreenshot},
{nullptr, nullptr},
{"LoadState", &Ui::MainWindow::actionLoadState},
{"SaveState", &Ui::MainWindow::actionSaveState},
{nullptr, nullptr},
{"Fullscreen", &Ui::MainWindow::actionFullscreen},
{"Settings", &Ui::MainWindow::actionSettings2},
{"ControllerSettings", &Ui::MainWindow::actionControllerSettings},
{"ControllerPresets", &Ui::MainWindow::actionControllerProfiles},
};
static constexpr const char* DEFAULT_TOOLBAR_ACTIONS =
"StartFile,StartBIOS,FullscreenUI,PowerOff,Reset,Pause,ChangeDisc,Cheats,Screenshot,LoadState,SaveState,"
"Fullscreen,Settings,ControllerSettings";
static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
"MainWindow",
"All File Types (*.bin *.img *.iso *.cue *.chd *.cpe *.ecm *.mds *.pbp *.elf *.exe *.psexe *.ps-exe *.psx *.psf "
@ -150,6 +178,7 @@ void MainWindow::initialize()
{
m_ui.setupUi(this);
setupAdditionalUi();
updateToolbarActions();
connectSignals();
restoreStateFromConfig();
@ -166,6 +195,11 @@ void MainWindow::initialize()
#endif
}
QMenu* MainWindow::createPopupMenu()
{
return nullptr;
}
void MainWindow::reportError(const QString& title, const QString& message)
{
QMessageBox::critical(this, title, message, QMessageBox::Ok);
@ -1636,7 +1670,6 @@ void MainWindow::setupAdditionalUi()
const bool toolbars_locked = Host::GetBaseBoolSettingValue("UI", "LockToolbar", false);
m_ui.actionViewLockToolbar->setChecked(toolbars_locked);
m_ui.toolBar->setMovable(!toolbars_locked);
m_ui.toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
m_game_list_widget = new GameListWidget(getContentParent());
m_game_list_widget->initialize();
@ -1735,6 +1768,86 @@ void MainWindow::setupAdditionalUi()
#endif
}
void MainWindow::updateToolbarActions()
{
const std::string active_buttons_str =
Host::GetBaseStringSettingValue("UI", "ToolbarButtons", DEFAULT_TOOLBAR_ACTIONS);
const std::vector<std::string_view> active_buttons = StringUtil::SplitString(active_buttons_str, ',');
m_ui.toolBar->clear();
bool any_items_before_separator = false;
for (const auto& [name, action_ptr] : s_toolbar_actions)
{
if (!name)
{
// separator, but don't insert empty space between them
if (any_items_before_separator)
{
any_items_before_separator = false;
m_ui.toolBar->addSeparator();
}
continue;
}
// enabled?
if (!StringUtil::IsInStringList(active_buttons, name))
continue;
// only one of resume/poweroff should be present depending on system state
QAction* action = (m_ui.*action_ptr);
if (action == m_ui.actionPowerOff && !s_system_valid)
action = m_ui.actionResumeLastState;
m_ui.toolBar->addAction(action);
any_items_before_separator = true;
}
}
void MainWindow::onToolbarContextMenuRequested(const QPoint& pos)
{
{
const std::string active_buttons_str =
Host::GetBaseStringSettingValue("UI", "ToolbarButtons", DEFAULT_TOOLBAR_ACTIONS);
std::vector<std::string_view> active_buttons = StringUtil::SplitString(active_buttons_str, ',');
bool active_buttons_changed = false;
QMenu menu;
for (const auto& [name, action_ptr] : s_toolbar_actions)
{
if (!name)
{
menu.addSeparator();
continue;
}
QAction* action = (m_ui.*action_ptr);
QAction* menu_action = menu.addAction(action->text());
menu_action->setCheckable(true);
menu_action->setChecked(StringUtil::IsInStringList(active_buttons, name));
connect(menu_action, &QAction::toggled, this, [&active_buttons, &active_buttons_changed, name](bool checked) {
if (checked)
StringUtil::AddToStringList(active_buttons, name);
else
StringUtil::RemoveFromStringList(active_buttons, name);
active_buttons_changed = true;
});
}
menu.exec(m_ui.toolBar->mapToGlobal(pos));
if (!active_buttons_changed)
return;
Host::SetBaseStringSettingValue("UI", "ToolbarButtons", StringUtil::JoinString(active_buttons, ',').c_str());
Host::CommitBaseSettingChanges();
}
updateToolbarActions();
}
void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode)
{
const bool starting_or_running = (starting || running);
@ -1774,7 +1887,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
if (starting_or_running)
{
if (!m_ui.toolBar->actions().contains(m_ui.actionPowerOff))
if (m_ui.toolBar->widgetForAction(m_ui.actionResumeLastState))
{
m_ui.toolBar->insertAction(m_ui.actionResumeLastState, m_ui.actionPowerOff);
m_ui.toolBar->removeAction(m_ui.actionResumeLastState);
@ -1782,7 +1895,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running, bool cheevo
}
else
{
if (!m_ui.toolBar->actions().contains(m_ui.actionResumeLastState))
if (m_ui.toolBar->widgetForAction(m_ui.actionPowerOff))
{
m_ui.toolBar->insertAction(m_ui.actionPowerOff, m_ui.actionResumeLastState);
m_ui.toolBar->removeAction(m_ui.actionPowerOff);
@ -1984,6 +2097,7 @@ void MainWindow::connectSignals()
updateEmulationActions(false, false, Achievements::IsHardcoreModeActive());
connect(qApp, &QGuiApplication::applicationStateChanged, this, &MainWindow::onApplicationStateChanged);
connect(m_ui.toolBar, &QToolBar::customContextMenuRequested, this, &MainWindow::onToolbarContextMenuRequested);
connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered);
connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered);

View File

@ -80,6 +80,9 @@ public:
explicit MainWindow();
~MainWindow();
/// Disable createPopupMenu(), the menu is bogus.
QMenu* createPopupMenu() override;
/// Performs update check if enabled in settings.
void startupUpdateCheck();
@ -155,6 +158,8 @@ private Q_SLOTS:
void onApplicationStateChanged(Qt::ApplicationState state);
void onToolbarContextMenuRequested(const QPoint& pos);
void onStartFileActionTriggered();
void onStartDiscActionTriggered();
void onStartBIOSActionTriggered();
@ -226,6 +231,7 @@ private:
void setupAdditionalUi();
void connectSignals();
void updateToolbarActions();
void updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode);
void updateShortcutActions(bool starting);
void updateStatusBarWidgetVisibility();

View File

@ -248,8 +248,8 @@
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="iconSize">
<size>
@ -260,29 +260,15 @@
<property name="toolButtonStyle">
<enum>Qt::ToolButtonStyle::ToolButtonTextUnderIcon</enum>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionStartFile"/>
<addaction name="actionStartBios"/>
<addaction name="actionStartFullscreenUI2"/>
<addaction name="separator"/>
<addaction name="actionResumeLastState"/>
<addaction name="actionReset"/>
<addaction name="actionPause"/>
<addaction name="actionChangeDisc"/>
<addaction name="actionCheatsToolbar"/>
<addaction name="actionScreenshot"/>
<addaction name="separator"/>
<addaction name="actionLoadState"/>
<addaction name="actionSaveState"/>
<addaction name="separator"/>
<addaction name="actionFullscreen"/>
<addaction name="actionSettings2"/>
<addaction name="actionControllerSettings"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionStartFile">