139 lines
6.6 KiB
C
139 lines
6.6 KiB
C
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "native_internal.h"
|
|
|
|
static GHashTable *extensionPages;
|
|
static GHashTable *extensionControls;
|
|
|
|
static void extension_combo_changed(GtkComboBox *combo, gpointer data) {
|
|
(void)data;
|
|
prefs_describe_combo(GTK_WIDGET(combo), g_object_get_data(G_OBJECT(combo), "extension-label"));
|
|
}
|
|
|
|
static char *control_key(const char *page, const char *id) {
|
|
return g_strconcat(page, "/", id, NULL);
|
|
}
|
|
static GtkWidget *extension_control(const char *page, const char *id) {
|
|
char *key = control_key(page, id);
|
|
GtkWidget *widget = extensionControls ? g_hash_table_lookup(extensionControls, key) : NULL;
|
|
g_free(key);
|
|
return widget;
|
|
}
|
|
void prefs_extension_add(const char *key, const char *title) {
|
|
if (!extensionPages) {
|
|
extensionPages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
|
extensionControls = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
|
}
|
|
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
GtkWidget *grid = gtk_grid_new();
|
|
gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
|
|
gtk_grid_set_column_spacing(GTK_GRID(grid), 12);
|
|
gtk_container_set_border_width(GTK_CONTAINER(grid), 12);
|
|
gtk_container_add(GTK_CONTAINER(scroll), grid);
|
|
g_object_set_data(G_OBJECT(scroll), "extension-grid", grid);
|
|
gtk_notebook_append_page(GTK_NOTEBOOK(prefs_widget("notebook")), scroll, gtk_label_new(title));
|
|
g_hash_table_insert(extensionPages, g_strdup(key), scroll);
|
|
gtk_widget_show_all(scroll);
|
|
}
|
|
void prefs_extension_remove(const char *key) {
|
|
if (!extensionPages) return;
|
|
GtkWidget *page = g_hash_table_lookup(extensionPages, key);
|
|
if (!page) return;
|
|
GtkNotebook *notebook = GTK_NOTEBOOK(prefs_widget("notebook"));
|
|
int pageIndex = gtk_notebook_page_num(notebook, page);
|
|
GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(prefs_widget("window")));
|
|
if (gtk_notebook_get_current_page(notebook) == pageIndex) {
|
|
// Switch while the old page is still realized. GTK must not attempt to
|
|
// restore a focus child from a page whose controls are being destroyed.
|
|
gtk_notebook_set_current_page(notebook, 0);
|
|
gtk_widget_grab_focus(GTK_WIDGET(notebook));
|
|
} else if (focus && gtk_widget_is_ancestor(focus, page)) {
|
|
gtk_widget_grab_focus(GTK_WIDGET(notebook));
|
|
}
|
|
GHashTableIter iterator;
|
|
gpointer controlKey, widget;
|
|
char *prefix = g_strconcat(key, "/", NULL);
|
|
g_hash_table_iter_init(&iterator, extensionControls);
|
|
while (g_hash_table_iter_next(&iterator, &controlKey, &widget)) {
|
|
if (g_str_has_prefix(controlKey, prefix)) g_hash_table_iter_remove(&iterator);
|
|
}
|
|
g_free(prefix);
|
|
gtk_notebook_remove_page(notebook, pageIndex);
|
|
g_hash_table_remove(extensionPages, key);
|
|
}
|
|
void prefs_extensions_close(void) {
|
|
g_clear_pointer(&extensionControls, g_hash_table_destroy);
|
|
g_clear_pointer(&extensionPages, g_hash_table_destroy);
|
|
}
|
|
const char *prefs_extension_selected(void) {
|
|
if (!extensionPages) return "";
|
|
GtkNotebook *notebook = GTK_NOTEBOOK(prefs_widget("notebook"));
|
|
GtkWidget *current = gtk_notebook_get_nth_page(notebook, gtk_notebook_get_current_page(notebook));
|
|
GHashTableIter iterator;
|
|
gpointer key, page;
|
|
g_hash_table_iter_init(&iterator, extensionPages);
|
|
while (g_hash_table_iter_next(&iterator, &key, &page)) {
|
|
if (page == current) return key;
|
|
}
|
|
return "";
|
|
}
|
|
void prefs_extension_select(const char *key) {
|
|
if (!extensionPages) return;
|
|
GtkWidget *page = g_hash_table_lookup(extensionPages, key);
|
|
if (page) {
|
|
GtkNotebook *notebook = GTK_NOTEBOOK(prefs_widget("notebook"));
|
|
gtk_notebook_set_current_page(notebook, gtk_notebook_page_num(notebook, page));
|
|
}
|
|
}
|
|
void prefs_extension_control(const char *pageKey, const char *id, const char *label, int kind, int row, int maxLength, int disabled) {
|
|
GtkWidget *page = g_hash_table_lookup(extensionPages, pageKey);
|
|
GtkWidget *grid = g_object_get_data(G_OBJECT(page), "extension-grid");
|
|
GtkWidget *control;
|
|
if (kind == 1) {
|
|
control = gtk_combo_box_text_new();
|
|
// Focus the internal combo button so Tab and Shift+Tab leave it normally.
|
|
gtk_widget_set_can_focus(control, FALSE);
|
|
g_object_set_data_full(G_OBJECT(control), "extension-label", g_strdup(label), g_free);
|
|
g_signal_connect(control, "changed", G_CALLBACK(extension_combo_changed), NULL);
|
|
} else if (kind == 2) {
|
|
control = gtk_entry_new();
|
|
gtk_entry_set_max_length(GTK_ENTRY(control), maxLength ? maxLength : 8192);
|
|
} else {
|
|
control = gtk_check_button_new_with_label(label);
|
|
}
|
|
atk_object_set_name(gtk_widget_get_accessible(control), label);
|
|
gtk_widget_set_hexpand(control, TRUE);
|
|
gtk_widget_set_sensitive(control, !disabled);
|
|
if (kind != 3) {
|
|
GtkWidget *name = gtk_label_new(label);
|
|
gtk_label_set_xalign(GTK_LABEL(name), 0);
|
|
gtk_label_set_mnemonic_widget(GTK_LABEL(name), control);
|
|
gtk_grid_attach(GTK_GRID(grid), name, 0, row, 1, 1);
|
|
gtk_grid_attach(GTK_GRID(grid), control, 1, row, 1, 1);
|
|
} else {
|
|
gtk_grid_attach(GTK_GRID(grid), control, 0, row, 2, 1);
|
|
}
|
|
g_hash_table_insert(extensionControls, control_key(pageKey, id), control);
|
|
gtk_widget_show_all(grid);
|
|
}
|
|
void prefs_extension_option(const char *page, const char *id, const char *option, const char *label, int disabled) {
|
|
// Disabled choices are omitted from native selection, preserving their IDs
|
|
// in the host schema so an old/malicious draft is still rejected there.
|
|
if (!disabled) gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(extension_control(page, id)), option, label);
|
|
}
|
|
void prefs_extension_set(const char *page, const char *id, int kind, const char *text, int checked) {
|
|
GtkWidget *widget = extension_control(page, id);
|
|
if (kind == 1) gtk_combo_box_set_active_id(GTK_COMBO_BOX(widget), text);
|
|
else if (kind == 2) gtk_entry_set_text(GTK_ENTRY(widget), text);
|
|
else gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), checked);
|
|
}
|
|
const char *prefs_extension_text(const char *page, const char *id, int kind) {
|
|
GtkWidget *widget = extension_control(page, id);
|
|
const char *value = kind == 1 ? gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget)) : gtk_entry_get_text(GTK_ENTRY(widget));
|
|
return value ? value : "";
|
|
}
|
|
int prefs_extension_checked(const char *page, const char *id) {
|
|
return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(extension_control(page, id)));
|
|
}
|