From c51f885253d9e6185adec25116198fcb0a29c259 Mon Sep 17 00:00:00 2001 From: ThePeGaSuS <25697531+TehPeGaSuS@users.noreply.github.com> Date: Tue, 6 Oct 2026 20:03:08 +0200 Subject: [PATCH] add option to align wrapped lines under the nick or message When nick indentation is off, lines after the first wrap back to the left edge, under the timestamp. Add a text_wrap_align preference (start of line, prefix, message) so they can line up under the nick or the message text instead. It is applied at wrap time, so existing scrollback is re-wrapped when the setting changes, and the menu is greyed out while nick indentation is on. --- src/common/cfgfiles.c | 2 ++ src/common/zoitechat.h | 1 + src/fe-gtk/setup.c | 38 ++++++++++++++++++++++++++++++++ src/fe-gtk/textgui.c | 2 +- src/fe-gtk/xtext.c | 50 ++++++++++++++++++++++++++++++++++++++---- src/fe-gtk/xtext.h | 1 + 6 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c index f01cb479..663b6051 100644 --- a/src/common/cfgfiles.c +++ b/src/common/cfgfiles.c @@ -590,6 +590,7 @@ const struct prefs vars[] = {"gui_gtk3_theme", P_OFFSET (hex_gui_gtk3_theme), TYPE_STR}, {"text_indent", P_OFFINT (hex_text_indent), TYPE_BOOL}, {"text_max_indent", P_OFFINT (hex_text_max_indent), TYPE_INT}, + {"text_wrap_align", P_OFFINT (hex_text_wrap_align), TYPE_INT}, {"text_max_lines", P_OFFINT (hex_text_max_lines), TYPE_INT}, {"text_replay", P_OFFINT (hex_text_replay), TYPE_BOOL}, {"text_search_case_match", P_OFFINT (hex_text_search_case_match), TYPE_BOOL}, @@ -881,6 +882,7 @@ load_default_config(void) prefs.hex_net_reconnect_delay = 10; prefs.hex_notify_timeout = 15; prefs.hex_text_max_indent = 256; + prefs.hex_text_wrap_align = 0; prefs.hex_text_max_lines = 5000; prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */ diff --git a/src/common/zoitechat.h b/src/common/zoitechat.h index b93005b5..ee60e6df 100644 --- a/src/common/zoitechat.h +++ b/src/common/zoitechat.h @@ -306,6 +306,7 @@ struct zoitechatprefs int hex_net_reconnect_delay; int hex_notify_timeout; int hex_text_max_indent; + int hex_text_wrap_align; int hex_text_max_lines; int hex_url_grabber_limit; diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c index 4fa488b9..e00c845c 100644 --- a/src/fe-gtk/setup.c +++ b/src/fe-gtk/setup.c @@ -59,6 +59,8 @@ static struct zoitechatprefs setup_prefs; static GtkWidget *cancel_button; static GtkWidget *font_dialog = NULL; static GtkWidget *setup_topicbar_multiline_toggle = NULL; +static GtkWidget *setup_wrap_align_label = NULL; +static GtkWidget *setup_wrap_align_combo = NULL; enum { @@ -157,6 +159,14 @@ static const char *const langsmenu[] = }; #endif +static const char *const wrapalignmenu[] = +{ + N_("Start of line (default)"), + N_("Prefix (nick name)"), + N_("Message"), + NULL +}; + static const setting appearance_settings[] = { {ST_HEADER, N_("General"),0,0,0}, @@ -172,6 +182,7 @@ static const setting appearance_settings[] = {ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(hex_text_indent), N_("Make nick names right-justified"),0,0}, {ST_TOGGLE, N_ ("Show marker line"), P_OFFINTNL (hex_text_show_marker), N_ ("Insert a red line after the last read text."), 0, 0}, {ST_TOGGLE, N_("Keep search position when closing"), P_OFFINTNL(hex_text_search_keep_position), 0, 0, 0}, + {ST_MENU, N_("Align wrapped lines under:"), P_OFFINTNL(hex_text_wrap_align), N_("Where wrapped lines start when nick names are not indented"), wrapalignmenu, 0}, {ST_HEADER, N_("Timestamps"),0,0,0}, {ST_TOGGLE, N_("Enable timestamps"), P_OFFINTNL(hex_stamp_text),0,0,1}, @@ -857,6 +868,20 @@ setup_topicbar_inline_toggled_cb (GtkToggleButton *but, gpointer userdata) !gtk_toggle_button_get_active (but)); } +static void +setup_text_indent_toggled_cb (GtkToggleButton *but, gpointer userdata) +{ + gboolean sens = !gtk_toggle_button_get_active (but); + + (void) userdata; + + /* wrapped line alignment only applies without nick indentation */ + if (setup_wrap_align_label) + gtk_widget_set_sensitive (setup_wrap_align_label, sens); + if (setup_wrap_align_combo) + gtk_widget_set_sensitive (setup_wrap_align_combo, sens); +} + static void setup_create_toggleR (GtkWidget *tab, int row, const setting *set) { @@ -867,6 +892,9 @@ setup_create_toggleR (GtkWidget *tab, int row, const setting *set) setup_get_int (&setup_prefs, set)); g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_text_indent)) + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_text_indent_toggled_cb), NULL); if (set->tooltip) gtk_widget_set_tooltip_text (wid, _(set->tooltip)); setup_table_attach (tab, wid, 4, 5, row, row + 1, TRUE, FALSE, @@ -1279,6 +1307,16 @@ setup_create_menu (GtkWidget *table, int row, const setting *set) setup_table_attach (table, cbox, 3, 6, row, row + 1, TRUE, FALSE, SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0); + + if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_text_wrap_align)) + { + setup_wrap_align_label = wid; + setup_wrap_align_combo = cbox; + g_object_add_weak_pointer (G_OBJECT (wid), (gpointer *)&setup_wrap_align_label); + g_object_add_weak_pointer (G_OBJECT (cbox), (gpointer *)&setup_wrap_align_combo); + gtk_widget_set_sensitive (wid, !setup_prefs.hex_text_indent); + gtk_widget_set_sensitive (cbox, !setup_prefs.hex_text_indent); + } } static void diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c index 3268a7c5..9b6baf3f 100644 --- a/src/fe-gtk/textgui.c +++ b/src/fe-gtk/textgui.c @@ -92,7 +92,7 @@ PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, ti memcpy (new_text, stamp, stamp_size); g_free (stamp); memcpy (new_text + stamp_size, text, len); - gtk_xtext_append (xtbuf, new_text, len + stamp_size, timet); + gtk_xtext_append_wrap (xtbuf, new_text, len + stamp_size, timet, stamp_size); g_free (new_text); } else gtk_xtext_append (xtbuf, text, len, timet); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index c9235567..4baff180 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -81,6 +81,7 @@ struct textentry gint16 mark_end; int indent; gint16 left_len; + gint16 stamp_len; /* bytes of str taken by the timestamp (no-indent mode) */ GSList *slp; GArray *sublines; /* byte offsets at the end of each screen row */ int subline_count; @@ -296,6 +297,7 @@ static void gtk_xtext_get_property (GObject *object, guint prop_id, static void gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *); static void gtk_xtext_recalc_widths (xtext_buffer *buf, int); static void gtk_xtext_fix_indent (xtext_buffer *buf); +static int gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len); static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line); /* For use by gtk_xtext_strip_color() and its callers -- */ struct offlen_s { @@ -1322,6 +1324,36 @@ gtk_xtext_selection_clear (xtext_buffer *buf) return ret; } +/* x position that the lines after the first of 'ent' start at */ + +static int +gtk_xtext_cont_indent (xtext_buffer *buf, textentry *ent) +{ + int w, off; + + if (ent->left_len != -1 || prefs.hex_text_wrap_align < 1) + return buf->indent; + + /* 1 = under the prefix (nick), 2 = under the message */ + off = ent->stamp_len; + if (prefs.hex_text_wrap_align >= 2) + { + /* the nick (or "*") is the first word after the timestamp */ + while (off < ent->str_len && ent->str[off] != ' ') + off++; + while (off < ent->str_len && ent->str[off] == ' ') + off++; + } + if (off <= 0 || off >= ent->str_len) + return buf->indent; + + w = ent->indent + gtk_xtext_text_width (buf->xtext, ent->str, off); + /* leave room for the text, or just wrap normally */ + if (w > (buf->window_width - MARGIN) / 2) + return buf->indent; + return w; +} + static int find_x (GtkXText *xtext, textentry *ent, int x, int subline, int indent) { @@ -1414,7 +1446,7 @@ gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline, if (subline < 1) indent = ent->indent; else - indent = xtext->buffer->indent; + indent = gtk_xtext_cont_indent (xtext->buffer, ent); if (line > gtk_adjustment_get_page_size (xtext->adj) || line < 0) { @@ -4035,7 +4067,7 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line, taken--; } - indent = xtext->buffer->indent; + indent = gtk_xtext_cont_indent (xtext->buffer, ent); line++; taken++; str += len; @@ -4296,7 +4328,7 @@ gtk_xtext_lines_taken (xtext_buffer *buf, textentry *ent) str += len; end = str - ent->str; g_array_append_val (ent->sublines, end); - indent = buf->indent; + indent = gtk_xtext_cont_indent (buf, ent); } while (str < ent->str + ent->str_len); } @@ -5376,6 +5408,7 @@ gtk_xtext_append_indent (xtext_buffer *buf, prefix_width = gtk_xtext_text_width (buf->xtext, str, left_len + 1); ent->left_len = left_len; + ent->stamp_len = 0; ent->str = str; ent->str_len = left_len + 1 + right_len; ent->indent = buf->indent - prefix_width; @@ -5421,6 +5454,14 @@ gtk_xtext_append_indent (xtext_buffer *buf, void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp) +{ + gtk_xtext_append_wrap (buf, text, len, stamp, 0); +} + +/* stamp_len: how many bytes at the start of text are the timestamp */ + +void +gtk_xtext_append_wrap (xtext_buffer *buf, unsigned char *text, int len, time_t stamp, int stamp_len) { textentry *ent; @@ -5438,6 +5479,7 @@ gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp) memcpy (ent->str, text, len); ent->indent = 0; ent->left_len = -1; + ent->stamp_len = (stamp_len > 0 && stamp_len < ent->str_len) ? stamp_len : 0; gtk_xtext_append_entry (buf, ent, stamp); } @@ -5474,7 +5516,7 @@ gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area) } else { - gtk_xtext_append (out, ent->str, ent->str_len, 0); + gtk_xtext_append_wrap (out, ent->str, ent->str_len, 0, ent->stamp_len); } if (out->text_last) diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index fdd442a9..eec6178f 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -281,6 +281,7 @@ struct _GtkXTextClass GtkWidget *gtk_xtext_new (const XTextColor *palette, int separator); void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len, time_t stamp); +void gtk_xtext_append_wrap (xtext_buffer *buf, unsigned char *text, int len, time_t stamp, int stamp_len); void gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *left_text, int left_len, unsigned char *right_text, int right_len,