Files

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)));
}