// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "native_internal.h" static GtkListStore *reading_store; static const struct { const char *id; const char *label; } reading_rows[] = { {"only_speak_displayed_text", "Only speak displayed text"}, {"automatically_read_title_changes", "Automatically read title changes"}, {"automatically_read_clipboard_changes", "Automatically read clipboard changes"}, {"enable_pause_breaks", "Break speech into chunks between pauses"}, {"speak_numbers_as_digits", "Speak numbers as digits"}, {"speak_multicase_strings_as_words", "Speak multicase strings as words"}, {"enable_indentation", "Enable indentation presentation"}, {"speak_blank_lines", "Speak blank lines"}, {"enable_mnemonic_speaking", "Speak object mnemonics"}, {"enable_position_speaking", "Speak child position"}, {"enable_tutorial_messages", "Speak tutorial messages"}, {"speak_description", "Speak object descriptions"}, {"messages_are_detailed", "System messages are detailed"}, {"use_color_names", "Speak colors as names"}, {"speak_misspelled_indicator", "Speak misspelled-word indicator"}, {"speak_context_blockquote", "Announce block quotes during navigation"}, {"speak_context_list", "Announce lists during navigation"}, {"speak_context_table", "Announce tables during navigation"}, {"speak_context_panel", "Announce panels during navigation"}, {"speak_context_landmark", "Announce landmarks during navigation"}, {"speak_context_non_landmark_form", "Announce forms during navigation"}, {"read_full_row_in_gui_table", "Speak full row in GUI tables"}, {"read_full_row_in_document_table", "Speak full row in document tables"}, {"read_full_row_in_spreadsheet", "Speak full row in spreadsheets"}, {"present_tooltips", "Present tooltips"}, {"enable_mouse_review", "Speak object under mouse"}, {"say_all_context_blockquote", "Announce block quotes in Say All"}, {"say_all_context_list", "Announce lists in Say All"}, {"say_all_context_table", "Announce tables in Say All"}, {"say_all_context_panel", "Announce panels in Say All"}, {"say_all_context_non_landmark_form", "Announce forms in Say All"}, {"say_all_context_landmark", "Announce landmarks in Say All"}, }; enum { READING_ID, READING_LABEL, READING_ENABLED, READING_N_COLUMNS }; static void name_reading_toggle_cells(void); static void reading_toggled(GtkCellRendererToggle *renderer, gchar *path_string, gpointer data) { (void)renderer; (void)data; GtkTreeIter iter; if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(reading_store), &iter, path_string)) { return; } gboolean active; gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ENABLED, &active, -1); gtk_list_store_set(reading_store, &iter, READING_ENABLED, !active, -1); name_reading_toggle_cells(); } static gboolean reading_key_press(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; gtk_tree_view_get_cursor(GTK_TREE_VIEW(widget), &path, NULL); if (!path) return FALSE; GtkTreeIter iter; gboolean found = gtk_tree_model_get_iter(GTK_TREE_MODEL(reading_store), &iter, path); gtk_tree_path_free(path); if (!found) return FALSE; gboolean active; gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ENABLED, &active, -1); gtk_list_store_set(reading_store, &iter, READING_ENABLED, !active, -1); name_reading_toggle_cells(); return TRUE; } static void name_reading_toggle_cells(void) { AtkObject *accessible = gtk_widget_get_accessible(prefs_widget("readingSettingsTreeview")); if (!ATK_IS_TABLE(accessible)) return; for (unsigned int i = 0; i < G_N_ELEMENTS(reading_rows); i++) { AtkObject *cell = atk_table_ref_at(ATK_TABLE(accessible), i, 0); if (!cell) continue; GtkTreeIter iter; gboolean active = FALSE; if (gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(reading_store), &iter, NULL, i)) gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ENABLED, &active, -1); const char *name = active ? "checked" : "unchecked"; if (g_strcmp0(atk_object_get_name(cell), name) != 0) atk_object_set_name(cell, name); g_object_unref(cell); } } void prefs_init_reading_settings(void) { reading_store = gtk_list_store_new(READING_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); for (unsigned int i = 0; i < G_N_ELEMENTS(reading_rows); i++) { GtkTreeIter iter; gtk_list_store_append(reading_store, &iter); gtk_list_store_set(reading_store, &iter, READING_ID, reading_rows[i].id, READING_LABEL, reading_rows[i].label, READING_ENABLED, FALSE, -1); } GtkTreeView *view = GTK_TREE_VIEW(prefs_widget("readingSettingsTreeview")); gtk_tree_view_set_model(view, GTK_TREE_MODEL(reading_store)); gtk_tree_view_set_headers_visible(view, TRUE); GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new(); GtkTreeViewColumn *setting_column = gtk_tree_view_column_new_with_attributes( "Setting", text_renderer, "text", READING_LABEL, NULL); GtkCellRenderer *toggle_renderer = gtk_cell_renderer_toggle_new(); g_object_set(toggle_renderer, "activatable", TRUE, NULL); g_signal_connect(toggle_renderer, "toggled", G_CALLBACK(reading_toggled), NULL); GtkTreeViewColumn *enabled_column = gtk_tree_view_column_new_with_attributes( "Enabled", toggle_renderer, "active", READING_ENABLED, NULL); gtk_tree_view_append_column(view, enabled_column); gtk_tree_view_append_column(view, setting_column); g_signal_connect(view, "key-press-event", G_CALLBACK(reading_key_press), NULL); name_reading_toggle_cells(); } void prefs_reading_set(const char *id, int value) { GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(reading_store), &iter); while (valid) { gchar *row_id; gboolean active; gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ID, &row_id, READING_ENABLED, &active, -1); if (g_strcmp0(row_id, id) == 0) { if (active != (value != 0)) { gtk_list_store_set(reading_store, &iter, READING_ENABLED, value != 0, -1); name_reading_toggle_cells(); } g_free(row_id); return; } g_free(row_id); valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(reading_store), &iter); } } int prefs_reading_get(const char *id) { GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(reading_store), &iter); while (valid) { gchar *row_id; gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ID, &row_id, -1); if (g_strcmp0(row_id, id) == 0) { gboolean active; gtk_tree_model_get(GTK_TREE_MODEL(reading_store), &iter, READING_ENABLED, &active, -1); g_free(row_id); return active ? 1 : 0; } g_free(row_id); valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(reading_store), &iter); } return 0; }