diff --git a/src/common/fe.h b/src/common/fe.h index 2790dfa7..a50d6daf 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -23,6 +23,8 @@ #ifndef ZOITECHAT_FE_H #define ZOITECHAT_FE_H +struct reply_item; + /* for storage of /menu entries */ typedef struct { @@ -110,6 +112,8 @@ void fe_progressbar_start (struct session *sess); void fe_progressbar_end (struct server *serv); void fe_print_text (struct session *sess, char *text, time_t stamp, gboolean no_activity); +void fe_print_text_with_message (struct session *sess, char *text, time_t stamp, + gboolean no_activity, const struct reply_item *message); void fe_userlist_insert (struct session *sess, struct User *newuser, gboolean sel); int fe_userlist_remove (struct session *sess, struct User *user); void fe_userlist_rehash (struct session *sess, struct User *user); diff --git a/src/common/inbound.c b/src/common/inbound.c index 638661d5..3207d07c 100644 --- a/src/common/inbound.c +++ b/src/common/inbound.c @@ -313,6 +313,20 @@ inbound_make_idtext (server *serv, char *idtext, int max, int id) } } +/* Pass message identity alongside the formatted event, outside plugin output. + * Private messages displayed in another channel have no reply destination here. */ +static void +inbound_emit_message (int event, session *sess, char *from, char *text, + char *c, char *d, const message_tags_data *tags_data) +{ + reply_item message = {tags_data->msgid, from, text, tags_data->timestamp}; + gboolean can_reply = reply_msgid_valid (message.msgid) && + event != XP_TE_PRIVMSG && event != XP_TE_PRIVACTION; + + text_emit_with_message (event, sess, from, text, c, d, tags_data->timestamp, + can_reply ? &message : NULL); +} + void inbound_privmsg (server *serv, char *from, char *ip, char *text, int id, const message_tags_data *tags_data) @@ -362,11 +376,9 @@ inbound_privmsg (server *serv, char *from, char *ip, char *text, int id, inbound_make_idtext (serv, idtext, sizeof (idtext), id); if (sess->type == SESS_DIALOG && !nodiag) - EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0, - tags_data->timestamp); + inbound_emit_message (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, tags_data); else - EMIT_SIGNAL_TIMESTAMP (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, 0, - tags_data->timestamp); + inbound_emit_message (XP_TE_PRIVMSG, sess, from, text, idtext, NULL, tags_data); } /* used for Alerts section. Masks can be separated by commas and spaces. */ @@ -608,24 +620,21 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text, { if (is_hilight (from, text, sess, serv)) { - EMIT_SIGNAL_TIMESTAMP (XP_TE_HCHANACTION, sess, from, text, nickchar, - idtext, 0, tags_data->timestamp); + inbound_emit_message (XP_TE_HCHANACTION, sess, from, text, nickchar, + idtext, tags_data); return; } } if (fromme) - EMIT_SIGNAL_TIMESTAMP (XP_TE_UACTION, sess, from, text, nickchar, idtext, - 0, tags_data->timestamp); + inbound_emit_message (XP_TE_UACTION, sess, from, text, nickchar, idtext, tags_data); else if (!privaction) - EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANACTION, sess, from, text, nickchar, - idtext, 0, tags_data->timestamp); + inbound_emit_message (XP_TE_CHANACTION, sess, from, text, nickchar, + idtext, tags_data); else if (sess->type == SESS_DIALOG) - EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, - 0, tags_data->timestamp); + inbound_emit_message (XP_TE_DPRIVACTION, sess, from, text, idtext, NULL, tags_data); else - EMIT_SIGNAL_TIMESTAMP (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, 0, - tags_data->timestamp); + inbound_emit_message (XP_TE_PRIVACTION, sess, from, text, idtext, NULL, tags_data); } void @@ -679,8 +688,7 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from, (!tags_data->timestamp || serv->have_echo_message)) sess->server->p_set_back (sess->server); reply_context_print (sess, tags_data); - EMIT_SIGNAL_TIMESTAMP (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, - 0, tags_data->timestamp); + inbound_emit_message (XP_TE_UCHANMSG, sess, from, text, nickchar, NULL, tags_data); reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp); return; } @@ -693,14 +701,11 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from, reply_context_print (sess, tags_data); if (sess->type == SESS_DIALOG) - EMIT_SIGNAL_TIMESTAMP (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, 0, - tags_data->timestamp); + inbound_emit_message (XP_TE_DPRIVMSG, sess, from, text, idtext, NULL, tags_data); else if (hilight) - EMIT_SIGNAL_TIMESTAMP (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, - 0, tags_data->timestamp); + inbound_emit_message (XP_TE_HCHANMSG, sess, from, text, nickchar, idtext, tags_data); else - EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, - 0, tags_data->timestamp); + inbound_emit_message (XP_TE_CHANMSG, sess, from, text, nickchar, idtext, tags_data); reply_cache_add (sess, tags_data->msgid, from, text, tags_data->timestamp); } diff --git a/src/common/text.c b/src/common/text.c index c2b389c4..23b0f7fe 100644 --- a/src/common/text.c +++ b/src/common/text.c @@ -834,8 +834,9 @@ G_GNUC_END_IGNORE_DEPRECATIONS #endif } -void -PrintTextTimeStamp (session *sess, char *text, time_t timestamp) +static void +PrintTextTimeStampWithMessage (session *sess, char *text, time_t timestamp, + const reply_item *message) { if (!sess) { @@ -856,10 +857,16 @@ PrintTextTimeStamp (session *sess, char *text, time_t timestamp) log_write (sess, text, timestamp); scrollback_save (sess, text, timestamp); - fe_print_text (sess, text, timestamp, FALSE); + fe_print_text_with_message (sess, text, timestamp, FALSE, message); g_free (text); } +void +PrintTextTimeStamp (session *sess, char *text, time_t timestamp) +{ + PrintTextTimeStampWithMessage (sess, text, timestamp, NULL); +} + void PrintText (session *sess, char *text) { @@ -1962,7 +1969,7 @@ display_event_string (session *sess, int event, char **args, char *format, static void display_event (session *sess, int event, char **args, - unsigned int stripcolor_args, time_t timestamp) + unsigned int stripcolor_args, time_t timestamp, const reply_item *message) { char o[4096]; char *format; @@ -1992,7 +1999,7 @@ display_event (session *sess, int event, char **args, } if (o[0]) - PrintTextTimeStamp (sess, o, timestamp); + PrintTextTimeStampWithMessage (sess, o, timestamp, message); } int @@ -2210,6 +2217,13 @@ text_color_of (char *name) void text_emit (int index, session *sess, char *a, char *b, char *c, char *d, time_t timestamp) +{ + text_emit_with_message (index, sess, a, b, c, d, timestamp, NULL); +} + +void +text_emit_with_message (int index, session *sess, char *a, char *b, char *c, char *d, + time_t timestamp, const reply_item *message) { char *word[PDIWORDS]; int i; @@ -2305,7 +2319,7 @@ text_emit (int index, session *sess, char *a, char *b, char *c, char *d, if (!prefs.hex_away_omit_alerts || !sess->server->is_away) sound_play_event (index); - display_event (sess, index, word, stripcolor_args, timestamp); + display_event (sess, index, word, stripcolor_args, timestamp, message); } char * diff --git a/src/common/text.h b/src/common/text.h index f164263e..bac1e30e 100644 --- a/src/common/text.h +++ b/src/common/text.h @@ -56,6 +56,8 @@ int text_color_of (char *name); char **text_color_event_names (int color, int *count); void text_emit (int index, session *sess, char *a, char *b, char *c, char *d, time_t timestamp); +void text_emit_with_message (int index, session *sess, char *a, char *b, char *c, char *d, + time_t timestamp, const reply_item *message); int text_emit_by_name (char *name, session *sess, time_t timestamp, char *a, char *b, char *c, char *d); gchar *text_convert_invalid (const gchar* text, gssize len, GIConv converter, const gchar *fallback, gsize *len_out); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c index 14495fdb..2b738de8 100644 --- a/src/fe-gtk/fe-gtk.c +++ b/src/fe-gtk/fe-gtk.c @@ -994,13 +994,20 @@ fe_progressbar_end (server *serv) void fe_print_text (struct session *sess, char *text, time_t stamp, gboolean no_activity) +{ + fe_print_text_with_message (sess, text, stamp, no_activity, NULL); +} + +void +fe_print_text_with_message (struct session *sess, char *text, time_t stamp, + gboolean no_activity, const reply_item *message) { /* image links become a "[view image]" placeholder with a hidden URL */ char *filtered = inline_image_filter_text (text); - PrintTextRaw (sess->res->buffer, + PrintTextRawWithMessage (sess->res->buffer, (unsigned char *)(filtered ? filtered : text), - prefs.hex_text_indent, stamp); + prefs.hex_text_indent, stamp, message); g_free (filtered); if (no_activity || !sess->gui->is_tab) diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c index b553d1f9..423cb7fa 100644 --- a/src/fe-gtk/maingui.c +++ b/src/fe-gtk/maingui.c @@ -3865,10 +3865,25 @@ mg_update_topicbar_layout (session_gui *gui) /* check if a word is clickable */ +static session * +mg_xtext_session (GtkWidget *widget) +{ + GSList *list; + + for (list = sess_list; list; list = list->next) + { + session *sess = list->data; + + if (sess->res && sess->res->buffer == GTK_XTEXT (widget)->buffer) + return sess; + } + return current_sess; +} + static int mg_word_check (GtkWidget * xtext, char *word) { - session *sess = current_sess; + session *sess = mg_xtext_session (xtext); int ret; ret = url_check_word (word); @@ -3883,7 +3898,7 @@ mg_word_check (GtkWidget * xtext, char *word) static void mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) { - session *sess = current_sess; + session *sess = mg_xtext_session (xtext); int word_type = 0, start, end; char *tmp; @@ -3963,7 +3978,9 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) case WORD_NICK: word[end] = 0; word += start; - menu_nickmenu (sess, even, word, FALSE); + menu_nickmenu_with_message (sess, even, word, FALSE, + gtk_xtext_entry_get_message (GTK_XTEXT (xtext), + gtk_xtext_get_clicked_entry (GTK_XTEXT (xtext)))); break; case WORD_CHANNEL: word[end] = 0; @@ -3978,7 +3995,9 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) g_free (tmp); break; case WORD_DIALOG: - menu_nickmenu (sess, even, sess->channel, FALSE); + menu_nickmenu_with_message (sess, even, sess->channel, FALSE, + gtk_xtext_entry_get_message (GTK_XTEXT (xtext), + gtk_xtext_get_clicked_entry (GTK_XTEXT (xtext)))); break; } } diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c index f4b9c5b9..1dc3a731 100644 --- a/src/fe-gtk/menu.c +++ b/src/fe-gtk/menu.c @@ -790,26 +790,73 @@ fe_userlist_update (session *sess, struct User *user) } -static void -menu_reply_to_latest_cb (GtkWidget *wid, gpointer data) +typedef struct { - reply_item *item; + session *sess; + char *target; + char *msgid; + char *nick; + char *text; + gboolean latest; +} reply_menu_data; - item = reply_cache_latest_from (current_sess, str_copy); - if (!item) +static void +menu_reply_data_free (gpointer data) +{ + reply_menu_data *reply = data; + g_free (reply->target); + g_free (reply->msgid); + g_free (reply->nick); + g_free (reply->text); + g_free (reply); +} + +static void +menu_reply_cb (GtkWidget *wid, gpointer data) +{ + reply_menu_data *reply = data; + session *sess = reply->sess; + reply_item selected = {reply->msgid, reply->nick, reply->text, 0}; + const reply_item *item = &selected; + GtkWidget *menu = g_object_ref (gtk_widget_get_parent (wid)); + + /* The menu may outlive a closed or retargeted tab. Never reply in whatever + * session happens to be current when the menu item is activated. */ + if (!is_session (sess) || + sess->server->p_cmp (sess->channel, reply->target)) + goto done; + if (sess != current_sess) { - PrintText (current_sess, _("No recent message to reply to.\n")); - return; + fe_ctrl_gui (sess, FE_GUI_FOCUS, 0); + if (!is_session (sess) || sess != current_sess || + sess->server->p_cmp (sess->channel, reply->target)) + goto done; + } + if (reply->latest) + item = reply_cache_latest_from (sess, reply->nick); + if (!item || !item->msgid) + { + PrintText (sess, _("No recent message to reply to.\n")); + goto done; } - reply_state_set (current_sess, item->msgid, current_sess->channel, item->nick, item->text); - mg_reply_update (current_sess); - if (current_sess->gui && current_sess->gui->input_box) - gtk_widget_grab_focus (current_sess->gui->input_box); + reply_state_set (sess, item->msgid, reply->target, item->nick, item->text); + mg_reply_update (sess); + if (sess->gui && sess->gui->input_box) + gtk_widget_grab_focus (sess->gui->input_box); +done: + g_object_unref (menu); } void menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel) +{ + menu_nickmenu_with_message (sess, event, nick, num_sel, NULL); +} + +void +menu_nickmenu_with_message (session *sess, GdkEventButton *event, char *nick, + int num_sel, const reply_item *message) { char buf[512]; struct User *user; @@ -853,7 +900,24 @@ menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel) if (num_sel <= 1) { - menu_quick_item_with_callback (menu_reply_to_latest_cb, _("Reply"), menu, 0); + /* A transcript click must identify that exact message. A user-list + * click has no message row and retains the reply-to-latest behaviour. + * Copy row data now: messages may arrive or be trimmed while open. */ + reply_menu_data *reply = g_new0 (reply_menu_data, 1); + + reply->sess = sess; + reply->target = g_strdup (sess->channel); + reply->latest = num_sel != 0; + if (reply->latest) + reply->nick = g_strdup (nick); + else if (message) + { + reply->msgid = g_strdup (message->msgid); + reply->nick = g_strdup (message->nick); + reply->text = g_strdup (message->text); + } + g_object_set_data_full (G_OBJECT (menu), "reply-message", reply, menu_reply_data_free); + menu_quick_item_with_callback (menu_reply_cb, _("Reply"), menu, reply); menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); } diff --git a/src/fe-gtk/menu.h b/src/fe-gtk/menu.h index 41a03b90..aa60e7cf 100644 --- a/src/fe-gtk/menu.h +++ b/src/fe-gtk/menu.h @@ -29,6 +29,8 @@ void menu_chanmenu (session *sess, GdkEventButton * event, char *chan); void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel, gboolean istree); void menu_addconnectmenu (server *serv, GtkWidget *menu); void menu_nickmenu (session *sess, GdkEventButton * event, char *nick, int num_sel); +void menu_nickmenu_with_message (session *sess, GdkEventButton *event, char *nick, + int num_sel, const reply_item *message); void menu_middlemenu (session *sess, GdkEventButton *event); void userlist_button_cb (GtkWidget * button, char *cmd); void nick_command_parse (session *sess, char *cmd, char *nick, char *allnick); diff --git a/src/fe-gtk/tests/test-xtext.c b/src/fe-gtk/tests/test-xtext.c index 568ddec0..301e204c 100644 --- a/src/fe-gtk/tests/test-xtext.c +++ b/src/fe-gtk/tests/test-xtext.c @@ -137,6 +137,49 @@ static void test_null_empty_prefix (Fixture *f, gconstpointer data) cairo_surface_destroy (snapshot (f, FALSE)); } +static void test_message_identity (Fixture *f, gconstpointer data) +{ + xtext_buffer *buf = f->xtext->buffer; + xtext_buffer *other; + textentry *first, *second; + char msgid[] = "message-one"; + char nick[] = "same-nick"; + char text[] = "identical text"; + reply_item message = {msgid, nick, text, 1234}; + const reply_item *saved; + + /* ZNC playback can repeat the same sender, text and server timestamp. */ + gtk_xtext_append_wrap_with_message (buf, (guchar *)text, -1, 1234, 0, &message); + first = buf->text_last; + message.msgid = "message-two"; + gtk_xtext_append_indent_with_message (buf, (guchar *)nick, -1, + (guchar *)text, -1, 1234, &message); + second = buf->text_last; + msgid[0] = nick[0] = text[0] = 'X'; + saved = gtk_xtext_entry_get_message (f->xtext, first); + g_assert_cmpstr (saved->msgid, ==, "message-one"); + g_assert_cmpstr (saved->nick, ==, "same-nick"); + g_assert_cmpstr (saved->text, ==, "identical text"); + g_assert_cmpint (saved->timestamp, ==, 1234); + g_assert_cmpstr (gtk_xtext_entry_get_message (f->xtext, second)->msgid, ==, "message-two"); + + /* Other output must not inherit a message ID, including after reflow. */ + append (f, "a plugin or status message"); + g_assert_null (gtk_xtext_entry_get_message (f->xtext, buf->text_last)); + g_assert_true (gtk_xtext_set_font (f->xtext, "monospace 18")); + g_assert_cmpstr (gtk_xtext_entry_get_message (f->xtext, first)->msgid, ==, "message-one"); + g_assert_null (gtk_xtext_entry_get_message (f->xtext, NULL)); + + /* Entries from another tab, and removed entries, are never dereferenced. */ + other = gtk_xtext_buffer_new (f->xtext); + gtk_xtext_buffer_show (f->xtext, other, TRUE); + g_assert_null (gtk_xtext_entry_get_message (f->xtext, first)); + gtk_xtext_buffer_show (f->xtext, buf, TRUE); + gtk_xtext_buffer_free (other); + gtk_xtext_clear (buf, 0); + g_assert_null (gtk_xtext_entry_get_message (f->xtext, first)); +} + static void test_wrapping (Fixture *f, gconstpointer data) { xtext_buffer *buf = f->xtext->buffer; @@ -1138,6 +1181,7 @@ int main (int argc, char **argv) ADD ("append/empty-bounded", test_empty_and_bounded_append); ADD ("append/utf8-limits", test_utf8_limits); ADD ("append/null-empty-prefix", test_null_empty_prefix); + ADD ("reply/message-identity", test_message_identity); ADD ("layout/wrapping", test_wrapping); ADD ("layout/font-isolation", test_font_isolation); ADD ("layout/wide-metrics", test_wide_metrics); diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c index 4abd7235..082c943a 100644 --- a/src/fe-gtk/textgui.c +++ b/src/fe-gtk/textgui.c @@ -69,7 +69,8 @@ xtext_get_stamp_str (time_t tim, char **ret) } static void -PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet) +PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet, + const reply_item *message) { unsigned char *tab, *new_text; int leftlen; @@ -92,10 +93,11 @@ 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_wrap (xtbuf, new_text, len + stamp_size, timet, stamp_size); + gtk_xtext_append_wrap_with_message (xtbuf, new_text, len + stamp_size, + timet, stamp_size, message); g_free (new_text); } else - gtk_xtext_append (xtbuf, text, len, timet); + gtk_xtext_append_wrap_with_message (xtbuf, text, len, timet, 0, message); return; } @@ -103,14 +105,21 @@ PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, ti if (tab && tab < (text + len)) { leftlen = tab - text; - gtk_xtext_append_indent (xtbuf, - text, leftlen, tab + 1, len - (leftlen + 1), timet); + gtk_xtext_append_indent_with_message (xtbuf, text, leftlen, + tab + 1, len - (leftlen + 1), timet, message); } else - gtk_xtext_append_indent (xtbuf, 0, 0, text, len, timet); + gtk_xtext_append_indent_with_message (xtbuf, 0, 0, text, len, timet, message); } void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp) +{ + PrintTextRawWithMessage (xtbuf, text, indent, stamp, NULL); +} + +void +PrintTextRawWithMessage (void *xtbuf, unsigned char *text, int indent, time_t stamp, + const reply_item *message) { char *last_text = text; int len = 0; @@ -122,10 +131,10 @@ PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp) switch (*text) { case 0: - PrintTextLine (xtbuf, last_text, len, indent, stamp); + PrintTextLine (xtbuf, last_text, len, indent, stamp, message); return; case '\n': - PrintTextLine (xtbuf, last_text, len, indent, stamp); + PrintTextLine (xtbuf, last_text, len, indent, stamp, message); text++; if (*text == 0) return; diff --git a/src/fe-gtk/textgui.h b/src/fe-gtk/textgui.h index 88dbd6e9..56b77f1b 100644 --- a/src/fe-gtk/textgui.h +++ b/src/fe-gtk/textgui.h @@ -20,7 +20,11 @@ #ifndef ZOITECHAT_TEXTGUI_H #define ZOITECHAT_TEXTGUI_H +struct reply_item; + void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp); +void PrintTextRawWithMessage (void *xtbuf, unsigned char *text, int indent, time_t stamp, + const struct reply_item *message); void pevent_dialog_show (void); #endif diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c index 8d9be195..36945910 100644 --- a/src/fe-gtk/xtext.c +++ b/src/fe-gtk/xtext.c @@ -90,6 +90,7 @@ struct textentry GdkPixbuf *image; /* inline image shown below the text, or NULL */ GdkPixbuf *image_full; /* larger version for the image viewer */ char *image_url; /* the URL the image was loaded from */ + reply_item *message; /* original IRC message, independent of the reply cache */ }; /* inline image display */ @@ -287,6 +288,7 @@ static char *gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret); static void gtk_xtext_clipboard_get (GtkClipboard *, GtkSelectionData *, guint, gpointer); static void gtk_xtext_clipboard_clear (GtkClipboard *, gpointer); static textentry *gtk_xtext_nth (GtkXText *xtext, int line, int *subline); +static void gtk_xtext_entry_set_message (textentry *ent, const reply_item *message); static void gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext); static void gtk_xtext_scroll_adjustments (GtkXText *xtext, GtkAdjustment *hadj, @@ -362,6 +364,13 @@ gtk_xtext_entry_free (GtkXText *xtext, textentry *ent) g_clear_pointer (&ent->sublines, g_array_unref); g_list_free (ent->marks); gtk_xtext_ent_free_image (ent); + if (ent->message) + { + g_free (ent->message->msgid); + g_free (ent->message->nick); + g_free (ent->message->text); + g_free (ent->message); + } g_free (ent); } @@ -5413,6 +5422,15 @@ gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *left_text, int left_len, unsigned char *right_text, int right_len, time_t stamp) +{ + gtk_xtext_append_indent_with_message (buf, left_text, left_len, + right_text, right_len, stamp, NULL); +} + +void +gtk_xtext_append_indent_with_message (xtext_buffer *buf, + unsigned char *left_text, int left_len, unsigned char *right_text, int right_len, + time_t stamp, const reply_item *message) { textentry *ent; unsigned char *str; @@ -5489,6 +5507,7 @@ gtk_xtext_append_indent (xtext_buffer *buf, } } + gtk_xtext_entry_set_message (ent, message); gtk_xtext_append_entry (buf, ent, stamp); } @@ -5502,6 +5521,13 @@ 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) +{ + gtk_xtext_append_wrap_with_message (buf, text, len, stamp, stamp_len, NULL); +} + +void +gtk_xtext_append_wrap_with_message (xtext_buffer *buf, unsigned char *text, + int len, time_t stamp, int stamp_len, const reply_item *message) { textentry *ent; @@ -5521,6 +5547,7 @@ gtk_xtext_append_wrap (xtext_buffer *buf, unsigned char *text, int len, time_t s ent->left_len = -1; ent->stamp_len = (stamp_len > 0 && stamp_len < ent->str_len) ? stamp_len : 0; + gtk_xtext_entry_set_message (ent, message); gtk_xtext_append_entry (buf, ent, stamp); } @@ -5854,6 +5881,28 @@ gtk_xtext_buffer_free (xtext_buffer *buf) /* ========== INLINE IMAGE API ============= */ /* ========================================= */ +/* Each rendered line owns its message identity, including continuation lines. + * Do not infer identity from nick, text or timestamp: ZNC playback and repeated + * messages can have identical values for all three. */ +static void +gtk_xtext_entry_set_message (textentry *ent, const reply_item *message) +{ + if (!message || !message->msgid || !*message->msgid) + return; + + ent->message = g_new0 (reply_item, 1); + ent->message->msgid = g_strdup (message->msgid); + ent->message->nick = g_strdup (message->nick); + ent->message->text = g_strdup (message->text); + ent->message->timestamp = message->timestamp; +} + +const reply_item * +gtk_xtext_entry_get_message (GtkXText *xtext, textentry *ent) +{ + return gtk_xtext_buffer_contains (xtext, ent) ? ent->message : NULL; +} + /* the entry the last word_click signal was emitted for; only valid while handling that signal */ diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h index eec6178f..1a251ea5 100644 --- a/src/fe-gtk/xtext.h +++ b/src/fe-gtk/xtext.h @@ -26,6 +26,7 @@ typedef struct _GtkXText GtkXText; typedef struct _GtkXTextClass GtkXTextClass; +struct reply_item; #define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) #define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) @@ -286,6 +287,11 @@ void gtk_xtext_append_indent (xtext_buffer *buf, unsigned char *left_text, int left_len, unsigned char *right_text, int right_len, time_t stamp); +void gtk_xtext_append_wrap_with_message (xtext_buffer *buf, unsigned char *text, + int len, time_t stamp, int stamp_len, const struct reply_item *message); +void gtk_xtext_append_indent_with_message (xtext_buffer *buf, + unsigned char *left_text, int left_len, unsigned char *right_text, int right_len, + time_t stamp, const struct reply_item *message); int gtk_xtext_set_font (GtkXText *xtext, char *name); void gtk_xtext_set_background (GtkXText * xtext, cairo_surface_t *surface); void gtk_xtext_set_palette (GtkXText * xtext, const XTextColor *palette); @@ -318,6 +324,7 @@ xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext); void gtk_xtext_buffer_free (xtext_buffer *buf); void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render); void gtk_xtext_copy_selection (GtkXText *xtext); +const struct reply_item *gtk_xtext_entry_get_message (GtkXText *xtext, textentry *ent); /* inline image support. A textentry can have one image attached, which is * rendered below its text. Loading is asynchronous: begin() registers the diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c index ffa962c7..46699646 100644 --- a/src/fe-text/fe-text.c +++ b/src/fe-text/fe-text.c @@ -406,6 +406,13 @@ fe_print_text (struct session *sess, char *text, time_t stamp, } #endif +void +fe_print_text_with_message (struct session *sess, char *text, time_t stamp, + gboolean no_activity, const reply_item *message) +{ + fe_print_text (sess, text, stamp, no_activity); +} + void fe_timeout_remove (int tag) {