Files

205 lines
12 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 *reading_store;
static const struct { const char *id; const char *label; const char *description; } reading_rows[] = {
{"only_speak_displayed_text", "Only speak displayed text",
"Limits reading to displayed names, text and values. Omits extra information such as control types, states and container announcements."},
{"automatically_read_title_changes", "Automatically read title changes",
"Announces changes to the active window's title, such as a new page title in a browser."},
{"automatically_read_clipboard_changes", "Automatically read clipboard changes",
"Automatically reads new text when the clipboard contents change, such as after copying text."},
{"phonetic_character_hints", "Phonetic character hints",
"When navigating characters with Left or Right, adds a phonetic name after a 300 ms pause, for example, a then alpha. Further input cancels the hint."},
{"enable_pause_breaks", "Break speech into chunks between pauses",
"Separates parts of an announcement with pauses, such as between a control's name, type and state."},
{"speak_numbers_as_digits", "Speak numbers as digits",
"Reads numbers one digit at a time, for example, 123 as one two three."},
{"speak_multicase_strings_as_words", "Speak multicase strings as words",
"Separates words where a lowercase letter is followed by an uppercase letter, for example, saveFile as save File."},
{"enable_indentation", "Enable indentation presentation",
"Reports changes in a line's leading spaces and tabs using the selected indentation presentation mode."},
{"speak_blank_lines", "Speak blank lines",
"Says blank when navigating to an empty line, instead of leaving it silent."},
{"enable_mnemonic_speaking", "Speak object mnemonics",
"Announces a control's access key when the application provides one, such as Alt+O for an Open button."},
{"enable_position_speaking", "Speak child position",
"Announces an item's position within a group when available, for example, 2 of 5."},
{"enable_tutorial_messages", "Speak tutorial messages",
"Adds brief instructions for using the focused control, such as Press Space to toggle for a checkbox."},
{"speak_description", "Speak object descriptions",
"Reads additional descriptive text supplied by the application for a control or window."},
{"messages_are_detailed", "System messages are detailed",
"Reads a notification's full message when available. When disabled, reads just its summary when one is provided."},
{"use_color_names", "Speak colors as names",
"Uses color names when reporting text colors, instead of their numeric color values."},
{"speak_misspelled_indicator", "Speak misspelled-word indicator",
"Says misspelled when reading text that the application's spell checker has marked as incorrect."},
{"speak_context_blockquote", "Announce block quotes during navigation",
"Announces when you enter or leave a block quote in browser browse mode."},
{"speak_context_list", "Announce lists during navigation",
"Announces when you enter or leave a list in browser browse mode."},
{"speak_context_table", "Announce tables during navigation",
"Announces when you enter or leave a data table in browser browse mode."},
{"speak_context_panel", "Announce panels during navigation",
"Announces when you enter or leave a panel or group of controls in browser browse mode."},
{"speak_context_landmark", "Announce landmarks during navigation",
"Announces when you enter or leave a landmark in browser browse mode, such as a page's main content or navigation region."},
{"speak_context_non_landmark_form", "Announce forms during navigation",
"Announces when you enter or leave a form that is not a landmark in browser browse mode."},
{"read_full_row_in_gui_table", "Speak full row in GUI tables",
"Reads all cells in a row when moving between rows in application tables, including this settings table. Left and Right read individual cells."},
{"read_full_row_in_document_table", "Speak full row in document tables",
"Reads the whole row when navigating rows in a document or web page table. Moving between columns reads individual cells."},
{"read_full_row_in_spreadsheet", "Speak full row in spreadsheets",
"Reads the whole row when navigating rows in a spreadsheet. Moving between columns reads individual cells."},
{"present_tooltips", "Present tooltips",
"Reads tooltip text when the application displays it."},
{"enable_mouse_review", "Speak object under mouse",
"Reads the object under the mouse pointer as you move the mouse."},
{"say_all_context_blockquote", "Announce block quotes in Say All",
"Announces entry into and exit from block quotes during continuous reading with Say All. This is separate from navigation announcements."},
{"say_all_context_list", "Announce lists in Say All",
"Announces entry into and exit from lists during continuous reading with Say All. This is separate from navigation announcements."},
{"say_all_context_table", "Announce tables in Say All",
"Announces entry into and exit from data tables during continuous reading with Say All. This is separate from navigation announcements."},
{"say_all_context_panel", "Announce panels in Say All",
"Announces entry into and exit from panels or groups of controls during continuous reading with Say All. This is separate from navigation announcements."},
{"say_all_context_non_landmark_form", "Announce forms in Say All",
"Announces entry into and exit from forms that are not landmarks during continuous reading with Say All. This is separate from navigation announcements."},
{"say_all_context_landmark", "Announce landmarks in Say All",
"Announces entry into and exit from landmarks, such as main content or navigation regions, during continuous reading with Say All. This is separate from navigation announcements."},
};
enum {
READING_ID,
READING_LABEL,
READING_ENABLED,
READING_DESCRIPTION,
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, G_TYPE_STRING);
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,
READING_DESCRIPTION, reading_rows[i].description,
-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);
GtkCellRenderer *descriptionRenderer = gtk_cell_renderer_text_new();
g_object_set(descriptionRenderer, "wrap-mode", PANGO_WRAP_WORD_CHAR, "wrap-width", 280, NULL);
GtkTreeViewColumn *descriptionColumn = gtk_tree_view_column_new_with_attributes(
"Description", descriptionRenderer, "text", READING_DESCRIPTION, NULL);
gtk_tree_view_column_set_expand(descriptionColumn, TRUE);
gtk_tree_view_append_column(view, descriptionColumn);
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;
}