// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "native_internal.h" enum { WORD, REPLACEMENT }; static GtkListStore *pronunciations; static void pronunciation_edited(GtkCellRendererText *renderer, gchar *path, gchar *text, gpointer data) { (void)renderer; GtkTreeIter iter; if (gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(pronunciations), &iter, path)) gtk_list_store_set(pronunciations, &iter, GPOINTER_TO_INT(data), text, -1); } static void pronunciation_add(GtkButton *button, gpointer data) { (void)button; (void)data; GtkTreeIter iter; gtk_list_store_append(pronunciations, &iter); gtk_list_store_set(pronunciations, &iter, WORD, "", REPLACEMENT, "", -1); GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(pronunciations), &iter); GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("pronunciationTreeView")); gtk_widget_grab_focus(GTK_WIDGET(view)); gtk_tree_view_set_cursor(view, path, gtk_tree_view_get_column(view, WORD), TRUE); gtk_tree_path_free(path); } static void pronunciation_delete(GtkButton *button, gpointer data) { (void)button; (void)data; GtkTreeIter iter; GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(prefs_widget("pronunciationTreeView"))); if (gtk_tree_selection_get_selected(selection, NULL, &iter)) gtk_list_store_remove(pronunciations, &iter); gtk_widget_grab_focus(prefs_widget("pronunciationTreeView")); } static gboolean pronunciation_key(GtkWidget *widget, GdkEventKey *event, gpointer data) { (void)data; if (event->keyval != GDK_KEY_Return && event->keyval != GDK_KEY_KP_Enter && event->keyval != GDK_KEY_F2) return FALSE; GtkTreePath *path = NULL; GtkTreeViewColumn *column = NULL; gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, &column); if (!path) return FALSE; gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), path, column ? column : gtk_tree_view_get_column(GTK_TREE_VIEW(widget), 0), TRUE); gtk_tree_path_free(path); return TRUE; } void prefs_init_pronunciation(void) { GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("pronunciationTreeView")); pronunciations = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(view, GTK_TREE_MODEL(pronunciations)); const char *labels[] = {"Word", "Pronunciation"}; for (int column = 0; column < 2; column++) { GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); g_object_set(renderer, "editable", TRUE, NULL); g_signal_connect(renderer, "edited", G_CALLBACK(pronunciation_edited), GINT_TO_POINTER(column)); gtk_tree_view_append_column(view, gtk_tree_view_column_new_with_attributes(labels[column], renderer, "text", column, NULL)); } atk_object_set_name(gtk_widget_get_accessible(GTK_WIDGET(view)), "Pronunciation dictionary"); atk_object_set_description(gtk_widget_get_accessible(GTK_WIDGET(view)), "Select a cell and press Enter to edit it. Tab moves to the next control."); g_signal_connect(view, "key-press-event", G_CALLBACK(pronunciation_key), NULL); g_signal_connect(prefs_widget("pronunciationAddButton"), "clicked", G_CALLBACK(pronunciation_add), NULL); g_signal_connect(prefs_widget("pronunciationDeleteButton"), "clicked", G_CALLBACK(pronunciation_delete), NULL); g_object_unref(pronunciations); } void prefs_pronunciation_clear(void) { gtk_list_store_clear(pronunciations); } void prefs_pronunciation_append(const char *word, const char *replacement) { GtkTreeIter iter; gtk_list_store_append(pronunciations, &iter); gtk_list_store_set(pronunciations, &iter, WORD, word, REPLACEMENT, replacement, -1); } int prefs_pronunciation_count(void) { return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(pronunciations), NULL); } char *prefs_pronunciation_value(int row, int column) { GtkTreeIter iter; if (column < 0 || column > 1 || !gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(pronunciations), &iter, NULL, row)) return NULL; char *value = NULL; gtk_tree_model_get(GTK_TREE_MODEL(pronunciations), &iter, column, &value, -1); return value; } enum { ATTRIBUTE_KEY, ATTRIBUTE_LABEL, ATTRIBUTE_SPEAK, ATTRIBUTE_BRAILLE }; static GtkListStore *attributes; static void attribute_toggle(GtkCellRendererToggle *renderer, gchar *path, gpointer data) { (void)renderer; GtkTreeIter iter; int column = GPOINTER_TO_INT(data); if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(attributes), &iter, path)) return; gboolean active = FALSE; gtk_tree_model_get(GTK_TREE_MODEL(attributes), &iter, column, &active, -1); gtk_list_store_set(attributes, &iter, column, !active, -1); } static gboolean attribute_key(GtkWidget *widget, GdkEventKey *event, gpointer data) { (void)data; if (event->keyval != GDK_KEY_space && event->keyval != GDK_KEY_KP_Space) return FALSE; GtkTreePath *path = NULL; GtkTreeViewColumn *column = NULL; gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, &column); if (!path) return FALSE; GtkTreeIter iter; gboolean found = gtk_tree_model_get_iter(GTK_TREE_MODEL(attributes), &iter, path); gtk_tree_path_free(path); if (!found) return FALSE; int selected = column == gtk_tree_view_get_column(GTK_TREE_VIEW(widget), 2) ? ATTRIBUTE_BRAILLE : ATTRIBUTE_SPEAK; gboolean active = FALSE; gtk_tree_model_get(GTK_TREE_MODEL(attributes), &iter, selected, &active, -1); gtk_list_store_set(attributes, &iter, selected, !active, -1); return TRUE; } static void attribute_move(GtkButton *button, gpointer data) { (void)button; GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("textAttributesTreeView")); GtkTreeIter iter, other; if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(view), NULL, &iter)) return; GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(attributes), &iter); int row = gtk_tree_path_get_indices(path)[0]; int last = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(attributes), NULL) - 1; int target = row; switch (GPOINTER_TO_INT(data)) { case 0: target = 0; break; case 1: target = row - 1; break; case 2: target = row + 1; break; case 3: target = last; break; } if (target >= 0 && target <= last && target != row && gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(attributes), &other, NULL, target)) { if (target < row) gtk_list_store_move_before(attributes, &iter, &other); else gtk_list_store_move_after(attributes, &iter, &other); GtkTreePath *new_path = gtk_tree_path_new_from_indices(target, -1); gtk_tree_view_set_cursor(view, new_path, NULL, FALSE); gtk_tree_path_free(new_path); } gtk_tree_path_free(path); gtk_widget_grab_focus(GTK_WIDGET(view)); } static void attribute_select_all(GtkButton *button, gpointer data) { (void)button; gboolean active = GPOINTER_TO_INT(data) != 0; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(attributes), &iter); while (valid) { gtk_list_store_set(attributes, &iter, ATTRIBUTE_SPEAK, active, ATTRIBUTE_BRAILLE, active, -1); valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(attributes), &iter); } } static void attribute_reset(GtkButton *button, gpointer data) { (void)button; (void)data; prefs_set_action(8); } void prefs_init_text_attributes(void) { GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("textAttributesTreeView")); attributes = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); gtk_tree_view_set_model(view, GTK_TREE_MODEL(attributes)); GtkCellRenderer *text = gtk_cell_renderer_text_new(); gtk_tree_view_append_column(view, gtk_tree_view_column_new_with_attributes("Attribute", text, "text", ATTRIBUTE_LABEL, NULL)); const char *labels[] = {"Speak", "Braille"}; for (int i = 0; i < 2; i++) { GtkCellRenderer *renderer = gtk_cell_renderer_toggle_new(); g_object_set(renderer, "activatable", TRUE, NULL); g_signal_connect(renderer, "toggled", G_CALLBACK(attribute_toggle), GINT_TO_POINTER(i+ATTRIBUTE_SPEAK)); gtk_tree_view_append_column(view, gtk_tree_view_column_new_with_attributes(labels[i], renderer, "active", i+ATTRIBUTE_SPEAK, NULL)); } g_signal_connect(view, "key-press-event", G_CALLBACK(attribute_key), NULL); AtkObject *accessible = gtk_widget_get_accessible(GTK_WIDGET(view)); atk_object_set_name(accessible, "Text attributes"); atk_object_set_description(accessible, "Use arrows to select an attribute. Space toggles the selected Speak or Braille column. Use the Move buttons to change order."); const char *moves[] = {"textMoveToTopButton", "textMoveUpOneButton", "textMoveDownOneButton", "textMoveToBottomButton"}; for (int i = 0; i < 4; i++) g_signal_connect(prefs_widget(moves[i]), "clicked", G_CALLBACK(attribute_move), GINT_TO_POINTER(i)); g_signal_connect(prefs_widget("textSelectAllButton"), "clicked", G_CALLBACK(attribute_select_all), GINT_TO_POINTER(1)); g_signal_connect(prefs_widget("textUnselectAllButton"), "clicked", G_CALLBACK(attribute_select_all), GINT_TO_POINTER(0)); g_signal_connect(prefs_widget("textResetButton"), "clicked", G_CALLBACK(attribute_reset), NULL); g_object_unref(attributes); } void prefs_text_attributes_clear(void) { gtk_list_store_clear(attributes); } void prefs_text_attribute_append(const char *key, const char *label, int speak, int braille) { GtkTreeIter iter; gtk_list_store_append(attributes, &iter); gtk_list_store_set(attributes, &iter, ATTRIBUTE_KEY, key, ATTRIBUTE_LABEL, label, ATTRIBUTE_SPEAK, speak != 0, ATTRIBUTE_BRAILLE, braille != 0, -1); } int prefs_text_attribute_count(void) { return gtk_tree_model_iter_n_children(GTK_TREE_MODEL(attributes), NULL); } char *prefs_text_attribute_key(int row) { GtkTreeIter iter; if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(attributes), &iter, NULL, row)) return NULL; char *key = NULL; gtk_tree_model_get(GTK_TREE_MODEL(attributes), &iter, ATTRIBUTE_KEY, &key, -1); return key; } int prefs_text_attribute_enabled(int row, int column) { GtkTreeIter iter; if (column < ATTRIBUTE_SPEAK || column > ATTRIBUTE_BRAILLE || !gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(attributes), &iter, NULL, row)) return 0; gboolean active = FALSE; gtk_tree_model_get(GTK_TREE_MODEL(attributes), &iter, column, &active, -1); return active; }