Rework preferences window

This commit is contained in:
deepend committed 2026-10-24 15:38:01 -06:00
1 parent 81f4f591f5
commit 8dabbfe702
30 files changed
+3404 -1396

No files matched your search

+300
View File
@@ -41,6 +41,8 @@
#include "gtkutil.h"
#include "maingui.h"
#include "editlist.h"
#include "preferences-persistence.h"
#include "setup.h"
#define ICON_EDITLIST_NEW "document-new"
#define ICON_EDITLIST_DELETE "edit-delete"
@@ -373,3 +375,301 @@ editlist_gui_open (char *title1, char *title2, GSList *list, char *title, char *
gtk_widget_show (editlist_win);
}
/* Preferences editors are independent instances: several lists may be open
* at once, and their models must never share the legacy dialog globals. */
typedef struct
{
GtkWidget *view;
GtkWidget *delete_button;
GtkWidget *up_button;
GtkWidget *down_button;
GSList **target;
const char *file;
char *original;
char *pending;
} PreferencesList;
static GSList *preferences_lists;
static void
preferences_list_update_buttons (PreferencesList *editor)
{
GtkTreeModel *model;
GtkTreeIter iter, next;
GtkTreePath *path;
gboolean selected = gtk_tree_selection_get_selected (
gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter);
gboolean up = FALSE, down = FALSE;
if (selected)
{
path = gtk_tree_model_get_path (model, &iter);
up = gtk_tree_path_get_indices (path)[0] > 0;
gtk_tree_path_free (path);
next = iter;
down = gtk_tree_model_iter_next (model, &next);
}
gtk_widget_set_sensitive (editor->delete_button, selected);
gtk_widget_set_sensitive (editor->up_button, up);
gtk_widget_set_sensitive (editor->down_button, down);
}
static void
preferences_list_selection_changed (GtkTreeSelection *selection, PreferencesList *editor)
{
preferences_list_update_buttons (editor);
}
static void
preferences_list_rows_reordered (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
gint *order, PreferencesList *editor)
{
preferences_list_update_buttons (editor);
}
static char *
preferences_list_serialize (GSList *list)
{
GString *text = g_string_new ("");
for (; list; list = list->next)
{
struct popup *item = list->data;
g_string_append_printf (text, "NAME %s\nCMD %s\n\n", item->name, item->cmd);
}
return g_string_free (text, FALSE);
}
static void
preferences_list_free (gpointer data)
{
PreferencesList *editor = data;
preferences_lists = g_slist_remove (preferences_lists, editor);
g_free (editor->original);
g_free (editor->pending);
g_free (editor);
}
static void
preferences_list_edited (GtkCellRenderer *renderer, char *path, char *text, PreferencesList *editor)
{
GtkTreeIter iter;
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view));
if (gtk_tree_model_get_iter_from_string (model, &iter, path))
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), "column")), text, -1);
}
static void
preferences_list_add (GtkButton *button, PreferencesList *editor)
{
GtkTreeIter iter;
GtkTreePath *path;
GtkTreeView *view = GTK_TREE_VIEW (editor->view);
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
(void)button;
gtk_list_store_insert_with_values (store, &iter, -1, NAME_COLUMN, "", CMD_COLUMN, "", -1);
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
gtk_tree_view_set_cursor (view, path, gtk_tree_view_get_column (view, 0), TRUE);
gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
preferences_list_update_buttons (editor);
}
static void
preferences_list_delete (GtkButton *button, PreferencesList *editor)
{
GtkTreeIter iter;
GtkTreeModel *model;
(void)button;
if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter))
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
}
static void
preferences_list_move (GtkButton *button, PreferencesList *editor)
{
GtkTreeModel *model;
GtkTreeIter iter, other;
GtkTreePath *path;
gboolean valid;
if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter))
return;
path = gtk_tree_model_get_path (model, &iter);
if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "direction")) < 0)
valid = gtk_tree_path_prev (path);
else { gtk_tree_path_next (path); valid = TRUE; }
if (valid && gtk_tree_model_get_iter (model, &other, path))
{
gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &other);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (editor->view), path, NULL, FALSE, 0, 0);
}
gtk_tree_path_free (path);
}
static gboolean
preferences_list_key (GtkWidget *view, GdkEventKey *event, PreferencesList *editor)
{
GtkWidget *button;
int direction;
(void)view;
if (!(event->state & GDK_SHIFT_MASK) || (event->keyval != GDK_KEY_Up && event->keyval != GDK_KEY_Down))
return FALSE;
direction = event->keyval == GDK_KEY_Up ? -1 : 1;
button = gtk_button_new ();
g_object_ref_sink (button);
g_object_set_data (G_OBJECT (button), "direction", GINT_TO_POINTER (direction));
preferences_list_move (GTK_BUTTON (button), editor);
g_object_unref (button);
return TRUE;
}
GtkWidget *
editlist_preferences_create (GSList **list, const char *file, const char *title1,
const char *title2, const char *help)
{
PreferencesList *editor = g_new0 (PreferencesList, 1);
GtkWidget *box, *scroll, *buttons, *button;
GtkListStore *store;
GtkCellRenderer *renderer;
int column;
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
editor->target = list;
editor->file = file;
editor->original = preferences_list_serialize (*list);
preferences_lists = g_slist_append (preferences_lists, editor);
g_object_set_data_full (G_OBJECT (box), "preferences-list", editor, preferences_list_free);
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
editor->view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (editor->view), TRUE);
gtk_tree_view_set_search_column (GTK_TREE_VIEW (editor->view), NAME_COLUMN);
for (column = 0; column < N_COLUMNS; column++)
{
GtkTreeViewColumn *col;
renderer = gtk_cell_renderer_text_new ();
g_object_set (renderer, "editable", TRUE, NULL);
g_object_set_data (G_OBJECT (renderer), "column", GINT_TO_POINTER (column));
g_signal_connect (renderer, "edited", G_CALLBACK (preferences_list_edited), editor);
col = gtk_tree_view_column_new_with_attributes (column ? title2 : title1, renderer, "text", column, NULL);
gtk_tree_view_column_set_resizable (col, TRUE);
gtk_tree_view_column_set_min_width (col, column ? 240 : 140);
gtk_tree_view_column_set_expand (col, column != 0);
gtk_tree_view_append_column (GTK_TREE_VIEW (editor->view), col);
}
editlist_load (store, *list);
g_object_unref (store);
g_signal_connect (editor->view, "key-press-event", G_CALLBACK (preferences_list_key), editor);
scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scroll), 220);
gtk_container_add (GTK_CONTAINER (scroll), editor->view);
gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0);
buttons = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
gtk_box_pack_start (GTK_BOX (box), buttons, FALSE, FALSE, 0);
button = gtk_button_new_with_mnemonic (_("_Add"));
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_add), editor);
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
button = gtk_button_new_with_mnemonic (_("_Delete"));
editor->delete_button = button;
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_delete), editor);
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
for (column = -1; column <= 1; column += 2)
{
button = gtk_button_new_with_label (column < 0 ? _("Move up") : _("Move down"));
if (column < 0) editor->up_button = button;
else editor->down_button = button;
g_object_set_data (G_OBJECT (button), "direction", GINT_TO_POINTER (column));
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_move), editor);
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
}
g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), "changed",
G_CALLBACK (preferences_list_selection_changed), editor);
g_signal_connect (gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view)), "rows-reordered",
G_CALLBACK (preferences_list_rows_reordered), editor);
preferences_list_update_buttons (editor);
if (help)
{
GtkWidget *expander = gtk_expander_new (_("Formatting codes and examples"));
GtkWidget *label = gtk_label_new (help);
gtk_label_set_xalign (GTK_LABEL (label), 0);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_label_set_selectable (GTK_LABEL (label), TRUE);
gtk_label_set_max_width_chars (GTK_LABEL (label), 60);
gtk_widget_set_margin_top (label, 10);
gtk_container_add (GTK_CONTAINER (expander), label);
gtk_box_pack_start (GTK_BOX (box), expander, FALSE, FALSE, 0);
}
return box;
}
gboolean
editlist_preferences_stage (GError **error)
{
GSList *entry;
for (entry = preferences_lists; entry; entry = entry->next)
{
PreferencesList *editor = entry->data;
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view));
GtkTreeIter iter;
GString *text = g_string_new ("");
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
char *live;
g_clear_pointer (&editor->pending, g_free);
while (valid)
{
char *name, *cmd;
gtk_tree_model_get (model, &iter, NAME_COLUMN, &name, CMD_COLUMN, &cmd, -1);
if ((name && strpbrk (name, "\r\n")) || (cmd && strpbrk (cmd, "\r\n")) ||
((!name || !*name) && cmd && *cmd))
{
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
int column = !name || !*name || strpbrk (name, "\r\n") ? NAME_COLUMN : CMD_COLUMN;
setup_reveal_widget (editor->view);
gtk_widget_grab_focus (editor->view);
gtk_tree_view_set_cursor (GTK_TREE_VIEW (editor->view), path,
gtk_tree_view_get_column (GTK_TREE_VIEW (editor->view), column), TRUE);
gtk_tree_path_free (path);
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
_("Give this entry a name and keep each field on one line."));
g_free (name); g_free (cmd); g_string_free (text, TRUE);
return FALSE;
}
if (name && *name)
g_string_append_printf (text, "NAME %s\nCMD %s\n\n", name, cmd ? cmd : "");
g_free (name); g_free (cmd);
valid = gtk_tree_model_iter_next (model, &iter);
}
if (strcmp (text->str, editor->original) == 0) { g_string_free (text, TRUE); continue; }
live = preferences_list_serialize (*editor->target);
if (strcmp (live, editor->original) != 0)
{
setup_reveal_widget (editor->view);
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_AGAIN,
_("These entries changed outside Preferences. Reopen Preferences before editing them."));
g_free (live); g_string_free (text, TRUE);
return FALSE;
}
g_free (live);
editor->pending = g_string_free (text, FALSE);
preferences_persistence_stage_file (editor->file, editor->pending);
}
return TRUE;
}
void
editlist_preferences_commit (void)
{
GSList *entry, *sessions;
for (entry = preferences_lists; entry; entry = entry->next)
{
PreferencesList *editor = entry->data;
if (!editor->pending) continue;
list_free (editor->target);
list_loadconf ((char *)editor->file, editor->target, NULL);
if (editor->target == &usermenu_list) usermenu_update ();
for (sessions = sess_list; sessions; sessions = sessions->next)
{
if (editor->target == &button_list) fe_buttons_update (sessions->data);
if (editor->target == &dlgbutton_list) fe_dlgbuttons_update (sessions->data);
}
}
}
+5
View File
@@ -21,5 +21,10 @@
#define ZOITECHAT_EDITLIST_H
void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help);
GtkWidget *editlist_preferences_create (GSList **list, const char *file,
const char *title1, const char *title2,
const char *help);
gboolean editlist_preferences_stage (GError **error);
void editlist_preferences_commit (void);
#endif
+98 -125
View File
@@ -58,6 +58,8 @@
#ifdef __APPLE__
#include "macos-shortcuts.h"
#endif
#include "setup.h"
#include "preferences-persistence.h"
#define ICON_FKEYS_NEW "document-new"
#define ICON_FKEYS_DELETE "edit-delete"
@@ -110,7 +112,7 @@ struct gcomp_data
static int key_load_kbs (void);
static int key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list);
static int key_save_kbs (void);
static void key_dialog_load (GtkListStore *store);
static void key_dialog_reset (GtkWidget *wid, gpointer userdata);
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
@@ -814,75 +816,6 @@ key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
key_dialog_print_text (xtext, _("Select a row to get help information on its Action."));
}
static void
key_dialog_close (GtkWidget *wid, gpointer userdata)
{
gtk_widget_destroy (key_dialog);
key_dialog = NULL;
}
static void
key_dialog_save (GtkWidget *wid, gpointer userdata)
{
GtkTreeModel *store = get_store ();
GtkTreeIter iter;
struct key_binding *kb;
char *data1, *data2, *accel, *actiontext;
guint keyval;
GdkModifierType mod;
if (keybind_list)
{
g_slist_free_full (keybind_list, key_free);
keybind_list = NULL;
}
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
{
do
{
kb = g_new0 (struct key_binding, 1);
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ACCEL_COLUMN, &accel,
ACTION_COLUMN, &actiontext,
D1_COLUMN, &data1,
D2_COLUMN, &data2,
-1);
kb->data1 = data1;
kb->data2 = data2;
if (accel)
{
gtk_accelerator_parse (accel, &keyval, &mod);
kb->keyval = keyval;
kb->mod = key_modifier_get_valid (mod);
g_free (accel);
}
if (actiontext)
{
kb->action = key_get_action_from_string (actiontext);
g_free (actiontext);
}
if (!accel || !actiontext)
key_free (kb);
else
keybind_list = g_slist_append (keybind_list, kb);
}
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
}
if (key_save_kbs () == 0)
{
menu_update_quit_accel ();
key_dialog_close (wid, NULL);
}
}
static void
key_dialog_add (GtkWidget *wid, gpointer userdata)
{
@@ -1227,25 +1160,25 @@ key_dialog_load (GtkListStore *store)
}
}
void
key_dialog_show (void)
static char *key_preferences_serialize (GtkTreeModel *model, GError **error);
static void
key_preferences_preview_realize (GtkWidget *widget, gpointer data)
{
gtk_xtext_set_font (GTK_XTEXT (widget), prefs.hex_text_font);
}
GtkWidget *
key_preferences_create (void)
{
GtkWidget *vbox, *box, *pane, *scroll;
GtkWidget *view, *xtext, *delete_button;
GtkListStore *store;
XTextColor xtext_palette[XTEXT_COLS];
char buf[128];
if (key_dialog)
{
mg_bring_tofront (key_dialog);
return;
}
g_snprintf(buf, sizeof(buf), _("Keyboard Shortcuts - %s"), _(DISPLAY_NAME));
key_dialog = mg_create_generic_tab ("editkeys", buf, TRUE, FALSE, key_dialog_close,
NULL, 600, 360, &vbox, 0);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
key_dialog = vbox;
g_object_add_weak_pointer (G_OBJECT (vbox), (gpointer *)&key_dialog);
pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
box = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
@@ -1260,7 +1193,7 @@ key_dialog_show (void)
theme_get_xtext_colors (xtext_palette, XTEXT_COLS);
xtext = gtk_xtext_new (xtext_palette, 0);
gtk_container_add (GTK_CONTAINER (scroll), xtext);
gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font);
g_signal_connect (xtext, "realize", G_CALLBACK (key_preferences_preview_realize), NULL);
g_object_set_data (G_OBJECT (key_dialog), "view", view);
g_object_set_data (G_OBJECT (key_dialog), "xtext", xtext);
@@ -1281,59 +1214,99 @@ key_dialog_show (void)
gtk_widget_set_sensitive (delete_button, FALSE);
gtkutil_button (box, ICON_FKEYS_RESET, NULL, key_dialog_reset,
NULL, _("Reset"));
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
NULL, _("Cancel"));
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
NULL, _("Save"));
store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
key_dialog_load (store);
g_object_set_data_full (G_OBJECT (vbox), "original-bindings", key_preferences_serialize (GTK_TREE_MODEL (store), NULL), g_free);
gtk_widget_show_all (key_dialog);
return vbox;
}
static int
key_save_kbs (void)
void
key_dialog_show (void)
{
int fd;
char buf[512];
char *accel_text;
GSList *list = keybind_list;
struct key_binding *kb;
setup_open_page ("keyboard");
}
fd = zoitechat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
0x180, XOF_DOMODE);
if (fd < 0)
return 1;
write (fd, buf, g_snprintf (buf, 510, "# ZoiteChat key bindings config file\n\n"));
while (list)
static char *
key_preferences_serialize (GtkTreeModel *model, GError **error)
{
GtkTreeIter iter;
GString *text = g_string_new ("# ZoiteChat key bindings config file\n\n");
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
while (valid)
{
kb = list->data;
accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
g_snprintf (buf, 510, "ACCEL=%s\n%s\n", accel_text, key_actions[kb->action].name);
write (fd, buf, strlen (buf));
g_free (accel_text);
if (kb->data1 && kb->data1[0])
write (fd, buf, g_snprintf (buf, 510, "D1:%s\n", kb->data1));
else
write (fd, "D1!\n", 4);
if (kb->data2 && kb->data2[0])
write (fd, buf, g_snprintf (buf, 510, "D2:%s\n", kb->data2));
else
write (fd, "D2!\n", 4);
write (fd, "\n", 1);
list = g_slist_next (list);
char *accel, *action, *data1, *data2;
guint key;
GdkModifierType modifiers;
gtk_tree_model_get (model, &iter, ACCEL_COLUMN, &accel, ACTION_COLUMN, &action,
D1_COLUMN, &data1, D2_COLUMN, &data2, -1);
if ((!accel || !*accel) && (!action || !*action) && (!data1 || !*data1) && (!data2 || !*data2))
goto next;
gtk_accelerator_parse (accel ? accel : "", &key, &modifiers);
if (!key || !action || strcmp (key_actions[key_get_action_from_string (action)].name, action) != 0 ||
(data1 && strpbrk (data1, "\r\n")) || (data2 && strpbrk (data2, "\r\n")))
{
GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
setup_reveal_widget (GTK_WIDGET (view));
gtk_widget_grab_focus (GTK_WIDGET (view));
gtk_tree_view_set_cursor (view, path, NULL, FALSE);
gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
_("Complete each keyboard shortcut with a key and an action. Data fields must stay on one line."));
g_free (accel); g_free (action); g_free (data1); g_free (data2);
g_string_free (text, TRUE);
return NULL;
}
g_string_append_printf (text, "ACCEL=%s\n%s\n", accel, action);
if (data1 && *data1) g_string_append_printf (text, "D1:%s\n", data1);
else g_string_append (text, "D1!\n");
if (data2 && *data2) g_string_append_printf (text, "D2:%s\n", data2);
else g_string_append (text, "D2!\n");
g_string_append_c (text, '\n');
next:
g_free (accel); g_free (action); g_free (data1); g_free (data2);
valid = gtk_tree_model_iter_next (model, &iter);
}
return g_string_free (text, FALSE);
}
close (fd);
return 0;
gboolean
key_preferences_stage (GError **error)
{
char *contents;
const char *original;
if (!key_dialog) return TRUE;
contents = key_preferences_serialize (get_store (), error);
if (!contents) return FALSE;
original = g_object_get_data (G_OBJECT (key_dialog), "original-bindings");
g_object_set_data_full (G_OBJECT (key_dialog), "pending-bindings", NULL, g_free);
if (g_strcmp0 (contents, original) != 0)
{
preferences_persistence_stage_file ("keybindings.conf", contents);
g_object_set_data_full (G_OBJECT (key_dialog), "pending-bindings", g_strdup (contents), g_free);
}
g_free (contents);
return TRUE;
}
void
key_preferences_commit (void)
{
GSList *bindings = NULL;
const char *contents;
if (!key_dialog) return;
contents = g_object_get_data (G_OBJECT (key_dialog), "pending-bindings");
if (!contents) return;
if (key_load_kbs_from_buffer (g_strdup (contents), strlen (contents), &bindings) == 0)
{
g_slist_free_full (keybind_list, key_free);
keybind_list = bindings;
menu_update_quit_accel ();
}
}
#define KBSTATE_MOD 0
+3
View File
@@ -31,6 +31,9 @@
void key_init (void);
void key_dialog_show (void);
GtkWidget *key_preferences_create (void);
gboolean key_preferences_stage (GError **error);
void key_preferences_commit (void);
int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
session *sess);
+22 -18
View File
@@ -31,6 +31,7 @@
#include "../common/fe.h"
#include "gtkutil.h"
#include "maingui.h"
#include "setup.h"
#define ICON_IGNORE_NEW "document-new"
#define ICON_IGNORE_DELETE "edit-delete"
@@ -234,6 +235,8 @@ ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess)
static void
ignore_store_new (int cancel, char *mask, gpointer data)
{
if (cancel || !ignorewin)
return;
GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
GtkListStore *store = GTK_LIST_STORE (get_store ());
GtkTreeIter iter;
@@ -265,6 +268,11 @@ ignore_store_new (int cancel, char *mask, gpointer data)
static void
ignore_clear_cb (GtkDialog *dialog, gint response)
{
if (!ignorewin)
{
gtk_widget_destroy (GTK_WIDGET (dialog));
return;
}
GtkListStore *store = GTK_LIST_STORE (get_store ());
GtkTreeIter iter;
char *mask;
@@ -293,7 +301,7 @@ ignore_clear_entry_clicked (GtkWidget * wid)
{
GtkWidget *dialog;
dialog = gtk_message_dialog_new (NULL, 0,
dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (ignorewin)), GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
_("Are you sure you want to remove all ignores?"));
theme_manager_attach_window (dialog);
@@ -335,8 +343,8 @@ ignore_stats_entry (GtkWidget * box, char *label, int value)
return wid;
}
void
ignore_gui_open (void)
GtkWidget *
ignore_preferences_create (void)
{
GtkWidget *vbox, *box, *stat_box, *frame;
GtkWidget *view;
@@ -345,20 +353,9 @@ ignore_gui_open (void)
GSList *temp = ignore_list;
char *mask;
gboolean private, chan, notice, ctcp, dcc, invite, unignore;
char buf[128];
if (ignorewin)
{
mg_bring_tofront (ignorewin);
return;
}
g_snprintf(buf, sizeof(buf), _("Ignore list - %s"), _(DISPLAY_NAME));
ignorewin =
mg_create_generic_tab ("IgnoreList", buf, FALSE, TRUE,
close_ignore_gui_callback,
NULL, 700, 300, &vbox, 0);
gtkutil_destroy_on_esc (ignorewin);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
ignorewin = vbox;
g_signal_connect (vbox, "destroy", G_CALLBACK (close_ignore_gui_callback), NULL);
view = ignore_treeview_new (vbox);
g_object_set_data (G_OBJECT (ignorewin), "view", view);
@@ -421,7 +418,14 @@ ignore_gui_open (void)
temp = temp->next;
}
gtk_widget_show (ignorewin);
gtk_widget_show_all (ignorewin);
return vbox;
}
void
ignore_gui_open (void)
{
setup_open_page ("ignore");
}
void
+71 -167
View File
@@ -50,6 +50,7 @@
#include "banlist.h"
#include "chanlist.h"
#include "editlist.h"
#include "setup.h"
#include "fkeys.h"
#ifdef __APPLE__
#include "macos-shortcuts.h"
@@ -1321,7 +1322,6 @@ menu_open_server_list (GtkWidget *wid, gpointer none)
static void
menu_settings (GtkWidget * wid, gpointer none)
{
extern void setup_open (void);
setup_open ();
}
@@ -1336,20 +1336,10 @@ menu_macos_minimize (GtkWidget *widget, gpointer userdata)
}
#endif
static void
menu_usermenu (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("User menu - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, usermenu_list, buf, "usermenu", "usermenu.conf", 0);
}
static void
usermenu_create (GtkWidget *menu)
{
menu_create (menu, usermenu_list, "", FALSE);
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); /* sep */
menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu" ELLIPSIS), menu, 0);
}
static void
@@ -1658,33 +1648,6 @@ menu_banlist (GtkWidget * wid, gpointer none)
banlist_opengui (current_sess);
}
#ifdef USE_PLUGIN
static void
menu_loadplugin (void)
{
plugingui_load ();
}
static void
menu_pluginlist (void)
{
plugingui_open ();
}
#else
static void
menu_noplugin_info (void)
{
fe_message (_(DISPLAY_NAME " has been build without plugin support."), FE_MSG_INFO);
}
#define menu_loadplugin menu_noplugin_info
#define menu_pluginlist menu_noplugin_info
#endif
#define usercommands_help _("User Commands - Special codes:\n\n"\
"%c = current channel\n"\
"%e = current network name\n"\
@@ -1741,73 +1704,47 @@ menu_noplugin_info (void)
"shell instead of ZoiteChat")
static void
menu_usercommands (void)
menu_preferences_selector_changed (GtkComboBox *combo, GtkStack *stack)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("User Defined Commands - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, command_list, buf, "commands", "commands.conf",
usercommands_help);
const char *id = gtk_combo_box_get_active_id (combo);
if (id) gtk_stack_set_visible_child_name (stack, id);
}
static void
menu_ulpopup (void)
GtkWidget *
menu_preferences_create (const char *id)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("Userlist Popup menu - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, popup_list, buf, "popup", "popup.conf", ulbutton_help);
}
static void
menu_rpopup (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("Replace - %s"), _(DISPLAY_NAME));
editlist_gui_open (_("Text"), _("Replace with"), replace_list, buf, "replace", "replace.conf", 0);
}
static void
menu_urlhandlers (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("URL Handlers - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, urlhandler_list, buf, "urlhandlers", "urlhandlers.conf", url_help);
}
static void
menu_evtpopup (void)
{
pevent_dialog_show ();
}
static void
menu_keypopup (void)
{
key_dialog_show ();
}
static void
menu_ulbuttons (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("Userlist buttons - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, button_list, buf, "buttons", "buttons.conf", ulbutton_help);
}
static void
menu_dlgbuttons (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("Dialog buttons - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, dlgbutton_list, buf, "dlgbuttons", "dlgbuttons.conf",
dlgbutton_help);
}
static void
menu_ctcpguiopen (void)
{
char buf[128];
g_snprintf(buf, sizeof(buf), _("CTCP Replies - %s"), _(DISPLAY_NAME));
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
if (!strcmp (id, "replace"))
return editlist_preferences_create (&replace_list, "replace.conf", _("Text"), _("Replace with"), NULL);
if (!strcmp (id, "commands"))
return editlist_preferences_create (&command_list, "commands.conf", _("Command"), _("Expansion"), usercommands_help);
if (!strcmp (id, "urls"))
return editlist_preferences_create (&urlhandler_list, "urlhandlers.conf", _("Name"), _("Command"), url_help);
if (!strcmp (id, "ctcp"))
return editlist_preferences_create (&ctcp_list, "ctcpreply.conf", _("Request"), _("Reply"), ctcp_help);
{
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
GtkWidget *selector = gtk_combo_box_text_new ();
GtkWidget *stack = gtk_stack_new ();
const char *ids[] = {"buttons", "popup", "dlgbuttons", "usermenu"};
const char *names[] = {N_("User list buttons"), N_("User list popup"), N_("Dialog buttons"), N_("User menu")};
const char *files[] = {"buttons.conf", "popup.conf", "dlgbuttons.conf", "usermenu.conf"};
GSList **lists[] = {&button_list, &popup_list, &dlgbutton_list, &usermenu_list};
const char *help[] = {ulbutton_help, ulbutton_help, dlgbutton_help, ulbutton_help};
int i;
gtk_box_pack_start (GTK_BOX (box), selector, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (box), stack, TRUE, TRUE, 0);
gtk_stack_set_homogeneous (GTK_STACK (stack), FALSE);
for (i = 0; i < 4; i++)
{
gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (selector), ids[i], _(names[i]));
gtk_stack_add_named (GTK_STACK (stack), editlist_preferences_create (lists[i], files[i], _("Name"), _("Command"), help[i]), ids[i]);
}
g_signal_connect (selector, "changed", G_CALLBACK (menu_preferences_selector_changed), stack);
gtk_combo_box_set_active (GTK_COMBO_BOX (selector), 0);
g_object_set_data (G_OBJECT (box), "menus-selector", selector);
g_object_set_data (G_OBJECT (stack), "preferences-selector", selector);
return box;
}
}
static void
@@ -2000,7 +1937,7 @@ menu_about (GtkWidget *wid, gpointer sess)
static struct mymenu mymenu[] = {
{N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1},
{N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
{N_("Network Li_st" ELLIPSIS), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1},
@@ -2011,14 +1948,14 @@ static struct mymenu mymenu[] = {
{0, 0, 0, M_END, 0, 0, 0},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("_Load Plugin or Script" ELLIPSIS), menu_loadplugin, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0}, /* 11 */
#define DETACH_OFFSET (12)
{0, menu_detach, 0, M_MENUITEM, 0, 0, 1}, /* 12 */
#define CLOSE_OFFSET (13)
#define DETACH_OFFSET (10)
{0, menu_detach, 0, M_MENUITEM, 0, 0, 1},
#define CLOSE_OFFSET (11)
{0, menu_close, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1}, /* 15 */
{N_("_Preferences" ELLIPSIS), menu_settings, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1},
{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
#define MENUBAR_OFFSET (17)
@@ -2028,18 +1965,18 @@ static struct mymenu mymenu[] = {
{N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 23 */
{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1},
#define TABS_OFFSET (24)
{N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1},
{N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1},
{0, 0, 0, M_END, 0, 0, 0},
{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 27 */
{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1},
#define METRE_OFFSET (28)
{N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1},
{N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1},
{N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1},
{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0}, /* 32 */
{0, 0, 0, M_END, 0, 0, 0},
{ 0, 0, 0, M_SEP, 0, 0, 0 },
{N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1},
@@ -2052,20 +1989,7 @@ static struct mymenu mymenu[] = {
#define AWAY_OFFSET (41)
{N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1},
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */
{N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1},
{N_("_Preferences"), menu_settings, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("Auto Replace"), menu_rpopup, 0, M_MENUITEM, 0, 0, 1},
{N_("CTCP Replies"), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1},
{N_("Dialog Buttons"), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1},
{N_("Keyboard Shortcuts"), menu_keypopup, 0, M_MENUITEM, 0, 0, 1},
{N_("Text Events"), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1},
{N_("URL Handlers"), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1},
{N_("User Commands"), menu_usercommands, 0, M_MENUITEM, 0, 0, 1},
{N_("User List Buttons"), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1},
{N_("User List Popup"), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1}, /* 52 */
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1},
{N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1},
{N_("_Ban List"), menu_banlist, 0, M_MENUITEM, 0, 0, 1},
@@ -2073,9 +1997,7 @@ static struct mymenu mymenu[] = {
{N_("Direct Chat"), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1},
{N_("File _Transfers"), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1},
{N_("Friends List"), notify_opengui, 0, M_MENUITEM, 0, 0, 1},
{N_("Ignore List"), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1},
{N_("_Plugins and Scripts"), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1},
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1},
{N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_SEP, 0, 0, 0},
{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1},
@@ -2083,14 +2005,14 @@ static struct mymenu mymenu[] = {
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
#define SEARCH_OFFSET (71)
#define SEARCH_OFFSET (57)
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
{N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1},
{0, 0, 0, M_END, 0, 0, 0},
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},
{N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1},
{N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1},
@@ -2100,42 +2022,24 @@ static struct mymenu mymenu[] = {
static const char *
menu_get_key_action_name (int index)
{
switch (index)
{
case 1:
return "network-list";
case 4:
return "new-server-tab";
case 6:
return "new-server-window";
case CLOSE_OFFSET:
return "close";
case 15:
return "quit";
case MENUBAR_OFFSET:
return "menu-toggle";
case MENUBAR_OFFSET + 2:
return "user-list-toggle";
case 34:
return "fullscreen-toggle";
case AWAY_OFFSET:
return "away-toggle";
case 66:
return "reset-marker";
case 67:
return "move-marker";
case 68:
return "copy-selection";
case SEARCH_OFFSET + 1:
return "search-text";
case SEARCH_OFFSET + 2:
return "search-next";
case SEARCH_OFFSET + 3:
return "search-previous";
case 77:
return "contents";
}
/* Bind accelerators to actions rather than positions in the menu. */
void *callback = mymenu[index].callback;
if (callback == menu_open_server_list) return "network-list";
if (callback == menu_newserver_tab) return "new-server-tab";
if (callback == menu_newserver_window) return "new-server-window";
if (callback == menu_close) return "close";
if (callback == menu_quit) return "quit";
if (callback == menu_bar_toggle_cb) return "menu-toggle";
if (callback == menu_userlist_toggle) return "user-list-toggle";
if (callback == menu_fullscreen_toggle) return "fullscreen-toggle";
if (callback == menu_away_toggle) return "away-toggle";
if (callback == menu_resetmarker) return "reset-marker";
if (callback == menu_movetomarker) return "move-marker";
if (callback == menu_copy_selection) return "copy-selection";
if (callback == menu_search) return "search-text";
if (callback == menu_search_next) return "search-next";
if (callback == menu_search_prev) return "search-previous";
if (callback == menu_docs) return "contents";
return NULL;
}
@@ -3008,7 +2912,7 @@ togitem:
#ifdef HAVE_GTK_MAC
/* We want ZoiteChat to be the app menu, not including Quit or ZoiteChat itself */
if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT)
if (bar && item && (i <= CLOSE_OFFSET + 1 || mymenu[i].callback == menu_settings) && mymenu[i].id != MENU_ID_ZOITECHAT)
{
if (!submenu || mymenu[i].type == M_MENUSUB)
gtkosx_application_insert_app_menu_item (osx_app, item, appmenu_offset++);
+1
View File
@@ -42,6 +42,7 @@ void menu_bar_toggle (void);
void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
void menu_change_layout (void);
void menu_update_quit_accel (void);
GtkWidget *menu_preferences_create (const char *id);
gboolean menu_key_action (const char *name, guint keyval, GdkModifierType state);
void menu_set_away (session_gui *gui, int away);
+14 -2
View File
@@ -62,7 +62,8 @@ if host_machine.system() == 'darwin' and gtk_mac_dep.found()
gtk_dep = declare_dependency(dependencies: [gtk_dep, gtk_mac_dep])
endif
zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep]
theme_math_dep = cc.find_library('m', required: false)
zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep, theme_math_dep]
if host_machine.system() != 'windows'
appindicator_opt = get_option('appindicator')
@@ -243,6 +244,17 @@ theme_runtime_tests = executable('theme_runtime_persistence_tests',
link_with: zoitechat_theme_lib,
)
preferences_persistence_tests = executable('preferences_persistence_tests',
'tests/test-preferences-persistence.c',
include_directories: [config_h_include],
dependencies: [gtk_dep],
)
test('Preferences Persistence Tests', preferences_persistence_tests,
protocol: 'tap',
timeout: 120,
)
test('Theme Runtime Persistence Tests', theme_runtime_tests,
protocol: 'tap',
timeout: 120,
@@ -277,7 +289,7 @@ test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
'theme/tests/test-theme-preferences-gtk3-populate.c',
include_directories: [config_h_include],
dependencies: [gtk_dep],
dependencies: [gtk_dep, theme_math_dep],
)
test('Theme Preferences GTK3 Populate Tests', theme_preferences_gtk3_populate_tests,
+14 -14
View File
@@ -35,6 +35,8 @@ typedef struct session zoitechat_context;
#include "../common/cfgfiles.h"
#include "gtkutil.h"
#include "maingui.h"
#include "setup.h"
#include "plugingui.h"
/* model for the plugin treeview */
enum
@@ -357,24 +359,15 @@ plugingui_reloadbutton_cb (GtkWidget *wid, GtkTreeView *view)
}
}
void
plugingui_open (void)
GtkWidget *
plugingui_preferences_create (void)
{
GtkWidget *view;
GtkWidget *view_scroll;
GtkWidget *vbox, *hbox;
char buf[128];
if (plugin_window)
{
mg_bring_tofront (plugin_window);
return;
}
g_snprintf(buf, sizeof(buf), _("Plugins and Scripts - %s"), _(DISPLAY_NAME));
plugin_window = mg_create_generic_tab ("Addons", buf, FALSE, TRUE, plugingui_close, NULL,
700, 300, &vbox, 0);
gtkutil_destroy_on_esc (plugin_window);
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
plugin_window = vbox;
g_signal_connect (vbox, "destroy", G_CALLBACK (plugingui_close), NULL);
view = plugingui_treeview_new (vbox);
view_scroll = gtk_widget_get_parent (view);
@@ -404,4 +397,11 @@ plugingui_open (void)
fe_pluginlist_update ();
gtk_widget_show_all (plugin_window);
return vbox;
}
void
plugingui_open (void)
{
setup_open_page ("plugins");
}
+1
View File
@@ -21,6 +21,7 @@
#define ZOITECHAT_PLUGINGUI_H
void plugingui_open (void);
GtkWidget *plugingui_preferences_create (void);
void plugingui_load (void);
#endif
+219 -56
View File
@@ -23,64 +23,227 @@
#include "../common/cfgfiles.h"
#include "theme/theme-runtime.h"
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <glib/gstdio.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
typedef struct
{
char *name;
char *contents;
char *temporary;
char *backup;
gboolean existed;
gboolean installed;
} PreferencesFile;
static GPtrArray *staged_files;
static void
preferences_file_free (gpointer data)
{
PreferencesFile *file = data;
if (file->temporary) g_unlink (file->temporary);
if (file->backup) g_unlink (file->backup);
g_free (file->name);
g_free (file->contents);
g_free (file->temporary);
g_free (file->backup);
g_free (file);
}
void
preferences_persistence_clear_staged_files (void)
{
if (staged_files) g_ptr_array_free (staged_files, TRUE);
staged_files = NULL;
}
void
preferences_persistence_stage_file (const char *name, const char *contents)
{
PreferencesFile *file;
g_return_if_fail (name && contents && !strchr (name, '/') && !strchr (name, '\\'));
if (!staged_files)
staged_files = g_ptr_array_new_with_free_func (preferences_file_free);
file = g_new0 (PreferencesFile, 1);
file->name = g_strdup (name);
file->contents = g_strdup (contents);
g_ptr_array_add (staged_files, file);
}
/* A temporary in the configuration directory stays on the same filesystem.
* Restrictive permissions also protect command text and identity settings. */
static gboolean
preferences_write_temporary (const char *contents, gsize length, char **path)
{
int fd;
gsize written = 0;
char *temporary = g_build_filename (get_xdir (), ".preferences-XXXXXX", NULL);
fd = g_mkstemp (temporary);
if (fd < 0) { g_free (temporary); return FALSE; }
while (written < length)
{
gssize count = write (fd, contents + written, length - written);
if (count < 0 && errno == EINTR) continue;
if (count <= 0)
{
close (fd);
g_unlink (temporary);
g_free (temporary);
return FALSE;
}
written += count;
}
if (close (fd) != 0)
{
g_unlink (temporary);
g_free (temporary);
return FALSE;
}
*path = temporary;
return TRUE;
}
static gboolean
preferences_backup (PreferencesFile *file)
{
char *path = g_build_filename (get_xdir (), file->name, NULL);
char *contents = NULL;
gsize length;
GError *error = NULL;
gboolean success;
if (!g_file_get_contents (path, &contents, &length, &error))
{
success = g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
g_clear_error (&error);
g_free (path);
return success;
}
file->existed = TRUE;
success = preferences_write_temporary (contents, length, &file->backup);
g_free (contents);
g_free (path);
return success;
}
static gboolean
preferences_install (PreferencesFile *file, const char *temporary)
{
char *path = g_build_filename (get_xdir (), file->name, NULL);
gboolean success;
#ifdef WIN32
/* Match the existing config/theme save convention on Windows. The
* backup remains available if replacement fails after unlinking. */
if (file->existed && g_unlink (path) != 0)
{
g_free (path);
return FALSE;
}
file->installed = TRUE;
#endif
success = g_rename (temporary, path) == 0;
if (success) file->installed = TRUE;
g_free (path);
return success;
}
static gboolean
preferences_restore (PreferencesFile *file)
{
char *path;
gboolean success;
if (!file->installed) return TRUE;
path = g_build_filename (get_xdir (), file->name, NULL);
if (file->existed)
{
#ifdef WIN32
g_unlink (path);
#endif
success = g_rename (file->backup, path) == 0;
/* Retain the recovery copy if restoration itself fails. */
if (!success) g_clear_pointer (&file->backup, g_free);
}
else success = g_unlink (path) == 0 || errno == ENOENT;
g_free (path);
return success;
}
PreferencesPersistenceResult
preferences_persistence_save_all (void)
{
PreferencesPersistenceResult result;
char *config_temp;
char *theme_temp;
result.success = FALSE;
result.retry_possible = TRUE;
result.partial_failure = FALSE;
result.config_failed = FALSE;
result.theme_failed = FALSE;
result.failed_file = NULL;
config_temp = NULL;
theme_temp = NULL;
if (!save_config_prepare (&config_temp))
{
result.config_failed = TRUE;
goto done;
}
if (!theme_runtime_save_prepare (&theme_temp))
{
result.theme_failed = TRUE;
goto done;
}
if (!save_config_finalize (config_temp))
{
result.config_failed = TRUE;
goto done;
}
if (!theme_runtime_save_finalize (theme_temp))
{
result.theme_failed = TRUE;
result.partial_failure = TRUE;
goto done;
}
result.success = TRUE;
PreferencesPersistenceResult result = {0};
GPtrArray *files = g_ptr_array_new_with_free_func (preferences_file_free);
PreferencesFile *config = g_new0 (PreferencesFile, 1);
PreferencesFile *theme = g_new0 (PreferencesFile, 1);
guint i;
result.retry_possible = TRUE;
config->name = g_strdup ("zoitechat.conf");
theme->name = g_strdup ("colors.conf");
g_ptr_array_add (files, config);
g_ptr_array_add (files, theme);
if (!save_config_prepare (&config->temporary))
{
result.config_failed = TRUE;
result.failed_file = "zoitechat.conf";
goto done;
}
if (!theme_runtime_save_prepare (&theme->temporary))
{
result.theme_failed = TRUE;
result.failed_file = "colors.conf";
goto done;
}
for (i = 0; staged_files && i < staged_files->len; i++)
{
PreferencesFile *queued = g_ptr_array_index (staged_files, i);
PreferencesFile *file = g_new0 (PreferencesFile, 1);
file->name = g_strdup (queued->name);
g_ptr_array_add (files, file);
if (!preferences_write_temporary (queued->contents, strlen (queued->contents), &file->temporary))
{
result.failed_file = queued->name;
goto done;
}
}
/* Prepare every file and recovery copy before replacing anything. */
for (i = 0; i < files->len; i++)
{
PreferencesFile *file = g_ptr_array_index (files, i);
if (!preferences_backup (file))
{
result.failed_file = i < 2 ? (i ? "colors.conf" : "zoitechat.conf")
: ((PreferencesFile *)g_ptr_array_index (staged_files, i - 2))->name;
result.config_failed = i == 0;
result.theme_failed = i == 1;
goto done;
}
}
for (i = 0; i < files->len; i++)
{
PreferencesFile *file = g_ptr_array_index (files, i);
if (!preferences_install (file, file->temporary))
{
result.failed_file = i < 2 ? (i ? "colors.conf" : "zoitechat.conf")
: ((PreferencesFile *)g_ptr_array_index (staged_files, i - 2))->name;
result.config_failed = i == 0;
result.theme_failed = i == 1;
goto rollback;
}
}
result.success = TRUE;
goto done;
rollback:
for (i = files->len; i > 0; i--)
if (!preferences_restore (g_ptr_array_index (files, i - 1)))
result.partial_failure = TRUE;
done:
if (!result.success)
{
if (result.config_failed && result.theme_failed)
result.failed_file = "zoitechat.conf and colors.conf";
else if (result.config_failed)
result.failed_file = "zoitechat.conf";
else if (result.theme_failed)
result.failed_file = "colors.conf";
}
save_config_discard (config_temp);
theme_runtime_save_discard (theme_temp);
g_free (config_temp);
g_free (theme_temp);
return result;
g_ptr_array_free (files, TRUE);
return result;
}
+4
View File
@@ -34,5 +34,9 @@ typedef struct
} PreferencesPersistenceResult;
PreferencesPersistenceResult preferences_persistence_save_all (void);
/* Editors queue serialized settings; runtime state is committed only after
* every queued file and the main preferences have been saved successfully. */
void preferences_persistence_stage_file (const char *name, const char *contents);
void preferences_persistence_clear_staged_files (void);
#endif
+16 -14
View File
@@ -70,7 +70,7 @@ static GtkWidget *entry_nick1;
static GtkWidget *entry_nick2;
static GtkWidget *entry_nick3;
static GtkWidget *entry_guser;
/* static GtkWidget *entry_greal; */
static GtkWidget *entry_greal;
enum {
SERVER_TREE,
@@ -1650,7 +1650,7 @@ servlist_savegui (void)
sp = strchr (prefs.hex_irc_user_name, ' ');
if (sp)
sp[0] = 0; /* spaces will break the login */
/* strcpy (prefs.hex_irc_real_name, gtk_entry_get_text (GTK_ENTRY (entry_greal))); */
safe_strcpy (prefs.hex_irc_real_name, gtk_entry_get_text (GTK_ENTRY (entry_greal)), sizeof(prefs.hex_irc_real_name));
servlist_save ();
if (!save_config ())
fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN);
@@ -2809,12 +2809,12 @@ servlist_open_networks (void)
GtkWidget *label4;
GtkWidget *label5;
GtkWidget *label6;
/* GtkWidget *label7; */
GtkWidget *label7;
GtkWidget *entry1;
GtkWidget *entry2;
GtkWidget *entry3;
GtkWidget *entry4;
/* GtkWidget *entry5; */
GtkWidget *entry5;
GtkWidget *vbox2;
GtkWidget *label1;
GtkWidget *table4;
@@ -2900,12 +2900,12 @@ servlist_open_networks (void)
gtk_widget_set_halign (label6, GTK_ALIGN_START);
gtk_widget_set_valign (label6, GTK_ALIGN_CENTER);
/* label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
gtk_widget_show (label7);
gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5,
(GtkAttachOptions) (GTK_FILL),
(GtkAttachOptions) (0), 0, 0);
*/
servlist_table_attach (table1, label7, 0, 1, 4, 5,
FALSE, FALSE,
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
0, 0);
entry_nick1 = entry1 = gtk_entry_new ();
gtk_entry_set_text (GTK_ENTRY (entry1), prefs.hex_irc_nick1);
@@ -2939,12 +2939,14 @@ servlist_open_networks (void)
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_CENTER,
0, 0);
/* entry_greal = entry5 = gtk_entry_new ();
entry_greal = entry5 = gtk_entry_new ();
gtk_entry_set_max_length (GTK_ENTRY (entry5), sizeof prefs.hex_irc_real_name - 1);
gtk_entry_set_text (GTK_ENTRY (entry5), prefs.hex_irc_real_name);
gtk_widget_show (entry5);
gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5,
(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
(GtkAttachOptions) (0), 0, 0); */
servlist_table_attach (table1, entry5, 1, 2, 4, 5,
TRUE, FALSE,
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_CENTER,
0, 0);
vbox2 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
gtk_widget_show (vbox2);
@@ -3098,7 +3100,7 @@ button_connect = gtkutil_button (hbuttonbox1, ICON_SERVLIST_CONNECT, NULL,
gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1);
gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4);
/* gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5); */
gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5);
gtk_widget_grab_focus (networks_tree);
gtk_widget_grab_default (button_close);
+789 -361
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -24,5 +24,9 @@
void setup_apply_real (const ThemeChangedEvent *event);
void setup_open (void);
/* Stable, untranslated page IDs allow existing menu entries to deep-link. */
void setup_open_page (const char *id);
/* Reveal an existing editor, including any nested menu selector, on errors. */
void setup_reveal_widget (GtkWidget *widget);
#endif
@@ -0,0 +1,226 @@
/* ZoiteChat
* Copyright (C) 1998-2010 Peter Zelezny.
* Copyright (C) 2009-2013 Berke Viktor.
* Copyright (C) 2026 deepend-tildeclub.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
/* Regression coverage for saving multiple Preferences editors together. */
#include "../preferences-persistence.h"
#include "../../common/cfgfiles.h"
#include "../theme/theme-runtime.h"
#include <errno.h>
#include <glib/gstdio.h>
#include <string.h>
static char *test_directory;
static gboolean fail_theme_prepare;
static int rename_count, fail_install, fail_restore;
char *get_xdir (void) { return test_directory; }
static gboolean
prepare (const char *name, const char *contents, char **path)
{
*path = g_build_filename (test_directory, name, NULL);
return g_file_set_contents (*path, contents, -1, NULL);
}
int save_config_prepare (char **path) { return prepare ("config.new", "new config", path); }
gboolean theme_runtime_save_prepare (char **path)
{
if (fail_theme_prepare) return FALSE;
return prepare ("theme.new", "new theme", path);
}
static int
checked_rename (const char *source, const char *destination)
{
rename_count++;
if (rename_count == fail_install || rename_count == fail_restore)
{
errno = EACCES;
return -1;
}
return g_rename (source, destination);
}
/* Exercise replacement failures without relying on filesystem permissions. */
#undef g_rename
#define g_rename checked_rename
#include "../preferences-persistence.c"
#undef g_rename
static void
put (const char *name, const char *contents)
{
char *path = g_build_filename (test_directory, name, NULL);
g_assert_true (g_file_set_contents (path, contents, -1, NULL));
g_free (path);
}
static void
assert_contents (const char *name, const char *expected)
{
char *path = g_build_filename (test_directory, name, NULL), *contents = NULL;
g_assert_true (g_file_get_contents (path, &contents, NULL, NULL));
g_assert_cmpstr (contents, ==, expected);
g_free (contents);
g_free (path);
}
static void
begin (void)
{
test_directory = g_dir_make_tmp ("zoitechat-preferences-XXXXXX", NULL);
g_assert_nonnull (test_directory);
rename_count = fail_install = fail_restore = 0;
fail_theme_prepare = FALSE;
put ("zoitechat.conf", "old config");
put ("colors.conf", "old theme");
put ("commands.conf", "old commands");
preferences_persistence_stage_file ("commands.conf", "new commands");
preferences_persistence_stage_file ("replace.conf", "new replacements");
}
static int
temporary_count (void)
{
GDir *directory = g_dir_open (test_directory, 0, NULL);
const char *name;
int count = 0;
while ((name = g_dir_read_name (directory)))
if (g_str_has_prefix (name, ".preferences-")) count++;
g_dir_close (directory);
return count;
}
static void
end (void)
{
GDir *directory = g_dir_open (test_directory, 0, NULL);
const char *name;
preferences_persistence_clear_staged_files ();
while ((name = g_dir_read_name (directory)))
{
char *path = g_build_filename (test_directory, name, NULL);
if (g_file_test (path, G_FILE_TEST_IS_DIR)) g_rmdir (path);
else g_unlink (path);
g_free (path);
}
g_dir_close (directory);
g_rmdir (test_directory);
g_free (test_directory);
}
static void
test_success (void)
{
PreferencesPersistenceResult result;
begin ();
result = preferences_persistence_save_all ();
g_assert_true (result.success);
assert_contents ("zoitechat.conf", "new config");
assert_contents ("colors.conf", "new theme");
assert_contents ("commands.conf", "new commands");
assert_contents ("replace.conf", "new replacements");
g_assert_cmpint (temporary_count (), ==, 0);
end ();
}
static void
test_prepare_failure (void)
{
PreferencesPersistenceResult result;
begin ();
fail_theme_prepare = TRUE;
result = preferences_persistence_save_all ();
g_assert_false (result.success);
g_assert_false (result.partial_failure);
g_assert_true (result.theme_failed);
assert_contents ("zoitechat.conf", "old config");
assert_contents ("commands.conf", "old commands");
g_assert_cmpint (rename_count, ==, 0);
g_assert_cmpint (temporary_count (), ==, 0);
end ();
}
static void
test_backup_failure (void)
{
PreferencesPersistenceResult result;
char *path;
begin ();
path = g_build_filename (test_directory, "replace.conf", NULL);
g_assert_cmpint (g_mkdir (path, 0700), ==, 0);
g_free (path);
result = preferences_persistence_save_all ();
g_assert_false (result.success);
g_assert_cmpstr (result.failed_file, ==, "replace.conf");
assert_contents ("zoitechat.conf", "old config");
assert_contents ("commands.conf", "old commands");
g_assert_cmpint (rename_count, ==, 0);
g_assert_cmpint (temporary_count (), ==, 0);
end ();
}
static void
test_rollback_and_retry (void)
{
PreferencesPersistenceResult result;
begin ();
fail_install = 4;
result = preferences_persistence_save_all ();
g_assert_false (result.success);
g_assert_false (result.partial_failure);
g_assert_true (result.retry_possible);
assert_contents ("zoitechat.conf", "old config");
assert_contents ("colors.conf", "old theme");
assert_contents ("commands.conf", "old commands");
g_assert_cmpint (temporary_count (), ==, 0);
fail_install = 0;
result = preferences_persistence_save_all ();
g_assert_true (result.success);
assert_contents ("commands.conf", "new commands");
end ();
}
static void
test_recovery_copy (void)
{
PreferencesPersistenceResult result;
begin ();
fail_install = 4;
fail_restore = 5;
result = preferences_persistence_save_all ();
g_assert_false (result.success);
g_assert_true (result.partial_failure);
g_assert_cmpint (temporary_count (), ==, 1);
assert_contents ("zoitechat.conf", "old config");
assert_contents ("colors.conf", "old theme");
end ();
}
int main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/preferences/save/success", test_success);
g_test_add_func ("/preferences/save/prepare-failure", test_prepare_failure);
g_test_add_func ("/preferences/save/backup-failure", test_backup_failure);
g_test_add_func ("/preferences/save/rollback-retry", test_rollback_and_retry);
g_test_add_func ("/preferences/save/recovery-copy", test_recovery_copy);
return g_test_run ();
}
+250 -59
View File
@@ -38,6 +38,8 @@
#include "theme/theme-access.h"
#include "theme/theme-manager.h"
#include "textgui.h"
#include "setup.h"
#include "preferences-persistence.h"
#define ICON_TEXTEVENT_SAVE_AS "document-save-as"
#define ICON_TEXTEVENT_OPEN "document-open"
@@ -50,6 +52,10 @@ extern char *pntevts[];
static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid,
*pevent_dialog_list, *pevent_dialog_hlist;
static char *pevent_staged[NUM_XP];
static char *pevent_original[NUM_XP];
static gboolean pevent_pending;
#define PEVENT_THEME_LISTENER_ID_KEY "textgui.theme-listener-id"
enum
@@ -148,10 +154,17 @@ PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp)
}
static void
pevent_dialog_close (GtkWidget *wid, gpointer arg)
pevent_preferences_destroy (GtkWidget *widget, gpointer data)
{
pevent_dialog = NULL;
pevent_save (NULL);
int i;
(void)widget; (void)data;
pevent_dialog = pevent_dialog_list = pevent_dialog_hlist = pevent_dialog_twid = NULL;
for (i = 0; i < NUM_XP; i++)
{
g_clear_pointer (&pevent_staged[i], g_free);
g_clear_pointer (&pevent_original[i], g_free);
}
pevent_pending = FALSE;
}
static void
@@ -211,9 +224,14 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
char *out;
int sig;
if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
&iter, ROW_COLUMN, &sig, -1))
path = gtk_tree_path_new_from_string (pathstr);
if (!gtk_tree_model_get_iter (model, &iter, path))
{
gtk_tree_path_free (path);
return;
}
gtk_tree_path_free (path);
gtk_tree_model_get (model, &iter, ROW_COLUMN, &sig, -1);
text = new_text;
len = strlen (new_text);
@@ -234,16 +252,11 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
return;
}
path = gtk_tree_path_new_from_string (pathstr);
gtk_tree_model_get_iter (model, &iter, path);
gtk_list_store_set (GTK_LIST_STORE (model), &iter, TEXT_COLUMN, new_text, -1);
gtk_tree_path_free (path);
g_free (pntevts_text[sig]);
g_free (pntevts[sig]);
pntevts_text[sig] = g_strdup (text);
pntevts[sig] = out;
g_free (pevent_staged[sig]);
pevent_staged[sig] = g_strdup (text);
g_free (out);
out = g_malloc (len + 2);
memcpy (out, text, len + 1);
@@ -257,8 +270,7 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
/* Scroll to bottom */
gtk_adjustment_set_value (xtext->adj, gtk_adjustment_get_upper (xtext->adj));
/* save this when we exit */
prefs.save_pevents = 1;
/* The Preferences Save action commits this staged template. */
}
static void
@@ -316,53 +328,222 @@ pevent_dialog_fill (GtkWidget *list)
i--;
gtk_list_store_insert_with_values (store, &iter, 0,
EVENT_COLUMN, te[i].name,
TEXT_COLUMN, pntevts_text[i],
TEXT_COLUMN, pevent_staged[i],
ROW_COLUMN, i, -1);
}
while (i != 0);
}
static void
pevent_save_req_cb (void *arg1, char *file)
static char *
pevent_preferences_serialize (void)
{
if (file)
pevent_save (file);
GString *text = g_string_new ("");
int i;
for (i = 0; i < NUM_XP; i++)
g_string_append_printf (text, "event_name=%s\nevent_text=%s\n\n", te[i].name, pevent_staged[i]);
return g_string_free (text, FALSE);
}
static void
pevent_save_cb (GtkWidget * wid, void *data)
static gboolean
pevent_preferences_validate (const char *text, int event, GError **error)
{
if (data)
char *compiled = NULL;
int maximum;
if (strpbrk (text, "\r\n") || pevt_build_string (text, &compiled, &maximum) != 0)
{
gtkutil_file_req (NULL, _("Print Texts File"), pevent_save_req_cb, NULL,
NULL, NULL, FRF_WRITE);
return;
g_free (compiled);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Invalid message format for %s."), te[event].name);
return FALSE;
}
pevent_save (NULL);
}
static void
pevent_load_req_cb (void *arg1, char *file)
{
if (file)
g_free (compiled);
if (maximum > (te[event].num_args & 0x7f))
{
pevent_load (file);
pevent_make_pntevts ();
pevent_dialog_fill (pevent_dialog_list);
prefs.save_pevents = 1;
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("%s only supports %d arguments."), te[event].name, te[event].num_args & 0x7f);
return FALSE;
}
return TRUE;
}
gboolean
pevent_preferences_import_text (const char *contents, GError **error)
{
char **lines;
char *values[NUM_XP] = {0};
int i, event = -1, matched = 0;
gboolean success = FALSE;
if (!pevent_dialog_list)
{
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Open message formatting before importing templates."));
return FALSE;
}
lines = g_strsplit (contents, "\n", -1);
for (i = 0; lines[i]; i++)
{
char *line = lines[i];
gsize length = strlen (line);
int n;
if (length && line[length - 1] == '\r') line[length - 1] = 0;
if (g_str_has_prefix (line, "event_name="))
{
event = -1;
for (n = 0; n < NUM_XP; n++)
if (!strcmp (te[n].name, line + 11)) { event = n; break; }
}
else if (event >= 0 && g_str_has_prefix (line, "event_text="))
{
if (!pevent_preferences_validate (line + 11, event, error)) goto done;
g_free (values[event]);
values[event] = g_strdup (line + 11);
matched++;
}
}
if (!matched)
{
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("No recognized text events were found in this file."));
goto done;
}
for (i = 0; i < NUM_XP; i++)
if (values[i])
{
g_free (pevent_staged[i]);
pevent_staged[i] = values[i];
values[i] = NULL;
}
pevent_dialog_fill (pevent_dialog_list);
success = TRUE;
done:
for (i = 0; i < NUM_XP; i++) g_free (values[i]);
g_strfreev (lines);
return success;
}
static gboolean
pevent_preferences_import (const char *file, GError **error)
{
char *contents;
if (!g_file_get_contents (file, &contents, NULL, error)) return FALSE;
gboolean result = pevent_preferences_import_text (contents, error);
g_free (contents);
return result;
}
typedef struct
{
GWeakRef page;
gboolean exporting;
} PeEventFileRequest;
static void
pevent_file_request_free (gpointer data)
{
PeEventFileRequest *request = data;
g_weak_ref_clear (&request->page);
g_free (request);
}
static void
pevent_load_cb (GtkWidget * wid, void *data)
pevent_file_response (GtkNativeDialog *dialog, gint response, PeEventFileRequest *request)
{
gtkutil_file_req (NULL, _("Print Texts File"), pevent_load_req_cb, NULL, NULL, NULL, 0);
GtkWidget *page = g_weak_ref_get (&request->page);
if (page && page == pevent_dialog && response == GTK_RESPONSE_ACCEPT)
{
char *file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
GError *error = NULL;
if (file)
{
if (request->exporting)
{
char *contents = pevent_preferences_serialize ();
g_file_set_contents (file, contents, -1, &error);
g_free (contents);
}
else pevent_preferences_import (file, &error);
if (error) { fe_message (error->message, FE_MSG_ERROR); g_clear_error (&error); }
g_free (file);
}
}
if (page) g_object_unref (page);
g_object_unref (dialog);
}
static void
pevent_ok_cb (GtkWidget * wid, void *data)
pevent_file_open (GtkWidget *button, gpointer exporting)
{
gtk_widget_destroy (pevent_dialog);
GtkFileChooserNative *dialog;
PeEventFileRequest *request = g_new0 (PeEventFileRequest, 1);
(void)button;
request->exporting = GPOINTER_TO_INT (exporting);
g_weak_ref_init (&request->page, pevent_dialog);
dialog = gtk_file_chooser_native_new (_("Text events file"),
GTK_WINDOW (gtk_widget_get_toplevel (pevent_dialog)),
request->exporting ? GTK_FILE_CHOOSER_ACTION_SAVE : GTK_FILE_CHOOSER_ACTION_OPEN,
request->exporting ? _("_Export") : _("_Import"), _("_Cancel"));
gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (dialog), TRUE);
g_object_set_data_full (G_OBJECT (dialog), "request", request, pevent_file_request_free);
g_signal_connect (dialog, "response", G_CALLBACK (pevent_file_response), request);
gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog));
}
gboolean
pevent_preferences_stage (GError **error)
{
int i;
char *contents;
pevent_pending = FALSE;
if (!pevent_dialog) return TRUE;
for (i = 0; i < NUM_XP; i++)
{
if (!pevent_preferences_validate (pevent_staged[i], i, error))
{
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list));
GtkTreeIter iter;
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
setup_reveal_widget (pevent_dialog_list);
while (valid)
{
int event;
gtk_tree_model_get (model, &iter, ROW_COLUMN, &event, -1);
if (event == i)
{
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
gtk_widget_grab_focus (pevent_dialog_list);
gtk_tree_view_set_cursor (GTK_TREE_VIEW (pevent_dialog_list), path, NULL, FALSE);
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (pevent_dialog_list), path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
break;
}
valid = gtk_tree_model_iter_next (model, &iter);
}
return FALSE;
}
if (g_strcmp0 (pevent_staged[i], pevent_original[i]) != 0) pevent_pending = TRUE;
}
if (!pevent_pending) return TRUE;
for (i = 0; i < NUM_XP; i++)
if (g_strcmp0 (pntevts_text[i], pevent_original[i]) != 0)
{
setup_reveal_widget (pevent_dialog_list);
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_AGAIN, _("Text events changed outside Preferences. Reopen Preferences before editing them."));
return FALSE;
}
contents = pevent_preferences_serialize ();
preferences_persistence_stage_file ("pevents.conf", contents);
g_free (contents);
return TRUE;
}
void
pevent_preferences_commit (void)
{
int i;
if (!pevent_pending) return;
for (i = 0; i < NUM_XP; i++)
{
g_free (pntevts_text[i]);
pntevts_text[i] = g_strdup (pevent_staged[i]);
}
pevent_make_pntevts ();
prefs.save_pevents = 1;
}
static void
@@ -373,7 +554,7 @@ pevent_test_cb (GtkWidget * wid, GtkWidget * twid)
for (n = 0; n < NUM_XP; n++)
{
text = _(pntevts_text[n]);
text = pevent_staged[n];
len = strlen (text);
out = g_malloc (len + 2);
@@ -485,22 +666,27 @@ pevent_hlist_treeview_new (GtkWidget *box)
return view;
}
void
pevent_dialog_show (void)
static void
pevent_preferences_preview_realize (GtkWidget *widget, gpointer data)
{
gtk_xtext_set_font (GTK_XTEXT (widget), prefs.hex_text_font);
}
GtkWidget *
pevent_preferences_create (void)
{
GtkWidget *vbox, *hbox, *wid, *pane, *preview_pane;
XTextColor xtext_palette[XTEXT_COLS];
if (pevent_dialog)
int i;
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
pevent_dialog = vbox;
for (i = 0; i < NUM_XP; i++)
{
mg_bring_tofront (pevent_dialog);
return;
pevent_original[i] = g_strdup (pntevts_text[i]);
pevent_staged[i] = g_strdup (pntevts_text[i]);
}
pevent_dialog =
mg_create_generic_tab ("edit events", _("Edit Events"),
TRUE, FALSE, pevent_dialog_close, NULL,
600, 455, &vbox, 0);
g_signal_connect (vbox, "destroy", G_CALLBACK (pevent_preferences_destroy), NULL);
preview_pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_box_pack_start (GTK_BOX (vbox), preview_pane, TRUE, TRUE, 0);
@@ -523,7 +709,7 @@ pevent_dialog_show (void)
pevent_dialog_twid = gtk_xtext_new (xtext_palette, 0);
gtk_widget_set_sensitive (pevent_dialog_twid, FALSE);
gtk_container_add (GTK_CONTAINER (wid), pevent_dialog_twid);
gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.hex_text_font);
g_signal_connect (pevent_dialog_twid, "realize", G_CALLBACK (pevent_preferences_preview_realize), NULL);
g_object_set_data (G_OBJECT (pevent_dialog), "xtext", pevent_dialog_twid);
g_object_set_data (G_OBJECT (pevent_dialog), PEVENT_THEME_LISTENER_ID_KEY,
GUINT_TO_POINTER (theme_listener_register ("textgui.events", pevent_dialog_theme_changed, pevent_dialog)));
@@ -532,15 +718,20 @@ pevent_dialog_show (void)
hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 4);
gtkutil_button (hbox, ICON_TEXTEVENT_SAVE_AS, NULL, pevent_save_cb,
(void *) 1, _("Save As..."));
gtkutil_button (hbox, ICON_TEXTEVENT_OPEN, NULL, pevent_load_cb,
NULL, _("Load From..."));
gtkutil_button (hbox, ICON_TEXTEVENT_SAVE_AS, NULL, pevent_file_open,
(void *) 1, _("Export..."));
gtkutil_button (hbox, ICON_TEXTEVENT_OPEN, NULL, pevent_file_open,
NULL, _("Import..."));
gtkutil_button (hbox, NULL, NULL, pevent_test_cb,
pevent_dialog_twid, _("Test All"));
gtkutil_button (hbox, ICON_TEXTEVENT_OK, NULL, pevent_ok_cb,
NULL, _("OK"));
gtk_widget_show_all (pevent_dialog);
pevent_dialog_theme_apply (pevent_dialog);
return vbox;
}
void
pevent_dialog_show (void)
{
setup_open_page ("events");
}
+4
View File
@@ -22,5 +22,9 @@
void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp);
void pevent_dialog_show (void);
GtkWidget *pevent_preferences_create (void);
gboolean pevent_preferences_import_text (const char *contents, GError **error);
gboolean pevent_preferences_stage (GError **error);
void pevent_preferences_commit (void);
#endif
@@ -112,6 +112,14 @@ theme_runtime_mode_has_user_colors (gboolean dark_mode)
return FALSE;
}
gboolean
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
{
(void)map;
*color = stub_light_colors[token];
return TRUE;
}
gboolean
theme_runtime_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
{
@@ -571,6 +571,31 @@ test_stale_theme_symlink_is_replaced (void)
theme_gtk3_disable ();
}
static void
test_builtin_appearances_restore_desktop_theme (void)
{
if (!gtk_available) return;
GtkSettings *settings = gtk_settings_get_default ();
char *original, *name;
gboolean dark;
theme_gtk3_disable ();
g_object_get (settings, "gtk-theme-name", &original, NULL);
g_strlcpy (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK, sizeof prefs.hex_gui_gtk3_theme);
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
g_assert_true (theme_gtk3_apply_current (NULL));
g_object_get (settings, "gtk-theme-name", &name, "gtk-application-prefer-dark-theme", &dark, NULL);
g_assert_cmpstr (name, ==, "Adwaita"); g_assert_true (dark); g_free (name);
g_strlcpy (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_LIGHT, sizeof prefs.hex_gui_gtk3_theme);
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_LIGHT;
g_assert_true (theme_gtk3_apply_current (NULL));
g_object_get (settings, "gtk-application-prefer-dark-theme", &dark, NULL);
g_assert_false (dark);
prefs.hex_gui_gtk3_theme[0] = '\0'; prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_AUTO;
g_assert_true (theme_gtk3_apply_current (NULL));
g_object_get (settings, "gtk-theme-name", &name, NULL);
g_assert_cmpstr (name, ==, original); g_free (name); g_free (original);
}
int
main (int argc, char **argv)
{
@@ -596,6 +621,7 @@ main (int argc, char **argv)
if (!gtk_available)
g_test_message ("Skipping GTK3 settings tests because GTK initialization failed");
g_test_add_func ("/theme/gtk3/builtin_appearances_restore_desktop", test_builtin_appearances_restore_desktop_theme);
rc = g_test_run ();
theme_gtk3_disable ();
teardown_themes ();
@@ -25,6 +25,7 @@
#include "../../../common/gtk3-theme-service.h"
#include "../theme-gtk3.h"
#include "../theme-manager.h"
#include "../theme-runtime.h"
struct session *current_sess;
struct session *current_tab;
@@ -38,6 +39,61 @@ static char applied_theme_id[256];
static ThemeGtk3Variant applied_variant;
static gboolean removed_selected;
static gboolean custom_tokens[THEME_TOKEN_COUNT];
static GdkRGBA custom_colors[THEME_TOKEN_COUNT];
gboolean
theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color)
{
if (!custom_tokens[token]) return FALSE;
*color = custom_colors[token];
return TRUE;
}
gboolean
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
{
(void)map;
return gdk_rgba_parse (color, token == THEME_TOKEN_TEXT_BACKGROUND || token == THEME_TOKEN_SELECTION_FOREGROUND ? "#ffffff" :
token == THEME_TOKEN_SELECTION_BACKGROUND ? "#336699" : "#000000");
}
gboolean
theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color)
{
(void)widget;
theme_runtime_get_default_color (token, NULL, color);
if (token == THEME_TOKEN_TEXT_BACKGROUND && prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_DARK)
gdk_rgba_parse (color, "#181818");
return TRUE;
}
void theme_runtime_reset_user_color (ThemeSemanticToken token) { custom_tokens[token] = FALSE; }
void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *color)
{ custom_tokens[token] = TRUE; custom_colors[token] = *color; }
gboolean theme_manager_apply_mode (unsigned int mode, gboolean *changed)
{ (void)mode; if (changed) *changed = FALSE; return FALSE; }
void theme_manager_apply_and_dispatch (unsigned int mode, ThemeChangedReason reasons, gboolean *changed)
{ (void)mode; (void)reasons; if (changed) *changed = FALSE; }
guint theme_listener_register (const char *id, ThemeChangedCallback callback, gpointer data)
{ (void)id; (void)callback; (void)data; return 0; }
void theme_listener_unregister (guint id) { (void)id; }
gboolean pevent_preferences_import_text (const char *contents, GError **error)
{ (void)contents; (void)error; return TRUE; }
void
setup_open_page (const char *id)
{
(void)id;
}
void
setup_reveal_widget (GtkWidget *widget)
{
(void)widget;
}
GtkWidget *
gtkutil_box_new (GtkOrientation orientation, gboolean homogeneous, gint spacing)
{
@@ -106,6 +162,7 @@ theme_manager_set_token_color (unsigned int dark_mode, ThemeSemanticToken token,
(void)token;
(void)color;
set_token_color_calls++;
theme_runtime_user_set_color (token, color);
if (changed)
*changed = FALSE;
}
@@ -458,6 +515,154 @@ test_select_theme_does_not_pin_palette_tokens (void)
gtk_widget_destroy (page);
}
static void
test_embedded_color_editor (void)
{
GtkWidget *window, *editor;
theme_color_manager_ui *ui;
theme_color_manager_row *row;
GError *error = NULL;
if (!gtk_available)
{
g_test_message ("GTK display not available");
return;
}
memset (&prefs, 0, sizeof prefs);
theme_preferences_stage_begin ();
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
editor = theme_preferences_create_color_editor (GTK_WINDOW (window), NULL);
gtk_container_add (GTK_CONTAINER (window), editor);
ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
row = g_ptr_array_index (ui->rows, 0);
set_token_color_calls = 0;
theme_preferences_manager_entry_commit (row);
g_assert_true (theme_preferences_color_editor_stage (editor, &error));
g_assert_no_error (error);
theme_preferences_stage_apply ();
g_assert_cmpint (set_token_color_calls, ==, 0);
gtk_entry_set_text (GTK_ENTRY (row->entry), "#123456");
g_assert_true (theme_preferences_color_editor_stage (editor, &error));
g_assert_no_error (error);
/* A typed edit previews its token, then Save reapplies that token. */
g_assert_cmpint (set_token_color_calls, ==, 1);
theme_preferences_stage_apply ();
g_assert_cmpint (set_token_color_calls, ==, 2);
gtk_entry_set_text (GTK_ENTRY (row->entry), "invalid");
g_assert_false (theme_preferences_color_editor_stage (editor, &error));
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
g_clear_error (&error);
gtk_widget_show_all (window);
gtk_button_clicked (GTK_BUTTON (row->button));
gtk_widget_destroy (window);
theme_preferences_stage_discard ();
}
static void
test_appearance_schemes_and_cancel (void)
{
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
GdkRGBA original, restored;
gdk_rgba_parse (&original, "#345678");
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &original);
theme_preferences_stage_begin ();
struct zoitechatprefs staged = prefs;
GtkWidget *page = theme_preferences_create_page (NULL, &staged, NULL);
theme_preferences_ui *appearance = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (appearance->color_editor), "zoitechat-theme-color-manager");
gtk_button_clicked (GTK_BUTTON (ui->scheme_buttons[2]));
g_assert_true (theme_preferences_stage.changed);
g_assert_true (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_BACKGROUND]);
gtk_button_clicked (GTK_BUTTON (appearance->style_buttons[2]));
g_assert_cmpint (prefs.hex_gui_dark_mode, ==, ZOITECHAT_DARK_MODE_DARK);
theme_preferences_stage_discard ();
g_assert_cmpint (prefs.hex_gui_dark_mode, ==, ZOITECHAT_DARK_MODE_AUTO);
g_assert_true (theme_runtime_get_user_color (THEME_TOKEN_TEXT_FOREGROUND, &restored));
g_assert_true (gdk_rgba_equal (&original, &restored));
for (int token = 0; token < THEME_TOKEN_COUNT; token++)
if (token != THEME_TOKEN_TEXT_FOREGROUND) g_assert_false (custom_tokens[token]);
gtk_widget_destroy (page);
}
static void
test_individual_reset_preserves_other_overrides (void)
{
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
theme_preferences_stage_begin ();
GtkWidget *editor = theme_preferences_create_color_editor (NULL, NULL);
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
gtk_button_clicked (GTK_BUTTON (ui->scheme_buttons[2]));
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_TEXT_BACKGROUND);
gtk_button_clicked (GTK_BUTTON (row->reset));
g_assert_false (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_BACKGROUND]);
g_assert_true (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_FOREGROUND]);
g_assert_cmpstr (gtk_label_get_text (GTK_LABEL (row->status)), ==, "Theme");
GdkRGBA light, dark;
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_BACKGROUND, &light);
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_BACKGROUND, &dark);
g_assert_false (gdk_rgba_equal (&light, &dark));
theme_preferences_stage_commit ();
g_assert_false (custom_tokens[THEME_TOKEN_TEXT_BACKGROUND]);
g_assert_true (custom_tokens[THEME_TOKEN_TEXT_FOREGROUND]);
gtk_widget_destroy (editor);
}
static void
test_contrast_suggestion_and_preview_navigation (void)
{
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
theme_preferences_stage_begin ();
GtkWidget *editor = theme_preferences_create_color_editor (NULL, NULL);
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_TEXT_FOREGROUND);
GdkRGBA white, black, gray;
gdk_rgba_parse (&white, "#ffffff"); gdk_rgba_parse (&black, "#000000"); gdk_rgba_parse (&gray, "#dddddd");
g_assert_cmpfloat_with_epsilon (theme_preferences_contrast_ratio (&white, &black), 21.0, 0.001);
theme_preferences_manager_row_commit (row, &gray);
g_assert_true (gtk_widget_get_visible (ui->contrast_fix));
gtk_button_clicked (GTK_BUTTON (ui->contrast_fix));
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_FOREGROUND, &gray);
g_assert_cmpfloat (theme_preferences_contrast_ratio (&gray, &white), >=, 4.5);
theme_preferences_manager_focus_color (ui, THEME_TOKEN_MIRC_12);
g_assert_true (gtk_expander_get_expanded (GTK_EXPANDER (ui->advanced)));
gtk_entry_set_text (GTK_ENTRY (row->entry), "invalid");
theme_preferences_manager_entry_commit (row);
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (row->entry)), ==, "invalid");
GError *error = NULL;
g_assert_false (theme_preferences_color_editor_stage (editor, &error));
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL); g_clear_error (&error);
theme_preferences_stage_discard (); gtk_widget_destroy (editor);
}
static void
test_picker_cancel_restores_inheritance (void)
{
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
theme_preferences_stage_begin ();
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
GtkWidget *editor = theme_preferences_create_color_editor (GTK_WINDOW (window), NULL);
gtk_container_add (GTK_CONTAINER (window), editor);
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_MIRC_12);
gtk_button_clicked (GTK_BUTTON (row->button));
GList *windows = gtk_window_list_toplevels ();
GtkWidget *picker = NULL;
for (GList *i = windows; i; i = i->next) if (GTK_IS_COLOR_CHOOSER_DIALOG (i->data)) picker = i->data;
g_assert_nonnull (picker);
GdkRGBA color; gdk_rgba_parse (&color, "#aabbcc");
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (picker), &color);
g_assert_true (theme_preferences_stage.staged_custom[row->token]);
gtk_dialog_response (GTK_DIALOG (picker), GTK_RESPONSE_CANCEL);
g_assert_false (theme_preferences_stage.staged_custom[row->token]);
g_assert_false (custom_tokens[row->token]);
g_list_free (windows);
theme_preferences_stage_discard (); gtk_widget_destroy (window);
}
int
main (int argc, char **argv)
{
@@ -471,5 +676,10 @@ main (int argc, char **argv)
test_select_none_resets_theme_and_applies);
g_test_add_func ("/theme/preferences/gtk3_select_theme_does_not_pin_palette_tokens",
test_select_theme_does_not_pin_palette_tokens);
g_test_add_func ("/theme/preferences/embedded_color_editor", test_embedded_color_editor);
g_test_add_func ("/theme/preferences/appearance_schemes_cancel", test_appearance_schemes_and_cancel);
g_test_add_func ("/theme/preferences/individual_reset_inherits", test_individual_reset_preserves_other_overrides);
g_test_add_func ("/theme/preferences/contrast_and_navigation", test_contrast_suggestion_and_preview_navigation);
g_test_add_func ("/theme/preferences/picker_cancel_inherits", test_picker_cancel_restores_inheritance);
return g_test_run ();
}
@@ -430,6 +430,36 @@ test_save_writes_only_custom_token_keys (void)
g_free (cfg);
}
static void
test_reset_one_colour_restores_inheritance_after_reload (void)
{
GdkRGBA color;
char *cfg;
setup_temp_home ();
theme_runtime_load ();
gdk_rgba_parse (&color, "#456789");
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &color);
theme_runtime_user_set_color (THEME_TOKEN_TEXT_BACKGROUND, &color);
theme_runtime_reset_user_color (THEME_TOKEN_TEXT_BACKGROUND);
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL);
g_assert_true (theme_runtime_save ());
cfg = read_colors_conf ();
g_assert_nonnull (strstr (cfg, "theme.mode.light.token.text_foreground"));
g_assert_null (strstr (cfg, "theme.mode.light.token.text_background"));
g_free (cfg);
theme_runtime_load ();
g_assert_false (theme_runtime_get_user_color (THEME_TOKEN_TEXT_BACKGROUND, &color));
g_assert_true (theme_runtime_get_user_color (THEME_TOKEN_TEXT_FOREGROUND, &color));
g_assert_cmpfloat_with_epsilon (color.red, 0x45 / 255.0, 0.0001);
ThemeGtkPaletteMap map = { .enabled = TRUE };
gdk_rgba_parse (&map.text_background, "#111111");
theme_runtime_get_default_color (THEME_TOKEN_TEXT_BACKGROUND, &map, &color);
g_assert_cmpfloat_with_epsilon (color.red, 0x11 / 255.0, 0.0001);
gdk_rgba_parse (&map.text_background, "#eeeeee");
theme_runtime_get_default_color (THEME_TOKEN_TEXT_BACKGROUND, &map, &color);
g_assert_cmpfloat_with_epsilon (color.red, 0xee / 255.0, 0.0001);
}
int
main (int argc, char **argv)
{
@@ -450,5 +480,6 @@ main (int argc, char **argv)
test_save_discard_unlinks_temp_file);
g_test_add_func ("/theme/runtime/save_writes_only_custom_token_keys",
test_save_writes_only_custom_token_keys);
g_test_add_func ("/theme/runtime/reset_colour_persists_inheritance", test_reset_one_colour_restores_inheritance_after_reload);
return g_test_run ();
}
+8
View File
@@ -206,6 +206,14 @@ theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map
return TRUE;
}
gboolean
theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color)
{
ThemeGtkPaletteMap map = { 0 };
theme_access_get_gtk_palette_map (widget, &map);
return theme_runtime_get_default_color (token, &map, color);
}
gboolean
theme_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
{
+1
View File
@@ -28,6 +28,7 @@
#include "theme-palette.h"
#include "../xtext-color.h"
gboolean theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color);
gboolean theme_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba);
gboolean theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba);
gboolean theme_get_color_rgb16 (ThemeSemanticToken token, guint16 *red, guint16 *green, guint16 *blue);
+18
View File
@@ -1223,9 +1223,27 @@ theme_gtk3_init (void)
gboolean
theme_gtk3_apply_current (GError **error)
{
if (!strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_LIGHT) || !strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK))
{
/* GTK ships Adwaita on every supported platform. Explicit Light/Dark
* choices must not depend on the desktop theme providing both variants. */
GtkSettings *settings = gtk_settings_get_default ();
theme_gtk3_disable ();
if (settings)
settings_apply_property (settings, "gtk-theme-name", "Adwaita");
settings_apply_for_variant (!strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK) ?
THEME_GTK3_VARIANT_PREFER_DARK : THEME_GTK3_VARIANT_PREFER_LIGHT);
return TRUE;
}
if (!prefs.hex_gui_gtk3_theme[0])
{
theme_gtk3_disable ();
/* Restoring a previous theme's settings must not undo an explicit
* appearance choice, including when a settings notification re-enters. */
settings_apply_for_variant (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO ?
THEME_GTK3_VARIANT_FOLLOW_SYSTEM : prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_DARK ?
THEME_GTK3_VARIANT_PREFER_DARK : THEME_GTK3_VARIANT_PREFER_LIGHT);
return TRUE;
}
+3
View File
@@ -24,6 +24,9 @@
#include <glib.h>
#include <gdk/gdk.h>
#define THEME_GTK3_BUILTIN_LIGHT "builtin:light"
#define THEME_GTK3_BUILTIN_DARK "builtin:dark"
typedef enum
{
THEME_GTK3_VARIANT_FOLLOW_SYSTEM = 0,
File diff suppressed because it is too large. Load diff
+3
View File
@@ -37,5 +37,8 @@ void theme_preferences_stage_begin (void);
void theme_preferences_stage_apply (void);
void theme_preferences_stage_commit (void);
void theme_preferences_stage_discard (void);
void theme_preferences_stage_revert_runtime (void);
GtkWidget *theme_preferences_create_color_editor (GtkWindow *parent, gboolean *color_change_flag);
gboolean theme_preferences_color_editor_stage (GtkWidget *editor, GError **error);
#endif
+31
View File
@@ -367,6 +367,37 @@ theme_runtime_get_xtext_colors_mapped (const ThemeGtkPaletteMap *gtk_map, XTextC
theme_palette_to_xtext_colors (&mapped_palette, palette, palette_len);
}
gboolean
theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color)
{
if (token < 0 || token >= THEME_TOKEN_COUNT || !light_custom_tokens[token])
return FALSE;
return theme_palette_get_color (&light_palette, token, color);
}
gboolean
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
{
ThemePalette defaults;
gboolean custom[THEME_TOKEN_COUNT] = { FALSE };
theme_palette_from_legacy_colors (&defaults, legacy_light_defaults, G_N_ELEMENTS (legacy_light_defaults));
theme_runtime_apply_gtk_map (&defaults, map, custom);
return theme_palette_get_color (&defaults, token, color);
}
void
theme_runtime_reset_user_color (ThemeSemanticToken token)
{
GdkRGBA color;
if (!theme_runtime_get_default_color (token, NULL, &color))
return;
if (!user_colors_valid)
light_palette = active_palette;
theme_palette_set_color (&light_palette, token, &color);
light_custom_tokens[token] = FALSE;
user_colors_valid = TRUE;
}
void
theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col)
{
+3
View File
@@ -44,6 +44,9 @@ gboolean theme_runtime_save_finalize (const char *temp_path);
void theme_runtime_save_discard (const char *temp_path);
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed);
gboolean theme_runtime_apply_dark_mode (gboolean enable);
gboolean theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color);
void theme_runtime_reset_user_color (ThemeSemanticToken token);
gboolean theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color);
void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col);
void theme_runtime_dark_set_color (ThemeSemanticToken token, const GdkRGBA *col);
void theme_runtime_reset_mode_colors (gboolean dark_mode);