Files

156 lines
8.1 KiB
C

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
#include "native_internal.h"
static GtkListStore *binding_store;
static void binding_selected(GtkTreeSelection *selection, gpointer data) {
(void)data;
GtkTreeModel *model;
GtkTreeIter iter;
gboolean selected = gtk_tree_selection_get_selected(selection, &model, &iter);
const char *controls[] = {"keyBindingEntry", "keyBindingSet", "keyBindingClear", "keyBindingReset"};
for (unsigned i = 0; i < G_N_ELEMENTS(controls); i++) gtk_widget_set_sensitive(prefs_widget(controls[i]), selected);
gchar *keys = NULL;
if (selected) gtk_tree_model_get(model, &iter, 2, &keys, -1);
gtk_entry_set_text(GTK_ENTRY(prefs_widget("keyBindingEntry")), keys && strcmp(keys, "Unbound") != 0 ? keys : "");
g_free(keys);
}
static void binding_clicked(GtkButton *button, gpointer data) { (void)button; prefs_set_action(GPOINTER_TO_INT(data)); }
void prefs_init_key_bindings(void) {
binding_store = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
GtkTreeView *tree = GTK_TREE_VIEW(prefs_widget("keyBindingsTreeview"));
gtk_tree_view_set_model(tree, GTK_TREE_MODEL(binding_store));
const char *titles[] = {"Command", "Scope", "Binding", "Default binding"};
for (int i = 0; i < 4; i++) {
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
gtk_tree_view_append_column(tree, gtk_tree_view_column_new_with_attributes(titles[i], renderer, "text", i, NULL));
}
gtk_tree_selection_set_mode(gtk_tree_view_get_selection(tree), GTK_SELECTION_SINGLE);
g_signal_connect(gtk_tree_view_get_selection(tree), "changed", G_CALLBACK(binding_selected), NULL);
const char *buttons[] = {"keyBindingSet", "keyBindingClear", "keyBindingReset", "keyBindingResetAll"};
for (int i = 0; i < 4; i++) g_signal_connect(prefs_widget(buttons[i]), "clicked", G_CALLBACK(binding_clicked), GINT_TO_POINTER(i + 4));
atk_object_set_name(gtk_widget_get_accessible(GTK_WIDGET(tree)), "Key bindings");
binding_selected(gtk_tree_view_get_selection(tree), NULL);
g_object_unref(binding_store); // The tree owns the model.
}
void prefs_bindings_clear(void) { gtk_list_store_clear(binding_store); }
void prefs_binding_append(const char *label, const char *scope, const char *keys, const char *defaults) {
GtkTreeIter iter;
gtk_list_store_append(binding_store, &iter);
gtk_list_store_set(binding_store, &iter, 0, label, 1, scope, 2, keys, 3, defaults, -1);
}
int prefs_binding_selected(void) {
GtkTreeModel *model;
GtkTreeIter iter;
if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(prefs_widget("keyBindingsTreeview"))), &model, &iter)) return -1;
GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
int row = gtk_tree_path_get_indices(path)[0];
gtk_tree_path_free(path);
return row;
}
void prefs_binding_select(int row) {
if (row < 0) return;
GtkTreePath *path = gtk_tree_path_new_from_indices(row, -1);
// Setting the cursor grabs focus and can switch the notebook to this page
// while loading preferences or changing the layout combo. Select the row
// without moving keyboard focus out of the current control.
GtkTreeView *tree = GTK_TREE_VIEW(prefs_widget("keyBindingsTreeview"));
gtk_tree_selection_select_path(gtk_tree_view_get_selection(tree), path);
gtk_tree_view_scroll_to_cell(tree, path, NULL, FALSE, 0, 0);
gtk_tree_path_free(path);
}
static GtkListStore *modifierStore;
static const struct { const char *id; const char *label; } modifierRows[] = {
{"Insert", "Insert"}, {"KP_Insert", "Numpad Insert"}, {"Caps_Lock", "Caps Lock"},
};
static void name_modifier_cells(void) {
AtkObject *accessible = gtk_widget_get_accessible(prefs_widget("cthulhuModifiersTreeview"));
if (!ATK_IS_TABLE(accessible)) return;
for (unsigned i = 0; i < G_N_ELEMENTS(modifierRows); i++) {
AtkObject *cell = atk_table_ref_at(ATK_TABLE(accessible), i, 0);
if (!cell) continue;
gboolean enabled = prefs_modifier_get(modifierRows[i].id);
const char *name = enabled ? "checked" : "unchecked";
if (g_strcmp0(atk_object_get_name(cell), name) != 0) atk_object_set_name(cell, name);
g_object_unref(cell);
}
}
void prefs_modifier_set(const char *id, int enabled) {
for (unsigned i = 0; i < G_N_ELEMENTS(modifierRows); i++) {
if (strcmp(id, modifierRows[i].id) != 0) continue;
if (prefs_modifier_get(id) == (enabled != 0)) return;
GtkTreeIter iter;
if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(modifierStore), &iter, NULL, i))
gtk_list_store_set(modifierStore, &iter, 1, enabled != 0, -1);
name_modifier_cells();
return;
}
}
int prefs_modifier_get(const char *id) {
for (unsigned i = 0; i < G_N_ELEMENTS(modifierRows); i++) {
if (strcmp(id, modifierRows[i].id) != 0) continue;
GtkTreeIter iter;
gboolean enabled = FALSE;
if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(modifierStore), &iter, NULL, i))
gtk_tree_model_get(GTK_TREE_MODEL(modifierStore), &iter, 1, &enabled, -1);
return enabled;
}
return FALSE;
}
static void toggle_modifier_path(const char *pathString) {
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(modifierStore), &iter, pathString)) return;
gboolean enabled;
gtk_tree_model_get(GTK_TREE_MODEL(modifierStore), &iter, 1, &enabled, -1);
gtk_list_store_set(modifierStore, &iter, 1, !enabled, -1);
name_modifier_cells();
}
static void modifier_toggled(GtkCellRendererToggle *renderer, gchar *pathString, gpointer data) {
(void)renderer; (void)data;
toggle_modifier_path(pathString);
}
static gboolean modifier_key_press(GtkWidget *view, GdkEventKey *event, gpointer data) {
(void)data;
if (event->keyval != GDK_KEY_space && event->keyval != GDK_KEY_KP_Space) return FALSE;
GtkTreePath *path = NULL;
gtk_tree_view_get_cursor(GTK_TREE_VIEW(view), &path, NULL);
if (!path) return FALSE;
gchar *pathString = gtk_tree_path_to_string(path);
toggle_modifier_path(pathString);
g_free(pathString);
gtk_tree_path_free(path);
return TRUE;
}
static void layout_modifiers(GtkToggleButton *button, gpointer data) {
if (!gtk_toggle_button_get_active(button)) return;
int laptop = GPOINTER_TO_INT(data);
prefs_modifier_set("Insert", FALSE);
prefs_modifier_set("KP_Insert", !laptop);
prefs_modifier_set("Caps_Lock", laptop);
}
void prefs_init_modifiers(void) {
modifierStore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_BOOLEAN);
for (unsigned i = 0; i < G_N_ELEMENTS(modifierRows); i++) {
GtkTreeIter iter;
gtk_list_store_append(modifierStore, &iter);
gtk_list_store_set(modifierStore, &iter, 0, modifierRows[i].label, 1, FALSE, -1);
}
GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("cthulhuModifiersTreeview"));
gtk_tree_view_set_model(view, GTK_TREE_MODEL(modifierStore));
GtkCellRenderer *textRenderer = gtk_cell_renderer_text_new();
GtkCellRenderer *toggleRenderer = gtk_cell_renderer_toggle_new();
g_signal_connect(toggleRenderer, "toggled", G_CALLBACK(modifier_toggled), NULL);
gtk_tree_view_append_column(view, gtk_tree_view_column_new_with_attributes("Enabled", toggleRenderer, "active", 1, NULL));
gtk_tree_view_append_column(view, gtk_tree_view_column_new_with_attributes("Cthulhu key", textRenderer, "text", 0, NULL));
g_signal_connect(view, "key-press-event", G_CALLBACK(modifier_key_press), NULL);
AtkObject *accessible = gtk_widget_get_accessible(GTK_WIDGET(view));
atk_object_set_name(accessible, "Cthulhu modifier keys");
atk_object_set_description(accessible, "Use arrow keys to select Insert, Numpad Insert, or Caps Lock. Space toggles each key independently. Tab leaves the table.");
g_signal_connect(prefs_widget("generalDesktopButton"), "toggled", G_CALLBACK(layout_modifiers), GINT_TO_POINTER(0));
g_signal_connect(prefs_widget("generalLaptopButton"), "toggled", G_CALLBACK(layout_modifiers), GINT_TO_POINTER(1));
name_modifier_cells();
g_object_unref(modifierStore); // The tree owns the model.
}