From b0b83b2a1627e0a1537d6c6cc7e1ba3ab6ff81db Mon Sep 17 00:00:00 2001 From: Rob Caelers Date: Sun, 9 Feb 2025 20:28:08 +0100 Subject: [PATCH] Qt improvements --- CMakeLists.txt | 6 +- libs/utils/src/Paths.cc | 18 +++- ui/app/Application.cc | 2 + ui/app/GenericDBusApplet.cc | 1 - .../platforms/windows/WindowsHarpoonLocker.cc | 5 +- ui/app/toolkits/qt/AutoUpdater.cc | 3 +- ui/app/toolkits/qt/BreakWindow.cc | 22 +++-- ui/app/toolkits/qt/BreakWindow.hh | 5 +- ui/app/toolkits/qt/CMakeLists.txt | 23 ++++- ui/app/toolkits/qt/DebugDialog.cc | 3 +- ui/app/toolkits/qt/DebugDialog.hh | 7 +- ui/app/toolkits/qt/PreferencesDialog.cc | 88 +++++++++-------- ui/app/toolkits/qt/PreferencesDialog.hh | 12 +-- ui/app/toolkits/qt/PreludeWindow.cc | 27 ++++++ ui/app/toolkits/qt/PreludeWindow.hh | 7 +- ui/app/toolkits/qt/StatisticsDialog.cc | 4 +- ui/app/toolkits/qt/Toolkit.cc | 22 +++++ ui/app/toolkits/qt/Toolkit.hh | 6 ++ ui/app/toolkits/qt/ToolkitMenu.cc | 3 +- ui/app/toolkits/qt/ToolkitWindows.cc | 6 +- ui/app/toolkits/qt/ToolkitWindows.hh | 2 + .../preferences/GeneralUiPreferencesPanel.cc | 69 +++++++++++++ .../preferences/GeneralUiPreferencesPanel.hh | 8 ++ .../preferences/MonitoringPreferencesPanel.cc | 97 +++++++++++++++++++ .../preferences/MonitoringPreferencesPanel.hh | 63 ++++++++++++ .../preferences/TimerBoxPreferencesPanel.cc | 36 ++++++- .../preferences/TimerBoxPreferencesPanel.hh | 10 +- .../qt/preferences/TimerPreferencesPanel.cc | 10 ++ .../qt/preferences/TimerPreferencesPanel.hh | 1 + ui/app/toolkits/qt/widgets/Frame.hh | 1 + .../toolkits/qt/widgets/IconListNotebook.cc | 19 +++- .../toolkits/qt/widgets/IconListNotebook.hh | 1 + 32 files changed, 507 insertions(+), 80 deletions(-) create mode 100644 ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.cc create mode 100644 ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index d6be02383..821ca1486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -805,7 +805,11 @@ if (WIN32) set (PLATFORM_OS_WINDOWS_NATIVE 1) endif() - set (HAVE_HARPOON ON) + if (WITH_UI_QT) + set (HAVE_HARPOON OFF) + else() + set (HAVE_HARPOON ON) + endif() if (NOT MSVC) find_program(MSYS2_SHELL diff --git a/libs/utils/src/Paths.cc b/libs/utils/src/Paths.cc index 42150662e..bb49bdc04 100644 --- a/libs/utils/src/Paths.cc +++ b/libs/utils/src/Paths.cc @@ -49,6 +49,14 @@ using namespace workrave::utils; namespace { + using namespace std::string_view_literals; + +#ifdef HAVE_APP_QT + constexpr std::string_view app_name = "WorkraveQt"sv; +#else + constexpr std::string_view app_name = "Workrave"sv; +#endif + std::filesystem::path portable_directory; #if defined(PLATFORM_OS_WINDOWS) @@ -230,7 +238,7 @@ Paths::get_config_directories() try { #if defined(PLATFORM_OS_WINDOWS) - directories.push_back(get_home_directory() / "Workrave"); + directories.push_back(get_home_directory() / app_name); directories.push_back(get_application_directory() / "etc"); #endif @@ -266,7 +274,7 @@ Paths::get_state_directories() { #if defined(PLATFORM_OS_WINDOWS) directories.push_back(get_application_directory() / "etc"); - directories.push_back(get_home_directory() / "Workrave"); + directories.push_back(get_home_directory() / app_name); #endif #if defined(HAVE_GLIB) @@ -378,7 +386,7 @@ Paths::get_state_directory() { TRACE_MSG("Using preferred directory"); #if defined(PLATFORM_OS_WINDOWS) - ret = get_home_directory() / "Workrave"; + ret = get_home_directory() / app_name; #elif defined(HAVE_GLIB) # if GLIB_CHECK_VERSION(2, 72, 0) const gchar *user_state_dir = g_get_user_state_dir(); @@ -469,7 +477,7 @@ Paths::get_log_directory() std::filesystem::path dir = get_home_directory(); if (!dir.empty()) { - dir /= std::filesystem::path("Library") / "Logs" / "Workrave"; + dir /= std::filesystem::path("Library") / "Logs" / app_name; return dir; } @@ -484,7 +492,7 @@ Paths::get_log_directory() std::filesystem::path dir = get_special_folder(FOLDERID_LocalAppData); if (!dir.empty()) { - dir /= std::filesystem::path("Workrave") / "Logs"; + dir /= std::filesystem::path(app_name) / "Logs"; return dir; } diff --git a/ui/app/Application.cc b/ui/app/Application.cc index dd7b371fb..cbefbd3ce 100644 --- a/ui/app/Application.cc +++ b/ui/app/Application.cc @@ -284,7 +284,9 @@ Application::init_dbus() // dialog.set_secondary_text(_("Is Workrave already running?")); // dialog.show(); // dialog.run(); +#if !defined(HAVE_APP_QT) exit(1); +#endif } try diff --git a/ui/app/GenericDBusApplet.cc b/ui/app/GenericDBusApplet.cc index a126c60f6..172c1ea5f 100644 --- a/ui/app/GenericDBusApplet.cc +++ b/ui/app/GenericDBusApplet.cc @@ -33,7 +33,6 @@ namespace { - constexpr const char *WORKRAVE_APPLET_SERVICE_NAME = "org.workrave.Workrave"; constexpr const char *WORKRAVE_APPLET_SERVICE_IFACE = "org.workrave.AppletInterface"; constexpr const char *WORKRAVE_APPLET_SERVICE_OBJ = "/org/workrave/Workrave/UI"; } // namespace diff --git a/ui/app/platforms/windows/WindowsHarpoonLocker.cc b/ui/app/platforms/windows/WindowsHarpoonLocker.cc index f490177e3..bdaf01dea 100644 --- a/ui/app/platforms/windows/WindowsHarpoonLocker.cc +++ b/ui/app/platforms/windows/WindowsHarpoonLocker.cc @@ -15,11 +15,12 @@ // along with this program. If not, see . // -#include #ifdef HAVE_CONFIG_H # include "config.h" #endif +#include + #include "ui/windows/WindowsHarpoonLocker.hh" #include "input-monitor/Harpoon.hh" @@ -58,6 +59,7 @@ WindowsHarpoonLocker::prepare_lock() text.resize(GetWindowTextLengthA(active_window)); GetWindowTextA(active_window, text.data(), text.size() + 1); TRACE_MSG("Save active window: {}", text); + spdlog::info("Save active window: {} {}", text, reinterpret_cast(active_window)); } void @@ -78,6 +80,7 @@ WindowsHarpoonLocker::unlock() text.resize(GetWindowTextLengthA(active_window)); GetWindowTextA(active_window, text.data(), text.size() + 1); TRACE_MSG("Restore active window: {}", text); + spdlog::info("Restore active window: {} {}", text, reinterpret_cast(active_window)); SetForegroundWindow(active_window); active_window = nullptr; } diff --git a/ui/app/toolkits/qt/AutoUpdater.cc b/ui/app/toolkits/qt/AutoUpdater.cc index 2f9c31ff1..f2f847019 100644 --- a/ui/app/toolkits/qt/AutoUpdater.cc +++ b/ui/app/toolkits/qt/AutoUpdater.cc @@ -218,7 +218,8 @@ AutoUpdater::init_preferences() ->when(&workrave::updater::Config::proxy_type(), [](unfold::ProxyType t) { return t == unfold::ProxyType::Custom; })); - context->get_preferences_registry()->add_page("auto-update", N_("Software updates"), "workrave-update-symbolic"); + // TODO: image is toolkit specific + context->get_preferences_registry()->add_page("auto-update", N_("Software updates"), "update.svg"); context->get_preferences_registry()->add(auto_update_def); } diff --git a/ui/app/toolkits/qt/BreakWindow.cc b/ui/app/toolkits/qt/BreakWindow.cc index 687a2c2a9..ef6a67e8a 100644 --- a/ui/app/toolkits/qt/BreakWindow.cc +++ b/ui/app/toolkits/qt/BreakWindow.cc @@ -52,6 +52,18 @@ BreakWindow::BreakWindow(std::shared_ptr app, QScreen *scre void BreakWindow::init() { +#if defined(HAVE_WAYLAND) + if (Platform::running_on_wayland()) + { + auto wm = std::make_shared(); + bool success = wm->init(); + if (success) + { + window_manager = wm; + } + } +#endif + if (block_mode != BlockMode::Off) { setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::SplashScreen); @@ -306,19 +318,18 @@ BreakWindow::check_skip_postpone_lock(bool &skip_locked, bool &postpone_locked, { for (int id = break_id - 1; id >= 0; id--) { - IBreak::Ptr b = core->get_break(BreakId(id)); - + auto b = core->get_break(BreakId(id)); bool overdue = b->get_elapsed_time() > b->get_limit(); if (((break_flags & BREAK_FLAGS_USER_INITIATED) == 0) || b->is_max_preludes_reached()) { if (!GUIConfig::break_ignorable(BreakId(id))()) { - skip_locked = overdue; + postpone_locked = overdue; } if (!GUIConfig::break_skippable(BreakId(id))()) { - postpone_locked = overdue; + skip_locked = overdue; } if (skip_locked || postpone_locked) { @@ -496,9 +507,6 @@ BreakWindow::stop() } hide(); - // TODO: - // platform->restore_foreground(); - // platform->unlock(); } void diff --git a/ui/app/toolkits/qt/BreakWindow.hh b/ui/app/toolkits/qt/BreakWindow.hh index 8bb147bb9..c48a28a65 100644 --- a/ui/app/toolkits/qt/BreakWindow.hh +++ b/ui/app/toolkits/qt/BreakWindow.hh @@ -80,7 +80,6 @@ private: void on_postpone_button_clicked(); void on_sysoper_combobox_changed(int index); - std::vector supported_system_operations; void append_row_to_sysoper_model(System::SystemOperation::SystemOperationType type); void get_operation_name_and_icon(System::SystemOperation::SystemOperationType type, QString &name, QString &icon_name); @@ -93,12 +92,16 @@ private: QScreen *screen{nullptr}; Frame *frame{nullptr}; QWidget *gui{nullptr}; + std::vector supported_system_operations; QWidget *block_window{nullptr}; QProgressBar *progress_bar{nullptr}; QPushButton *postpone_button{nullptr}; QPushButton *skip_button{nullptr}; QComboBox *sysoper_combo{nullptr}; std::shared_ptr size_group; +#if defined(HAVE_WAYLAND) + std::shared_ptr window_manager; +#endif }; #endif // BREAKWINDOW_HH diff --git a/ui/app/toolkits/qt/CMakeLists.txt b/ui/app/toolkits/qt/CMakeLists.txt index 89ec5f376..3a7ed7802 100644 --- a/ui/app/toolkits/qt/CMakeLists.txt +++ b/ui/app/toolkits/qt/CMakeLists.txt @@ -17,6 +17,7 @@ target_sources(workrave-toolkit-qt PRIVATE ToolkitFactory.cc ToolkitMenu.cc preferences/GeneralUiPreferencesPanel.cc + preferences/MonitoringPreferencesPanel.cc preferences/SoundsPreferencesPanel.cc preferences/TimerBoxPreferencesPanel.cc preferences/TimerPreferencesPanel.cc @@ -36,7 +37,27 @@ target_sources(workrave-toolkit-qt PRIVATE if (PLATFORM_OS_UNIX) target_sources(workrave-toolkit-qt PRIVATE ToolkitLinux.cc) - target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix) + if (HAVE_WAYLAND) + qt_generate_wayland_protocol_client_sources(workrave-toolkit-qt FILES + platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml + platforms/unix/protocols/xdg-shell.xml) + + target_sources(workrave-toolkit-qt PRIVATE + platforms/unix/WaylandWindowManager.cc + ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1.c + ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c) + endif() + + if (HAVE_DBUSMENU) + target_sources(workrave-toolkit-qt PRIVATE platforms/unix/DbusMenu.cc) + endif() + + if (HAVE_APPINDICATOR) + target_sources(workrave-toolkit-qt PRIVATE platforms/unix/AppIndicatorMenu.cc) + endif() + + target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols) + target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/platforms/unix) endif() if (PLATFORM_OS_WINDOWS) diff --git a/ui/app/toolkits/qt/DebugDialog.cc b/ui/app/toolkits/qt/DebugDialog.cc index 72737bee4..e3a5c6fda 100644 --- a/ui/app/toolkits/qt/DebugDialog.cc +++ b/ui/app/toolkits/qt/DebugDialog.cc @@ -34,7 +34,8 @@ using namespace workrave; using namespace workrave::utils; -DebugDialog::DebugDialog() +DebugDialog::DebugDialog(std::shared_ptr app) + : app(app) { setWindowTitle(tr("Debug Workrave")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); diff --git a/ui/app/toolkits/qt/DebugDialog.hh b/ui/app/toolkits/qt/DebugDialog.hh index 0557c8f36..23875a4f9 100644 --- a/ui/app/toolkits/qt/DebugDialog.hh +++ b/ui/app/toolkits/qt/DebugDialog.hh @@ -21,12 +21,17 @@ #include #include +#include "ui/IApplicationContext.hh" + class DebugDialog : public QDialog { Q_OBJECT public: - DebugDialog(); + explicit DebugDialog(std::shared_ptr app); + +private: + std::shared_ptr app; }; #endif // DEBUGDIALOG_HH diff --git a/ui/app/toolkits/qt/PreferencesDialog.cc b/ui/app/toolkits/qt/PreferencesDialog.cc index 7c5862629..8a27db8bc 100644 --- a/ui/app/toolkits/qt/PreferencesDialog.cc +++ b/ui/app/toolkits/qt/PreferencesDialog.cc @@ -28,6 +28,7 @@ #include "IconListNotebook.hh" #include "GeneralUiPreferencesPanel.hh" +#include "MonitoringPreferencesPanel.hh" #include "SoundsPreferencesPanel.hh" #include "TimerBoxPreferencesPanel.hh" #include "TimerPreferencesPanel.hh" @@ -35,14 +36,19 @@ #include "Ui.hh" #include "Icon.hh" +#include "core/CoreTypes.hh" +#include "ui/GUIConfig.hh" #include "ui/prefwidgets/qt/Builder.hh" #include "utils/AssetPath.hh" +#include "utils/Enum.hh" +#include "utils/EnumIterator.hh" using namespace workrave; using namespace workrave::utils; -PreferencesDialog::PreferencesDialog(std::shared_ptr app) - : app(app) +PreferencesDialog::PreferencesDialog(std::shared_ptr app, QWidget *parent) + : QDialog(parent) + , app(app) { TRACE_ENTRY(); @@ -72,33 +78,32 @@ PreferencesDialog::init_ui() auto *layout = new QVBoxLayout(); setLayout(layout); - auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); - QPushButton *close_button = buttonBox->button(QDialogButtonBox::Close); + auto *button_box = new QDialogButtonBox(QDialogButtonBox::Close); + QPushButton *close_button = button_box->button(QDialogButtonBox::Close); close_button->setAutoDefault(true); close_button->setDefault(true); notebook = new IconListNotebook(); layout->addWidget(notebook); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(accept())); - layout->addWidget(buttonBox); + connect(button_box, SIGNAL(rejected()), this, SLOT(accept())); + layout->addWidget(button_box); } void PreferencesDialog::create_timers_page() { - std::array timer_ids = {"microbreak", "restbreak", "dailylimit"}; auto page = add_page("timer", tr("Timers"), "timer.svg"); auto hsize_group = std::make_shared(Qt::Horizontal); auto vsize_group = std::make_shared(Qt::Horizontal); - for (int i = 0; i < BREAK_ID_SIZEOF; i++) + for (auto id: workrave::utils::enum_range()) { - auto *panel = new TimerPreferencesPanel(app, BreakId(i), hsize_group, vsize_group); - QPixmap pixmap(Ui::get_break_icon_filename(BreakId(i))); + auto *panel = new TimerPreferencesPanel(app, id, hsize_group, vsize_group); + QPixmap pixmap(Ui::get_break_icon_filename(id)); QIcon icon(pixmap); - page->add_panel(timer_ids.at(i), panel, Ui::get_break_name(BreakId(i)), icon); + page->add_panel(std::string(workrave::utils::enum_to_string(id)), panel, Ui::get_break_name(id), icon); } } @@ -121,14 +126,16 @@ void PreferencesDialog::create_monitoring_page() { auto page = add_page("monitoring", tr("Monitoring"), "mouse.svg"); - // QWidget *monitoring_panel = new MonitoringPreferencePanel(app); - // page->add_panel("monitoring", monitoring_panel, _("Monitoring")); + + QWidget *monitoring_panel = new MonitoringPreferencesPanel(app); + page->add_panel("monitoring", monitoring_panel, tr("Monitoring")); } void PreferencesDialog::create_sounds_page() { auto page = add_page("sounds", tr("Sounds"), "sound.svg"); + QWidget *gui_sounds_page = new SoundsPreferencesPanel(app); page->add_panel("sounds", gui_sounds_page, tr("Sounds")); } @@ -140,6 +147,7 @@ PreferencesDialog::create_plugin_pages() for (auto &[id, def]: preferences_registry->get_pages()) { auto &[label, image] = def; + spdlog::info("Adding page {} with label {} and image {}", id, label, image); auto page = add_page(id, QString::fromStdString(label), image); } } @@ -157,6 +165,7 @@ PreferencesDialog::create_panel(std::shared_ptr &def) spdlog::error("Cannot find page {} when adding panel {}", pageid, panelid); return; } + auto page = pageit->second; if (auto paneldef = std::dynamic_pointer_cast(def); paneldef) { @@ -169,6 +178,7 @@ PreferencesDialog::create_panel(std::shared_ptr &def) auto frame = std::make_shared(box); builder.build(widget, frame); page->add_panel(panelid, boxwidget, QString::fromStdString(paneldef->get_label())); + frames.push_back(frame); } } @@ -179,7 +189,7 @@ PreferencesDialog::create_plugin_panels() for (auto &[id, deflist]: preferences_registry->get_widgets()) { - for (std::shared_ptr def: deflist) + for (auto def: deflist) { create_panel(def); } @@ -204,31 +214,31 @@ PreferencesDialog::add_page(const std::string &id, const QString &label, const s return page_info; } -// bool -// PreferencesDialog::on_focus_in_event(GdkEventFocus *event) -// { -// TRACE_ENTRY(); -// BlockMode block_mode = GUIConfig::block_mode()(); -// if (block_mode != BlockMode::Off) -// { -// auto core = app->get_core(); -// OperationMode mode = core->get_active_operation_mode(); -// if (mode == OperationMode::Normal) -// { -// core->set_operation_mode_override(OperationMode::Quiet, "preferences"); -// } -// } -// return Gtk::Dialog::on_focus_in_event(event); -// } - -// bool -// PreferencesDialog::on_focus_out_event(GdkEventFocus *event) -// { -// TRACE_ENTRY(); -// auto core = app->get_core(); -// core->remove_operation_mode_override("preferences"); -// return Gtk::Dialog::on_focus_out_event(event); -// } +bool +PreferencesDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::FocusIn) + { + TRACE_ENTRY(); + BlockMode block_mode = GUIConfig::block_mode()(); + if (block_mode != BlockMode::Off) + { + auto core = app->get_core(); + OperationMode mode = core->get_active_operation_mode(); + if (mode == OperationMode::Normal) + { + core->set_operation_mode_override(OperationMode::Quiet, "preferences"); + } + } + } + else if (event->type() == QEvent::FocusOut) + { + TRACE_ENTRY(); + auto core = app->get_core(); + core->remove_operation_mode_override("preferences"); + } + return QDialog::eventFilter(watched, event); +} PreferencesPage::PreferencesPage(const std::string &id, QTabWidget *notebook) : id(id) diff --git a/ui/app/toolkits/qt/PreferencesDialog.hh b/ui/app/toolkits/qt/PreferencesDialog.hh index f51083bbf..1265e44fa 100644 --- a/ui/app/toolkits/qt/PreferencesDialog.hh +++ b/ui/app/toolkits/qt/PreferencesDialog.hh @@ -27,6 +27,8 @@ #include "IconListNotebook.hh" #include "ui/IApplicationContext.hh" +#include "ui/prefwidgets/qt/BoxWidget.hh" + class PanelList; @@ -50,7 +52,7 @@ class PreferencesDialog : public QDialog Q_OBJECT public: - explicit PreferencesDialog(std::shared_ptr app); + explicit PreferencesDialog(std::shared_ptr app, QWidget *parent = nullptr); ~PreferencesDialog() override; private: @@ -66,15 +68,13 @@ private: void create_plugin_panels(); void create_panel(std::shared_ptr &def); - // bool on_focus_in_event(GdkEventFocus *event) override; - // bool on_focus_out_event(GdkEventFocus *event) override; + bool eventFilter(QObject *watched, QEvent *event) override; private: std::shared_ptr app; std::map> pages; + std::list> frames; IconListNotebook *notebook{nullptr}; - // QStackedWidget *stack{nullptr}; - // QHBoxLayout *layout{nullptr}; -}; +};; #endif // PREFERENCESDIALOG_HH diff --git a/ui/app/toolkits/qt/PreludeWindow.cc b/ui/app/toolkits/qt/PreludeWindow.cc index ae3df0a0d..7359c820a 100644 --- a/ui/app/toolkits/qt/PreludeWindow.cc +++ b/ui/app/toolkits/qt/PreludeWindow.cc @@ -44,6 +44,18 @@ PreludeWindow::PreludeWindow(QScreen *screen, workrave::BreakId break_id) , break_id(break_id) , screen(screen) { +#if defined(HAVE_WAYLAND) + if (Platform::running_on_wayland()) + { + auto wm = std::make_shared(); + bool success = wm->init(); + if (success) + { + window_manager = wm; + } + } +#endif + auto *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(update())); timer->start(1000); @@ -125,6 +137,13 @@ PreludeWindow::PreludeWindow(QScreen *screen, workrave::BreakId break_id) void PreludeWindow::start() { +#if defined(HAVE_WAYLAND) + if (window_manager) + { + window_manager->init_surface(*this, head.get_monitor(), false); + } +#endif + timebar->set_bar_color(TimerColorId::Overdue); refresh(); show(); @@ -141,6 +160,14 @@ void PreludeWindow::stop() { frame->set_frame_flashing(0); + +#if defined(HAVE_WAYLAND) + if (window_manager) + { + window_manager->clear_surfaces(); + } +#endif + hide(); #if defined(PLATFORM_OS_MACOS) diff --git a/ui/app/toolkits/qt/PreludeWindow.hh b/ui/app/toolkits/qt/PreludeWindow.hh index 2bb5c5687..67bd196a1 100644 --- a/ui/app/toolkits/qt/PreludeWindow.hh +++ b/ui/app/toolkits/qt/PreludeWindow.hh @@ -60,8 +60,8 @@ private: workrave::BreakId break_id; QScreen *screen{nullptr}; - int progress_value = 0; - int progress_max_value = 1; + int progress_value{0}; + int progress_max_value{1}; bool flash_visible = false; QString progress_text; @@ -76,6 +76,9 @@ private: #if defined(PLATFORM_OS_MACOS) MouseMonitor::Ptr mouse_monitor; #endif +#if defined(HAVE_WAYLAND) + std::shared_ptr window_manager; +#endif }; #endif // PRELUDEWINDOW_HH diff --git a/ui/app/toolkits/qt/StatisticsDialog.cc b/ui/app/toolkits/qt/StatisticsDialog.cc index af7e3c0cd..1e796ef52 100644 --- a/ui/app/toolkits/qt/StatisticsDialog.cc +++ b/ui/app/toolkits/qt/StatisticsDialog.cc @@ -543,6 +543,7 @@ StatisticsDialog::on_history_goto_first() void StatisticsDialog::on_history_delete_all() { + // TODO: // /* Modal dialogs interrupt GUI input. That can be a problem if for example a break is // triggered while the message boxes are shown. The user would have no way to interact // with the break window without closing out the dialog which may be hidden behind it. @@ -595,7 +596,6 @@ StatisticsDialog::on_history_delete_all() // app->get_core()->remove_operation_mode_override( funcname ); } -//! Periodic heartbeat. auto StatisticsDialog::on_timer() -> bool { @@ -610,8 +610,8 @@ StatisticsDialog::on_timer() -> bool void StatisticsDialog::stream_distance(std::stringstream &stream, int64_t pixels) { + // TODO: // char buf[64]; - // double mm = (double) pixels * gdk_screen_width_mm() / gdk_screen_width(); // sprintf(buf, "%.02f m", mm/1000); // stream << buf; diff --git a/ui/app/toolkits/qt/Toolkit.cc b/ui/app/toolkits/qt/Toolkit.cc index c0a345f05..542416dae 100644 --- a/ui/app/toolkits/qt/Toolkit.cc +++ b/ui/app/toolkits/qt/Toolkit.cc @@ -22,6 +22,7 @@ #include "Toolkit.hh" #include +#include #include "DailyLimitWindow.hh" #include "MicroBreakWindow.hh" @@ -54,6 +55,7 @@ Toolkit::init(std::shared_ptr app) this->app = app; setQuitOnLastWindowClosed(false); + installEventFilter(this); menu_model = app->get_menu_model(); sound_theme = app->get_sound_theme(); @@ -197,6 +199,12 @@ Toolkit::show_about() void Toolkit::show_debug() { + if (debug_dialog == nullptr) + { + debug_dialog = new DebugDialog(app); + debug_dialog->setAttribute(Qt::WA_DeleteOnClose); + } + debug_dialog->show(); } void @@ -296,6 +304,7 @@ Toolkit::show_notification(const std::string &id, void Toolkit::show_tooltip(const std::string &tip) { + // TODO: } void @@ -341,3 +350,16 @@ Toolkit::notify_confirm(const std::string &id) notifiers[id](); } } + +bool +Toolkit::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ApplicationPaletteChange || event->type() == QEvent::ThemeChange) + { + spdlog::info("Theme changed to {}", + (QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Light) ? "Light" : "Dark"); + event->accept(); + return true; + } + return false; +} diff --git a/ui/app/toolkits/qt/Toolkit.hh b/ui/app/toolkits/qt/Toolkit.hh index a010351e2..3dd8c8e52 100644 --- a/ui/app/toolkits/qt/Toolkit.hh +++ b/ui/app/toolkits/qt/Toolkit.hh @@ -26,6 +26,7 @@ #include #include "AboutDialog.hh" +#include "DebugDialog.hh" #include "ExercisesDialog.hh" #include "IToolkitPrivate.hh" #include "MainWindow.hh" @@ -99,6 +100,8 @@ private: void on_status_icon_balloon_activated(const std::string &id); void on_status_icon_activated(); + bool eventFilter(QObject *obj, QEvent *event) override; + protected: std::shared_ptr app; MainWindow *main_window{nullptr}; @@ -108,6 +111,7 @@ private: char **argv{}; QPointer statistics_dialog; QPointer preferences_dialog; + QPointer debug_dialog; QPointer exercises_dialog; QPointer about_dialog; std::shared_ptr status_icon; @@ -120,6 +124,8 @@ private: std::map> notifiers; + workrave::utils::Trackable tracker; + boost::signals2::signal timer_signal; boost::signals2::signal main_window_closed_signal; boost::signals2::signal session_idle_changed_signal; diff --git a/ui/app/toolkits/qt/ToolkitMenu.cc b/ui/app/toolkits/qt/ToolkitMenu.cc index e073bacf4..56cec68cf 100644 --- a/ui/app/toolkits/qt/ToolkitMenu.cc +++ b/ui/app/toolkits/qt/ToolkitMenu.cc @@ -15,8 +15,9 @@ // along with this program. If not, see . // +#include + #ifdef HAVE_CONFIG_H -# include # include "config.h" #endif diff --git a/ui/app/toolkits/qt/ToolkitWindows.cc b/ui/app/toolkits/qt/ToolkitWindows.cc index 76290e2f6..b2ccbb9ad 100644 --- a/ui/app/toolkits/qt/ToolkitWindows.cc +++ b/ui/app/toolkits/qt/ToolkitWindows.cc @@ -94,12 +94,14 @@ ToolkitWindows::init_gui() void ToolkitWindows::init_filter() { + QCoreApplication::instance()->installNativeEventFilter(this); } bool -ToolkitWindows::filter_func(MSG *msg) +ToolkitWindows::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) { TRACE_ENTRY(); + auto *msg = static_cast(message); switch (msg->message) { case WM_WTSSESSION_CHANGE: @@ -176,7 +178,7 @@ ToolkitWindows::filter_func(MSG *msg) event_hook(msg); - return true; + return false; } HWND diff --git a/ui/app/toolkits/qt/ToolkitWindows.hh b/ui/app/toolkits/qt/ToolkitWindows.hh index 6e4abb047..cff2df9e5 100644 --- a/ui/app/toolkits/qt/ToolkitWindows.hh +++ b/ui/app/toolkits/qt/ToolkitWindows.hh @@ -33,6 +33,7 @@ class ToolkitWindows : public Toolkit , public IToolkitWindows + , public QAbstractNativeEventFilter { public: ToolkitWindows(int argc, char **argv); @@ -56,6 +57,7 @@ private: void init_gui(); bool filter_func(MSG *msg); + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; private: boost::signals2::signal event_hook; diff --git a/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.cc b/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.cc index ab04f63d2..d39baea1d 100644 --- a/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.cc +++ b/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.cc @@ -218,3 +218,72 @@ GeneralUiPreferencesPanel::on_block_changed() } GUIConfig::block_mode().set(m); } + +// void +// GeneralPreferencePanel::on_icon_theme_changed() +// { +// TRACE_ENTRY(); +// int idx = icon_theme_button->get_active_row_number(); + +// if (idx == 0) +// { +// GUIConfig::icon_theme().set(""); +// } +// else +// { +// GUIConfig::icon_theme().set(icon_theme_button->get_active_text()); +// } +// } + +// void +// GeneralPreferencePanel::update_icon_theme_combo() +// { +// TRACE_ENTRY(); +// std::list themes; + +// for (const auto &dirname: AssetPath::get_search_path(SearchPathId::Images)) +// { +// auto path = dirname.string(); +// if (!g_str_has_suffix(path.c_str(), "images")) +// { +// continue; +// } + +// GDir *dir = g_dir_open(path.c_str(), 0, nullptr); +// if (dir != nullptr) +// { +// const char *file = nullptr; +// while ((file = g_dir_read_name(dir)) != nullptr) +// { +// gchar *test_path = g_build_filename(dirname.string().c_str(), file, nullptr); +// if (test_path != nullptr && g_file_test(test_path, G_FILE_TEST_IS_DIR)) +// { +// themes.emplace_back(file); +// } +// g_free(test_path); +// } +// g_dir_close(dir); +// } +// } + +// if (!themes.empty()) +// { +// icon_theme_button = Gtk::manage(new Gtk::ComboBoxText()); + +// icon_theme_button->append(_("Default")); +// icon_theme_button->set_active(0); + +// std::string current_icontheme = GUIConfig::icon_theme()(); +// int idx = 1; +// for (auto &theme: themes) +// { +// icon_theme_button->append(theme); +// if (current_icontheme == theme) +// { +// icon_theme_button->set_active(idx); +// } +// idx++; +// } +// icon_theme_button->signal_changed().connect(sigc::mem_fun(*this, &GeneralPreferencePanel::on_icon_theme_changed)); +// } +// } diff --git a/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.hh b/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.hh index cba0ae652..d3fad06e0 100644 --- a/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.hh +++ b/ui/app/toolkits/qt/preferences/GeneralUiPreferencesPanel.hh @@ -34,10 +34,12 @@ public: private: void on_block_changed(); + #if defined(PLATFORM_OS_WINDOWS) void on_autostart_toggled(); #endif + void on_icon_theme_changed(); private: DataConnector::Ptr connector; @@ -46,6 +48,12 @@ private: QComboBox *languages_combo{nullptr}; QStandardItemModel *model{nullptr}; + QComboBox *icon_theme_combo{nullptr}; // TODO + QCheckBox *trayicon_cb{nullptr}; + +#if defined(PLATFORM_OS_UNIX) + QCheckBox *force_x11_cb{nullptr}; // TODO +#endif #if defined(PLATFORM_OS_WINDOWS) QCheckBox *autostart_cb{nullptr}; #endif diff --git a/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.cc b/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.cc new file mode 100644 index 000000000..36c1f4f6b --- /dev/null +++ b/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.cc @@ -0,0 +1,97 @@ +// Copyright (C) 2002 - 2013 Rob Caelers & Raymond Penners +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "MonitoringPreferencesPanel.hh" + +#include "debug.hh" + +#include "DataConnector.hh" + +#include "core/CoreConfig.hh" + +using namespace workrave; +using namespace workrave::utils; + +MonitoringPreferencesPanel::MonitoringPreferencesPanel(std::shared_ptr app, QWidget *parent) + : QWidget(parent) + , app(app) + , connector(std::make_shared(app)) +{ + TRACE_ENTRY(); + create_panel(); +} + +MonitoringPreferencesPanel::~MonitoringPreferencesPanel() +{ + TRACE_ENTRY(); +} + +void MonitoringPreferencesPanel::create_panel() +{ + auto *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(12, 12, 12, 12); + +#if defined(PLATFORM_OS_WINDOWS) + monitor_type_cb = new QCheckBox(this); + monitor_type_cb->setLayoutDirection(Qt::RightToLeft); + monitor_type_cb->setText(tr("Use alternate monitor")); + connect(monitor_type_cb, &QCheckBox::toggled, this, &MonitoringPreferencesPanel::on_monitor_type_toggled); + main_layout->addWidget(monitor_type_cb); + + auto *monitor_type_help1 = new QLabel(tr("Enable this option if Workrave fails to detect when you are using your computer"), this); + main_layout->addWidget(monitor_type_help1); + auto *monitor_type_help2 = new QLabel(tr("Workrave needs to be restarted manually after changing this setting"), this); + main_layout->addWidget(monitor_type_help2); + + sensitivity_box = new QHBoxLayout(); + auto *sensitivity_label = new QLabel(tr("Mouse sensitivity:"), this); + sensitivity_box->addWidget(sensitivity_label); + auto *sensitivity_spin = new QSpinBox(this); + sensitivity_box->addWidget(sensitivity_spin); + main_layout->addLayout(sensitivity_box); + + connector->connect(CoreConfig::monitor_sensitivity(), dc::wrap(sensitivity_spin)); + + std::string monitor_type; + app->get_configurator()->get_value_with_default("advanced/monitor", monitor_type, "default"); + + monitor_type_cb->setChecked(monitor_type != "default"); + sensitivity_box->setEnabled(monitor_type != "default"); +#endif + + debug_btn = new QPushButton(tr("Debug monitoring"), this); + connect(debug_btn, &QPushButton::clicked, this, &MonitoringPreferencesPanel::on_debug_pressed); + main_layout->addWidget(debug_btn); +} + +#if defined(PLATFORM_OS_WINDOWS) +void MonitoringPreferencesPanel::on_monitor_type_toggled() +{ + bool on = monitor_type_cb->isChecked(); + app->get_configurator()->set_value("advanced/monitor", on ? "lowlevel" : "default"); + sensitivity_box->setEnabled(on); +} +#endif + +void MonitoringPreferencesPanel::on_debug_pressed() +{ + app->get_toolkit()->show_window(IToolkit::WindowType::Debug); +} diff --git a/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.hh b/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.hh new file mode 100644 index 000000000..00c28b8f7 --- /dev/null +++ b/ui/app/toolkits/qt/preferences/MonitoringPreferencesPanel.hh @@ -0,0 +1,63 @@ +// Copyright (C) 2002- 2012 Rob Caelers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef MONITORINGPREFERENCESPANEL_HH +#define MONITORINGPREFERENCESPANEL_HH + +#include +#include + +#include + +#include "ui/IApplicationContext.hh" + +class DataConnector; +class Configurator; + +class MonitoringPreferencesPanel + : public QWidget +{ + Q_OBJECT + +public: + explicit MonitoringPreferencesPanel(std::shared_ptr app, QWidget *parent = nullptr); + ~MonitoringPreferencesPanel() override; + +private slots: + void on_debug_pressed(); + +#if defined(PLATFORM_OS_WINDOWS) + void on_monitor_type_toggled(); +#endif + +private: + void create_panel(); + +private: + std::shared_ptr app; + std::shared_ptr connector; + + QPushButton *debug_btn{nullptr}; + +#if defined(PLATFORM_OS_WINDOWS) + QCheckBox *monitor_type_cb{nullptr}; + QSlider *sensitivity_slider{nullptr}; + QHBoxLayout *sensitivity_box{nullptr}; +#endif +}; + +#endif // MONITORINGPREFERENCESPANEL_HH diff --git a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc index 6a2304fe8..7964ae0b5 100644 --- a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc +++ b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc @@ -27,8 +27,6 @@ #include #include -#include "debug.hh" - #include "ui/GUIConfig.hh" #include "core/CoreConfig.hh" @@ -52,8 +50,10 @@ TimerBoxPreferencesPanel::TimerBoxPreferencesPanel(std::shared_ptrsetText(tr("Fallback applet enabled")); + connector->connect(GUIConfig::applet_fallback_enabled(), dc::wrap(applet_fallback_enabled_cb)); +} + +void +TimerBoxPreferencesPanel::init_status_icon() +{ + applet_icon_enabled_cb->setText(tr("Fallback applet enabled")); + connector->connect(GUIConfig::applet_icon_enabled(), dc::wrap(applet_icon_enabled_cb)); +} + void TimerBoxPreferencesPanel::init() { @@ -159,6 +173,8 @@ TimerBoxPreferencesPanel::init() init_placement(); init_cycle(); init_timer_display(); + init_fallback_applet(); + init_status_icon(); layout = new QVBoxLayout; setLayout(layout); @@ -174,7 +190,17 @@ TimerBoxPreferencesPanel::init() if (name == "main_window") { - display_layout->addWidget(ontop_cb); +#if defined(PLATFORM_OS_UNIX) + if (!workrave::utils::Platform::running_on_wayland()) +#endif + { + display_layout->addWidget(ontop_cb); + } + } + if (name == "applet") + { + display_layout->addWidget(applet_fallback_enabled_cb); + display_layout->addWidget(applet_icon_enabled_cb); } UiUtil::add_widget(display_layout, tr("Placement:"), place_button); @@ -316,6 +342,8 @@ TimerBoxPreferencesPanel::enable_buttons() timer_display_button[i]->setEnabled(on && timer_on); } cycle_entry->setEnabled(on && num_disabled != 3); + applet_fallback_enabled_cb->setEnabled(on && num_disabled != 3); + applet_icon_enabled_cb->setEnabled(on && num_disabled != 3); } else if (name == "main_window") { diff --git a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.hh b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.hh index 720ce5dff..fb9f0a5b0 100644 --- a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.hh +++ b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.hh @@ -41,15 +41,19 @@ private: void init_ontop(); void init_placement(); void init_cycle(); + void init_fallback_applet(); + void init_status_icon(); void init_timer_display(); void init_config(); void init(); void enable_buttons(); - void on_place_changed(); + void on_place_changed(); auto on_enabled_toggled(const std::string &key, bool write) -> bool; auto on_timer_display_changed(int break_id, const std::string &key, bool write) -> bool; + void on_applet_fallback_enabled_toggled(); + void on_applet_icon_enabled_toggled(); private: std::shared_ptr app; @@ -60,8 +64,10 @@ private: QCheckBox *ontop_cb{nullptr}; QCheckBox *enabled_cb{nullptr}; QComboBox *place_button{nullptr}; - std::array timer_display_button {}; + std::array timer_display_button{}; QSpinBox *cycle_entry{nullptr}; + QCheckBox *applet_fallback_enabled_cb{nullptr}; + QCheckBox *applet_icon_enabled_cb{nullptr}; }; #endif // TIMERBOXUIPREFERENCESPANEL_HH diff --git a/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.cc b/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.cc index ed397c77a..1e569ebfb 100644 --- a/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.cc +++ b/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.cc @@ -142,6 +142,11 @@ TimerPreferencesPanel::create_options_panel() -> QWidget * layout->addWidget(auto_natural_cb); connector->connect(GUIConfig::break_auto_natural(break_id), dc::wrap(auto_natural_cb)); + + allow_shutdown_cb = new QCheckBox(tr("Enable shutting down the computer from the rest screen")); + layout->addWidget(allow_shutdown_cb); + + connector->connect(GUIConfig::break_enable_shutdown(break_id), dc::wrap(allow_shutdown_cb)); } layout->addStretch(); @@ -294,6 +299,11 @@ TimerPreferencesPanel::enable_buttons() monitor_cb->setEnabled(on); } + if (allow_shutdown_cb != nullptr) + { + allow_shutdown_cb->setEnabled(on); + } + prelude_cb->setEnabled(on); has_max_prelude_cb->setEnabled(on); limit_tim->setEnabled(on); diff --git a/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.hh b/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.hh index 89c19caac..a987efdfa 100644 --- a/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.hh +++ b/ui/app/toolkits/qt/preferences/TimerPreferencesPanel.hh @@ -60,6 +60,7 @@ private: QCheckBox *monitor_cb{nullptr}; QCheckBox *prelude_cb{nullptr}; QCheckBox *skippable_cb{nullptr}; + QCheckBox *allow_shutdown_cb{nullptr}; QSpinBox *exercises_spin{nullptr}; QSpinBox *max_prelude_spin{nullptr}; TimeEntry *auto_reset_tim{nullptr}; diff --git a/ui/app/toolkits/qt/widgets/Frame.hh b/ui/app/toolkits/qt/widgets/Frame.hh index 8016ce9b2..09d6fd7d3 100644 --- a/ui/app/toolkits/qt/widgets/Frame.hh +++ b/ui/app/toolkits/qt/widgets/Frame.hh @@ -53,6 +53,7 @@ protected: private: auto get_frame_rect() const -> QRect; +private: int frame_width{0}; int border_width{0}; QColor frame_color{QColor("black")}; diff --git a/ui/app/toolkits/qt/widgets/IconListNotebook.cc b/ui/app/toolkits/qt/widgets/IconListNotebook.cc index 6d45621e8..fe0a27493 100644 --- a/ui/app/toolkits/qt/widgets/IconListNotebook.cc +++ b/ui/app/toolkits/qt/widgets/IconListNotebook.cc @@ -70,6 +70,19 @@ IconListNotebook::sizeHint() const -> QSize return {max_x, max_y}; } +QIcon +IconListNotebook::pad_icon(const QIcon &icon, int padding) +{ + QPixmap pixmap(icon.pixmap(24 + (2 * padding), 24 + (2 * padding))); + pixmap.fill(Qt::transparent); + + QPainter painter(&pixmap); + painter.drawPixmap(padding, padding, icon.pixmap(24, 24)); + painter.end(); + + return QIcon(pixmap); +} + void IconListNotebook::add_page(QWidget *page, const QIcon &icon, const QString &title) { @@ -84,9 +97,11 @@ IconListNotebook::add_page(QWidget *page, const QIcon &icon, const QString &titl { button->setChecked(true); } - button->setIconSize(QSize(20, 20)); + + QIcon padded_icon = pad_icon(icon, 10); + button->setIconSize(QSize(44, 44)); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - button->setIcon(icon); + button->setIcon(padded_icon); button->setText(QString(" ") + title); button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); button->setStyleSheet("QToolButton { padding: 10px; } "); diff --git a/ui/app/toolkits/qt/widgets/IconListNotebook.hh b/ui/app/toolkits/qt/widgets/IconListNotebook.hh index 3de8b6c2a..c72f7a135 100644 --- a/ui/app/toolkits/qt/widgets/IconListNotebook.hh +++ b/ui/app/toolkits/qt/widgets/IconListNotebook.hh @@ -43,6 +43,7 @@ public: private: void on_button_selected(QAbstractButton *button); + QIcon pad_icon(const QIcon &icon, int padding); private: std::unique_ptr button_group;