205 lines
12 KiB
C
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;
|
|
}
|
|
|