refactor: build the shortcut overview from AdwShortcutsDialog
The hand-rolled grid existed to avoid GTK's deprecated GtkShortcutsWindow; libadwaita 1.8's AdwShortcutsDialog is the supported replacement, so use the platform widget and get its search, sectioning and shortcut labels for free. The table strings become GTK accelerator syntax, since the shortcut label parses them. The six mouse rows cannot: no accelerator syntax names a wheel or a drag, so they carry an empty accelerator and prose in the new subtitle field. adw_init() is called in run_ui() and in choose_source() (replacing its gtk_init()), which means the whole app is drawn with libadwaita's stylesheet. Verified settled: viewport.w 870..870, axes.w changes 2, x-desync 0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
018b98eddf
commit
d65a121a9a
7 files changed
+123
-92
No files matched your search
+47
-62
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
#include "help_window.hpp"
|
||||
|
||||
#include <adwaita.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
@@ -15,7 +17,10 @@ namespace {
|
||||
/// One pointer per window, file-scope because there is one application.
|
||||
GtkWidget* g_about = nullptr;
|
||||
GtkWidget* g_help = nullptr;
|
||||
GtkWidget* g_shortcuts = nullptr;
|
||||
|
||||
/// The shortcut overview is an `AdwDialog`, not a window, so it has its own pointer and
|
||||
/// its own clearing signal (`::closed` rather than `::close-request`).
|
||||
AdwDialog* g_shortcuts = nullptr;
|
||||
|
||||
gboolean on_window_close(GtkWindow* window, gpointer slot) {
|
||||
(void)window;
|
||||
@@ -23,6 +28,12 @@ gboolean on_window_close(GtkWindow* window, gpointer slot) {
|
||||
return FALSE; // let the default handler destroy it
|
||||
}
|
||||
|
||||
void on_shortcuts_closed(AdwDialog* dialog, gpointer user) {
|
||||
(void)dialog;
|
||||
(void)user;
|
||||
g_shortcuts = nullptr;
|
||||
}
|
||||
|
||||
struct HelpPage {
|
||||
const char* section; ///< the group heading in the sidebar
|
||||
const char* title; ///< the row's label and the page's heading
|
||||
@@ -216,91 +227,65 @@ void show_help(GtkWindow* parent) {
|
||||
|
||||
namespace {
|
||||
|
||||
/// One `accel description` pair appended to `grid` at `row`. The accel column is
|
||||
/// monospaced and right-aligned so the descriptions line up down the window.
|
||||
void append_shortcut_row(GtkWidget* grid, int row, const char* accel_text,
|
||||
const char* description) {
|
||||
GtkWidget* keys = gtk_label_new(nullptr);
|
||||
char markup[256];
|
||||
std::snprintf(markup, sizeof(markup), "<tt>%s</tt>", accel_text);
|
||||
gtk_label_set_markup(GTK_LABEL(keys), markup);
|
||||
gtk_label_set_xalign(GTK_LABEL(keys), 1.0f);
|
||||
gtk_widget_set_margin_end(keys, 14);
|
||||
gtk_grid_attach(GTK_GRID(grid), keys, 0, row, 1, 1);
|
||||
|
||||
GtkWidget* what = gtk_label_new(description);
|
||||
gtk_label_set_xalign(GTK_LABEL(what), 0.0f);
|
||||
gtk_grid_attach(GTK_GRID(grid), what, 1, row, 1, 1);
|
||||
}
|
||||
|
||||
/// A section heading spanning both columns.
|
||||
void append_shortcut_heading(GtkWidget* grid, int row, const char* section) {
|
||||
GtkWidget* heading = gtk_label_new(nullptr);
|
||||
char markup[128];
|
||||
std::snprintf(markup, sizeof(markup), "<b>%s</b>", section);
|
||||
gtk_label_set_markup(GTK_LABEL(heading), markup);
|
||||
gtk_label_set_xalign(GTK_LABEL(heading), 0.0f);
|
||||
gtk_widget_set_margin_top(heading, row == 0 ? 0 : 14);
|
||||
gtk_widget_set_margin_bottom(heading, 4);
|
||||
gtk_grid_attach(GTK_GRID(grid), heading, 0, row, 2, 1);
|
||||
/// Appends one table row to `dialog`, opening a new section whenever the row's `section`
|
||||
/// string differs from the last one. `*current` is the section being filled, `*title` the
|
||||
/// name it carries; both are owned by the caller so the two tables share one run.
|
||||
///
|
||||
/// `accel` is handed to `AdwShortcutsItem` as an accelerator when it is non-empty and
|
||||
/// `subtitle` is used when it is not, which is how a wheel or a drag gets a row: neither
|
||||
/// GTK's nor libadwaita's accelerator syntax can name one, and asking them to parse
|
||||
/// "Wheel" costs a parse warning and draws nothing.
|
||||
void append_shortcut(AdwShortcutsDialog* dialog, AdwShortcutsSection** current,
|
||||
const char** title, const char* section, const char* accel,
|
||||
const char* subtitle, const char* description) {
|
||||
if (*current == nullptr || std::string(*title) != section) {
|
||||
*current = adw_shortcuts_section_new(section);
|
||||
*title = section;
|
||||
adw_shortcuts_dialog_add(dialog, *current);
|
||||
}
|
||||
AdwShortcutsItem* item = adw_shortcuts_item_new(description, accel);
|
||||
if (subtitle != nullptr && *subtitle != '\0') {
|
||||
adw_shortcuts_item_set_subtitle(item, subtitle);
|
||||
}
|
||||
adw_shortcuts_section_add(*current, item);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void show_shortcuts(GtkWindow* parent) {
|
||||
if (g_shortcuts != nullptr) {
|
||||
gtk_window_present(GTK_WINDOW(g_shortcuts));
|
||||
adw_dialog_present(g_shortcuts, GTK_WIDGET(parent));
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget* window = gtk_window_new();
|
||||
g_shortcuts = window;
|
||||
gtk_window_set_title(GTK_WINDOW(window), "Keyboard and mouse shortcuts");
|
||||
gtk_window_set_default_size(GTK_WINDOW(window), 560, 680);
|
||||
gtk_window_set_transient_for(GTK_WINDOW(window), parent);
|
||||
AdwDialog* dialog = adw_shortcuts_dialog_new();
|
||||
g_shortcuts = dialog;
|
||||
adw_dialog_set_title(dialog, "Keyboard and mouse shortcuts");
|
||||
|
||||
GtkWidget* grid = gtk_grid_new();
|
||||
gtk_grid_set_row_spacing(GTK_GRID(grid), 2);
|
||||
gtk_widget_set_margin_start(grid, 20);
|
||||
gtk_widget_set_margin_end(grid, 20);
|
||||
gtk_widget_set_margin_top(grid, 16);
|
||||
gtk_widget_set_margin_bottom(grid, 16);
|
||||
|
||||
int row = 0;
|
||||
const char* section = nullptr;
|
||||
AdwShortcutsSection* section = nullptr;
|
||||
const char* section_title = nullptr;
|
||||
int accel_count = 0;
|
||||
const Accel* accels = app_accels(&accel_count);
|
||||
for (int i = 0; i < accel_count; ++i) {
|
||||
if (section == nullptr || std::string(section) != accels[i].section) {
|
||||
section = accels[i].section;
|
||||
append_shortcut_heading(grid, row++, section);
|
||||
}
|
||||
append_shortcut_row(grid, row++, accels[i].accel_text, accels[i].description);
|
||||
append_shortcut(ADW_SHORTCUTS_DIALOG(dialog), §ion, §ion_title,
|
||||
accels[i].section, accels[i].accel, "", accels[i].description);
|
||||
}
|
||||
int gesture_count = 0;
|
||||
const Gesture* gestures = plot_gestures(&gesture_count);
|
||||
for (int i = 0; i < gesture_count; ++i) {
|
||||
if (section == nullptr || std::string(section) != gestures[i].section) {
|
||||
section = gestures[i].section;
|
||||
append_shortcut_heading(grid, row++, section);
|
||||
}
|
||||
append_shortcut_row(grid, row++, gestures[i].accel_text, gestures[i].description);
|
||||
append_shortcut(ADW_SHORTCUTS_DIALOG(dialog), §ion, §ion_title,
|
||||
gestures[i].section, gestures[i].accel, gestures[i].subtitle,
|
||||
gestures[i].description);
|
||||
}
|
||||
|
||||
GtkWidget* scroll = gtk_scrolled_window_new();
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), grid);
|
||||
gtk_window_set_child(GTK_WINDOW(window), scroll);
|
||||
|
||||
g_signal_connect(window, "close-request", G_CALLBACK(on_window_close), &g_shortcuts);
|
||||
gtk_window_present(GTK_WINDOW(window));
|
||||
g_signal_connect(dialog, "closed", G_CALLBACK(on_shortcuts_closed), nullptr);
|
||||
adw_dialog_present(dialog, GTK_WIDGET(parent));
|
||||
}
|
||||
|
||||
void close_help_windows() {
|
||||
if (g_help != nullptr) gtk_window_close(GTK_WINDOW(g_help));
|
||||
if (g_about != nullptr) gtk_window_close(GTK_WINDOW(g_about));
|
||||
if (g_shortcuts != nullptr) gtk_window_close(GTK_WINDOW(g_shortcuts));
|
||||
if (g_shortcuts != nullptr) adw_dialog_close(g_shortcuts);
|
||||
}
|
||||
|
||||
} // namespace udpscope
|
||||
Reference in new issue
Block a user