From 05ee0dbdb5f7182716cc7de8858ceef5050b0f73 Mon Sep 17 00:00:00 2001 From: deepend-tildeclub Date: Thu, 16 Jul 2026 08:48:33 -0600 Subject: [PATCH] Reduce needed client restarts for settings changes. --- src/fe-gtk/chanview.c | 103 ++++++++++++ src/fe-gtk/chanview.h | 4 + src/fe-gtk/maingui.c | 233 ++++++++++++++++++++++++--- src/fe-gtk/maingui.h | 18 +++ src/fe-gtk/setup.c | 112 ++++++++----- src/fe-gtk/theme/theme-preferences.c | 34 ++++ src/fe-gtk/userlistgui.c | 88 +++++++++- src/fe-gtk/userlistgui.h | 3 + 8 files changed, 532 insertions(+), 63 deletions(-) diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c index 18dd772a..77caa9bd 100644 --- a/src/fe-gtk/chanview.c +++ b/src/fe-gtk/chanview.c @@ -307,6 +307,109 @@ chanview_set_impl (chanview *cv, int type) cv->func_focus (cv->focused); } +void +chanview_set_sorted (chanview *cv, gboolean sorted) +{ + cv->sorted = sorted ? 1 : 0; +} + +void +chanview_set_use_icons (chanview *cv, gboolean use_icons) +{ + cv->use_icons = use_icons ? 1 : 0; +} + +void +chanview_set_trunc_len (chanview *cv, int trunc_len) +{ + cv->trunc_len = trunc_len; +} + +typedef struct +{ + chan *ch; + int old_pos; +} chanview_sort_entry; + +/* re-sort the session rows below one parent with cb_compare, leaving + * utility rows (tag != 0) in place; mirrors chanview_insert_sorted() */ +static void +chanview_resort_children (chanview *cv, GtkTreeIter *parent) +{ + GtkTreeModel *model = GTK_TREE_MODEL (cv->store); + GtkTreeIter iter; + chanview_sort_entry *entries; + int *positions; + gint *new_order; + chan *ch; + int n, i, j, count; + + n = gtk_tree_model_iter_n_children (model, parent); + if (n < 2) + return; + + entries = g_new0 (chanview_sort_entry, n); + positions = g_new0 (int, n); + new_order = g_new0 (gint, n); + + count = 0; + for (i = 0; i < n; i++) + { + new_order[i] = i; + if (!gtk_tree_model_iter_nth_child (model, &iter, parent, i)) + continue; + gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1); + if (ch && ch->tag == 0 && ch->userdata) + { + entries[count].ch = ch; + entries[count].old_pos = i; + positions[count] = i; + count++; + } + } + + /* stable insertion sort; tab groups stay small so this is cheap */ + for (i = 1; i < count; i++) + { + chanview_sort_entry key = entries[i]; + + j = i - 1; + while (j >= 0 && cv->cb_compare (entries[j].ch->userdata, key.ch->userdata) > 0) + { + entries[j + 1] = entries[j]; + j--; + } + entries[j + 1] = key; + } + + for (i = 0; i < count; i++) + new_order[positions[i]] = entries[i].old_pos; + + gtk_tree_store_reorder (cv->store, parent, new_order); + + g_free (entries); + g_free (positions); + g_free (new_order); +} + +/* top-level rows keep their order (insertion never sorts them either); + * only the children within each family are sorted */ +void +chanview_resort (chanview *cv) +{ + GtkTreeIter iter; + + if (!cv->cb_compare) + return; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter)) + { + do + chanview_resort_children (cv, &iter); + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } +} + static void chanview_free_ch (chanview *cv, GtkTreeIter *iter) { diff --git a/src/fe-gtk/chanview.h b/src/fe-gtk/chanview.h index 04876539..767c9089 100644 --- a/src/fe-gtk/chanview.h +++ b/src/fe-gtk/chanview.h @@ -34,6 +34,10 @@ void chanview_set_callbacks (chanview *cv, gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *), int (*cb_compare) (void *a, void *b)); void chanview_set_impl (chanview *cv, int type); +void chanview_set_sorted (chanview *cv, gboolean sorted); +void chanview_set_use_icons (chanview *cv, gboolean use_icons); +void chanview_set_trunc_len (chanview *cv, int trunc_len); +void chanview_resort (chanview *cv); chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon); int chanview_get_size (chanview *cv); GtkWidget *chanview_get_box (chanview *cv); diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index eba7b975..f196ae9e 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -569,6 +569,18 @@ mg_create_tab_colors (void) away_list = mg_attr_list_create (&gui_palette[THEME_TOKEN_TAB_AWAY], FALSE); } +static PangoAttrList * +mg_attr_list_for_tab_state (int state) +{ + if (state & TAB_STATE_NEW_HILIGHT) + return nickseen_list; + if (state & TAB_STATE_NEW_MSG) + return newmsg_list; + if (state & TAB_STATE_NEW_DATA) + return newdata_list; + return plain_list; +} + static void set_window_urgency (GtkWidget *win, gboolean set) { @@ -714,7 +726,10 @@ mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away) { if (gui->op_xpm) { - if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */ + /* no change? (the pref check makes sure a live settings + change can still remove the icon) */ + if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm)) && + prefs.hex_gui_input_icon) { mg_set_myself_away (gui, away); return; @@ -3596,6 +3611,48 @@ mg_create_topicbar (session *sess, GtkWidget *box) mg_create_chanmodebuttons (gui, bbox); } +/* re-apply the multi-line topic and inline mode button preferences to an + * already built topic bar (see mg_create_topicbar () for the layout) */ + +static void +mg_update_topicbar_layout (session_gui *gui) +{ + GtkWidget *topic = gui->topic_entry; + GtkWidget *vbox = gui->topic_bar; + GtkWidget *topic_scroll; + GtkWidget *hbox; + GtkWidget *mode_hbox; + GtkWidget *desired_parent; + GtkWidget *current_parent; + gboolean wrap; + + if (!topic || !GTK_IS_TEXT_VIEW (topic) || !vbox || !gui->topicbutton_box) + return; + + wrap = prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline; + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic), + wrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE); + + topic_scroll = gtk_widget_get_parent (topic); + hbox = topic_scroll ? gtk_widget_get_parent (topic_scroll) : NULL; + mode_hbox = gtk_widget_get_parent (gui->topicbutton_box); + + if (mode_hbox && hbox) + { + desired_parent = prefs.hex_gui_mode_buttons_inline ? hbox : vbox; + current_parent = gtk_widget_get_parent (mode_hbox); + if (current_parent && current_parent != desired_parent) + { + g_object_ref (mode_hbox); + gtk_container_remove (GTK_CONTAINER (current_parent), mode_hbox); + gtk_box_pack_start (GTK_BOX (desired_parent), mode_hbox, 0, 0, 0); + g_object_unref (mode_hbox); + } + } + + mg_topicbar_update_height (topic); +} + /* check if a word is clickable */ static int @@ -4004,8 +4061,12 @@ mg_create_userlist (session_gui *gui, GtkWidget *box) gtk_widget_set_margin_end (gui->namelistinfo, 0); gtk_widget_set_hexpand (gui->namelistinfo, TRUE); gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_FILL); + /* always pack it so the preference can be toggled without a + restart; visibility is controlled explicitly */ + gtk_widget_set_no_show_all (gui->namelistinfo, TRUE); + gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0); if (prefs.hex_gui_ulist_count) - gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0); + gtk_widget_show (gui->namelistinfo); gui->user_tree = ulist = userlist_create (vbox); @@ -5233,6 +5294,10 @@ mg_apply_setup (void) sess = list->data; gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text); ((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE; + /* the attribute lists were just recreated, so re-apply the + color matching each tab's activity state */ + if (sess->res->tab) + chan_set_color (sess->res->tab, mg_attr_list_for_tab_state (sess->tab_state)); if (!sess->gui->is_tab || !done_main) mg_place_userlist_and_chanview (sess->gui); if (sess->gui->is_tab) @@ -5275,6 +5340,49 @@ fe_buttons_update (session *sess) gtk_widget_hide (sess->gui->button_box); } +/* tab label for a session that has no joined channel: the channel we are + * waiting to join in parentheses, or "". tbuf must hold CHANLEN+6. */ + +static void +mg_tab_pending_name (session *sess, char *tbuf) +{ + if (sess->waitchannel[0]) + { + if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc) + { + /* truncate long channel names */ + tbuf[0] = '('; + strcpy (tbuf + 1, sess->waitchannel); + g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0; + strcat (tbuf, "..)"); + } else + { + sprintf (tbuf, "(%s)", sess->waitchannel); + } + } + else + strcpy (tbuf, _("")); +} + +/* re-apply the tab label truncation preference to a session's tab */ + +static void +mg_retrunc_tab (session *sess) +{ + char tbuf[CHANLEN+6]; + + if (!sess->res->tab) + return; + + if (sess->channel[0]) + chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc); + else + { + mg_tab_pending_name (sess, tbuf); + chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc); + } +} + void fe_clear_channel (session *sess) { @@ -5283,22 +5391,7 @@ fe_clear_channel (session *sess) if (sess->gui->is_tab) { - if (sess->waitchannel[0]) - { - if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc) - { - /* truncate long channel names */ - tbuf[0] = '('; - strcpy (tbuf + 1, sess->waitchannel); - g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0; - strcat (tbuf, "..)"); - } else - { - sprintf (tbuf, "(%s)", sess->waitchannel); - } - } - else - strcpy (tbuf, _("")); + mg_tab_pending_name (sess, tbuf); chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc); } @@ -5422,6 +5515,110 @@ fe_set_channel (session *sess) chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc); } +static void +mg_apply_live_prefs_to_gui (session_gui *gui, session *sess, const mg_live_prefs *changes) +{ + if (changes->meters) + mg_update_meters (gui); + + if (changes->ulist_columns && gui->user_tree) + userlist_rebuild_columns (gui->user_tree); + + if (changes->ulist_count && gui->namelistinfo) + { + if (prefs.hex_gui_ulist_count) + gtk_widget_show (gui->namelistinfo); + else + gtk_widget_hide (gui->namelistinfo); + } + + if (changes->input_box && gui->nick_box) + { + if (prefs.hex_gui_input_nick) + gtk_widget_show (gui->nick_box); + else + gtk_widget_hide (gui->nick_box); + } + + /* refresh the access icon; re-filling the user list rows can + clear it as a side effect, so restore it here too */ + if ((changes->input_box || changes->ulist_rows) && sess && is_session (sess)) + { + if (sess->type == SESS_DIALOG) + mg_set_access_icon (gui, NULL, sess->server->is_away); + else + mg_set_access_icon (gui, get_user_icon (sess->server, sess->me), sess->server->is_away); + } + + if (changes->topic_bar) + mg_update_topicbar_layout (gui); + + if (changes->transparency && gui->window) + gtk_widget_set_opacity (gui->window, (prefs.hex_gui_transparency / 255.)); +} + +/* apply preference changes that used to require a restart. called by + * setup.c after the new prefs have been committed. */ + +void +mg_apply_live_prefs (const mg_live_prefs *changes) +{ + GSList *list; + session *sess; + int done_main = FALSE; + + if (!changes) + return; + + /* the tab bar / channel tree lives in the shared tab window */ + if (mg_gui && mg_gui->chanview) + { + if (changes->tab_resort) + { + chanview_set_sorted (mg_gui->chanview, prefs.hex_gui_tab_sort); + chanview_resort (mg_gui->chanview); + } + if (changes->tab_trunc) + chanview_set_trunc_len (mg_gui->chanview, prefs.hex_gui_tab_trunc); + if (changes->chanview || changes->tab_resort) + { + chanview_set_use_icons (mg_gui->chanview, + prefs.hex_gui_tab_icons && (pix_tree_channel || pix_tree_dialog || + pix_tree_server || pix_tree_util)); + /* rebuild the tab bar / tree widgets from the model */ + chanview_set_impl (mg_gui->chanview, prefs.hex_gui_tab_layout); + } + } + + list = sess_list; + while (list) + { + sess = list->data; + + if (changes->tab_trunc) + mg_retrunc_tab (sess); + if (changes->ulist_sort) + userlist_apply_sort (sess); + if (changes->ulist_rows) + userlist_refill (sess); + + if (sess->gui->is_tab) + { + /* only apply to the shared tab window once, using the + front-most session for session-dependent bits */ + if (!done_main) + { + done_main = TRUE; + mg_apply_live_prefs_to_gui (sess->gui, current_tab, changes); + } + } + else + mg_apply_live_prefs_to_gui (sess->gui, sess, changes); + + list = list->next; + } +} + void fe_set_typing (session *sess, const char *nick, const char *state) { diff --git a/src/fe-gtk/maingui.h b/src/fe-gtk/maingui.h index 1e6a6fc0..7dee166f 100644 --- a/src/fe-gtk/maingui.h +++ b/src/fe-gtk/maingui.h @@ -37,6 +37,24 @@ GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, i void mg_set_title (GtkWidget *button, char *title); void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away); void mg_apply_setup (void); + +/* which parts of the GUI need a live rebuild after a preferences change */ +typedef struct +{ + unsigned int chanview:1; /* rebuild the tab bar / channel tree */ + unsigned int tab_resort:1; /* re-sort channel tabs */ + unsigned int tab_trunc:1; /* re-truncate tab labels */ + unsigned int ulist_columns:1; /* rebuild user list columns */ + unsigned int ulist_rows:1; /* re-fill user list rows */ + unsigned int ulist_sort:1; /* re-apply user list sort order */ + unsigned int ulist_count:1; /* user count label visibility */ + unsigned int meters:1; /* rebuild lag/throttle meters */ + unsigned int input_box:1; /* nick box visibility and access icon */ + unsigned int topic_bar:1; /* topic wrap mode and mode button placement */ + unsigned int transparency:1; /* main window opacity */ +} mg_live_prefs; + +void mg_apply_live_prefs (const mg_live_prefs *changes); void mg_apply_session_font_prefs (session_gui *gui); void mg_close_sess (session *); void mg_tab_close (session *sess); diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 5229da0d..e15fbd54 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -1045,28 +1045,30 @@ setup_create_spin (GtkWidget *table, int row, const setting *set) return wid; } +static guint setup_trans_apply_tag = 0; +/* the window opacity the dialog opened with, so cancelling can undo the + * live preview done by the slider */ +static int setup_transparency_snapshot; + static gint -setup_apply_trans (int *tag) +setup_apply_trans (gpointer userdata) { prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency; - gtk_widget_set_opacity (current_sess->gui->window, - (prefs.hex_gui_transparency / 255.)); + if (current_sess) + gtk_widget_set_opacity (current_sess->gui->window, + (prefs.hex_gui_transparency / 255.)); - *tag = 0; + setup_trans_apply_tag = 0; return 0; } static void setup_hscale_cb (GtkRange *wid, const setting *set) { - static int tag = 0; - setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid)); - if (tag == 0) - { - tag = g_idle_add ((GSourceFunc) setup_apply_trans, &tag); - } + if (setup_trans_apply_tag == 0) + setup_trans_apply_tag = g_idle_add ((GSourceFunc) setup_apply_trans, NULL); } static void @@ -1091,7 +1093,8 @@ setup_create_hscale (GtkWidget *table, int row, const setting *set) #ifndef WIN32 /* Windows always supports this */ /* Only used for transparency currently */ - if (!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window))) + if (current_sess && + !gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window))) gtk_widget_set_sensitive (wid, FALSE); #endif } @@ -2293,7 +2296,9 @@ setup_apply (struct zoitechatprefs *pr) ThemeChangedEvent event; struct zoitechatprefs old_prefs = prefs; int old_dark_mode = prefs.hex_gui_dark_mode; + mg_live_prefs live_changes; + memset (&live_changes, 0, sizeof (live_changes)); #define DIFF(a) (pr->a != prefs.a) @@ -2301,40 +2306,49 @@ setup_apply (struct zoitechatprefs *pr) if (DIFF (hex_gui_lang)) noapply = TRUE; #endif - if (DIFF (hex_gui_compact)) - noapply = TRUE; - if (DIFF (hex_gui_input_icon)) - noapply = TRUE; - if (DIFF (hex_gui_input_nick)) - noapply = TRUE; - if (DIFF (hex_gui_lagometer)) - noapply = TRUE; - if (DIFF (hex_gui_mode_buttons_inline)) - noapply = TRUE; - if (DIFF (hex_gui_tab_icons)) - noapply = TRUE; - if (DIFF (hex_gui_tab_closebuttons)) - noapply = TRUE; + /* server tabs are only created when a connection is opened, so + existing connections keep their current tabs until a restart; + the channel tree indentation is still re-applied below */ if (DIFF (hex_gui_tab_server)) + { noapply = TRUE; - if (DIFF (hex_gui_tab_small)) - noapply = TRUE; + live_changes.chanview = TRUE; + } + + if (DIFF (hex_gui_compact)) + { + live_changes.chanview = TRUE; + live_changes.ulist_columns = TRUE; + } + if (DIFF (hex_gui_input_icon) || DIFF (hex_gui_input_nick)) + live_changes.input_box = TRUE; + if (DIFF (hex_gui_lagometer) || DIFF (hex_gui_throttlemeter)) + live_changes.meters = TRUE; + if (DIFF (hex_gui_mode_buttons_inline) || DIFF (hex_gui_topicbar_multiline)) + live_changes.topic_bar = TRUE; + if (DIFF (hex_gui_tab_icons) || DIFF (hex_gui_tab_closebuttons) || + DIFF (hex_gui_tab_small)) + live_changes.chanview = TRUE; if (DIFF (hex_gui_tab_sort)) - noapply = TRUE; + live_changes.tab_resort = TRUE; if (DIFF (hex_gui_tab_trunc)) - noapply = TRUE; - if (DIFF (hex_gui_throttlemeter)) - noapply = TRUE; - if (DIFF (hex_gui_topicbar_multiline)) - noapply = TRUE; + live_changes.tab_trunc = TRUE; if (DIFF (hex_gui_ulist_count)) - noapply = TRUE; + live_changes.ulist_count = TRUE; if (DIFF (hex_gui_ulist_icons)) - noapply = TRUE; + live_changes.ulist_rows = TRUE; if (DIFF (hex_gui_ulist_show_hosts)) - noapply = TRUE; + { + live_changes.ulist_columns = TRUE; + live_changes.ulist_rows = TRUE; /* hostnames may be stale */ + } if (DIFF (hex_gui_ulist_sort)) - noapply = TRUE; + live_changes.ulist_sort = TRUE; + /* compare against the pre-dialog value: the slider previews the + opacity live on the current window only, so OK still needs to + apply it to the other windows */ + if (pr->hex_gui_transparency != setup_transparency_snapshot) + live_changes.transparency = TRUE; if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) && pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos) @@ -2378,6 +2392,8 @@ setup_apply (struct zoitechatprefs *pr) theme_manager_dispatch_setup_apply (&event); + mg_apply_live_prefs (&live_changes); + if (noapply) fe_message (_("Some settings were changed that require a" " restart to take full effect."), FE_MSG_WARN); @@ -2410,6 +2426,9 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win) if (save_result.success) { theme_preferences_stage_commit (); + /* the previewed values were accepted, so the close handler + must not revert them */ + setup_transparency_snapshot = prefs.hex_gui_transparency; gtk_widget_destroy (win); return; } @@ -2472,6 +2491,24 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin) { *swin = NULL; + /* drop any pending transparency preview and undo the previewed + opacity when the dialog is cancelled; after OK the snapshot + was updated so this is a no-op */ + if (setup_trans_apply_tag) + { + g_source_remove (setup_trans_apply_tag); + setup_trans_apply_tag = 0; + } + if (prefs.hex_gui_transparency != setup_transparency_snapshot) + { + mg_live_prefs revert_changes; + + prefs.hex_gui_transparency = setup_transparency_snapshot; + memset (&revert_changes, 0, sizeof (revert_changes)); + revert_changes.transparency = TRUE; + mg_apply_live_prefs (&revert_changes); + } + theme_preferences_stage_discard (); if (font_dialog) @@ -2493,6 +2530,7 @@ setup_open (void) memcpy (&setup_prefs, &prefs, sizeof (prefs)); color_change = FALSE; + setup_transparency_snapshot = prefs.hex_gui_transparency; theme_preferences_stage_begin (); setup_window = setup_window_open (); diff --git a/src/fe-gtk/theme/theme-preferences.c b/src/fe-gtk/theme/theme-preferences.c index 6c2e0e16..4058da68 100644 --- a/src/fe-gtk/theme/theme-preferences.c +++ b/src/fe-gtk/theme/theme-preferences.c @@ -106,6 +106,11 @@ typedef struct gboolean staged_dirty[THEME_TOKEN_COUNT]; GdkRGBA snapshot[THEME_TOKEN_COUNT]; GdkRGBA staged[THEME_TOKEN_COUNT]; + /* the GTK3 theme selection is previewed live from the combo, so + * remember what was active when the dialog opened in order to + * restore it when the dialog is cancelled */ + char gtk3_theme_snapshot[sizeof ((struct zoitechatprefs *) 0)->hex_gui_gtk3_theme]; + int gtk3_variant_snapshot; } theme_preferences_stage_state; static theme_preferences_stage_state theme_preferences_stage; @@ -204,6 +209,11 @@ theme_preferences_stage_begin (void) memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); theme_preferences_stage.active = TRUE; + g_strlcpy (theme_preferences_stage.gtk3_theme_snapshot, + prefs.hex_gui_gtk3_theme, + sizeof (theme_preferences_stage.gtk3_theme_snapshot)); + theme_preferences_stage.gtk3_variant_snapshot = prefs.hex_gui_gtk3_variant; + for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++) { GdkRGBA rgba; @@ -237,12 +247,36 @@ theme_preferences_stage_commit (void) memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); } +/* undo a live GTK3 theme preview: put the selection that was active when + * the dialog opened back and re-apply it */ +static void +theme_preferences_stage_restore_gtk3 (void) +{ + if (g_strcmp0 (prefs.hex_gui_gtk3_theme, theme_preferences_stage.gtk3_theme_snapshot) == 0 && + prefs.hex_gui_gtk3_variant == theme_preferences_stage.gtk3_variant_snapshot) + return; + + g_strlcpy (prefs.hex_gui_gtk3_theme, + theme_preferences_stage.gtk3_theme_snapshot, + sizeof (prefs.hex_gui_gtk3_theme)); + prefs.hex_gui_gtk3_variant = theme_preferences_stage.gtk3_variant_snapshot; + + theme_gtk3_apply_current (NULL); + theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK | + THEME_CHANGED_REASON_PALETTE | + THEME_CHANGED_REASON_WIDGET_STYLE | + THEME_CHANGED_REASON_USERLIST | + THEME_CHANGED_REASON_MODE); +} + void theme_preferences_stage_discard (void) { if (!theme_preferences_stage.active) return; + /* restore the GTK3 theme first so the color snapshot below wins */ + theme_preferences_stage_restore_gtk3 (); theme_preferences_stage_sync_runtime_to_snapshot (); memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage)); } diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c index e8a6d845..79760811 100644 --- a/src/fe-gtk/userlistgui.c +++ b/src/fe-gtk/userlistgui.c @@ -796,16 +796,12 @@ userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken } } -GtkListStore * -userlist_create_model (session *sess) +static void +userlist_model_set_sort (GtkListStore *store, session *sess) { - GtkListStore *store; GtkTreeIterCompareFunc cmp_func; GtkSortType sort_type; - store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, - G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE); - switch (prefs.hex_gui_ulist_sort) { case 0: @@ -825,18 +821,77 @@ userlist_create_model (session *sess) sort_type = GTK_SORT_DESCENDING; break; default: - /* No sorting */ + /* No sorting; rows keep their current order and new rows + go wherever they are inserted */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store), + GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), NULL, NULL, NULL); - return store; + return; } gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), cmp_func, sess, NULL); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, sort_type); +} + +GtkListStore * +userlist_create_model (session *sess) +{ + GtkListStore *store; + + store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE); + userlist_model_set_sort (store, sess); return store; } +void +userlist_apply_sort (session *sess) +{ + if (!sess->res || !sess->res->user_model) + return; + + userlist_model_set_sort (sess->res->user_model, sess); +} + +/* remove and re-insert every row, so per-row content that depends on + * preferences (mode icons vs. text prefixes, hostnames) is rebuilt */ +void +userlist_refill (session *sess) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GSList *users = NULL; + GSList *it; + struct User *user; + + if (!sess->res || !sess->res->user_model) + return; + + model = GTK_TREE_MODEL (sess->res->user_model); + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_USER, &user, -1); + if (user) + users = g_slist_prepend (users, user); + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + fe_userlist_clear (sess); + + /* fe_userlist_insert() prepends, so inserting in reverse display + order restores the on-screen order for unsorted lists; sorted + models position the rows themselves */ + for (it = users; it; it = it->next) + fe_userlist_insert (sess, it->data, FALSE); + + g_slist_free (users); +} + static void userlist_add_columns (GtkTreeView * treeview) { @@ -906,6 +961,23 @@ userlist_add_columns (GtkTreeView * treeview) } } +void +userlist_rebuild_columns (GtkWidget *treeview) +{ + GList *columns; + GList *it; + + if (!treeview) + return; + + columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (treeview)); + for (it = columns; it; it = it->next) + gtk_tree_view_remove_column (GTK_TREE_VIEW (treeview), it->data); + g_list_free (columns); + + userlist_add_columns (GTK_TREE_VIEW (treeview)); +} + static gint userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata) { diff --git a/src/fe-gtk/userlistgui.h b/src/fe-gtk/userlistgui.h index 393ea44b..75143521 100644 --- a/src/fe-gtk/userlistgui.h +++ b/src/fe-gtk/userlistgui.h @@ -24,6 +24,9 @@ void userlist_set_value (GtkWidget *treeview, gfloat val); gfloat userlist_get_value (GtkWidget *treeview); GtkWidget *userlist_create (GtkWidget *box); GtkListStore *userlist_create_model (session *sess); +void userlist_apply_sort (session *sess); +void userlist_refill (session *sess); +void userlist_rebuild_columns (GtkWidget *treeview); void userlist_show (session *sess); void userlist_select (session *sess, char *name); void fe_userlist_set_typing (session *sess, const char *nick, const char *state);