Index: WORKxchat/src/fe-gtk/xtext.c =================================================================== --- WORKxchat/src/fe-gtk/xtext.c (revision 1451) +++ WORKxchat/src/fe-gtk/xtext.c (working copy) @@ -66,6 +66,11 @@ #include "mmx_cmod.h" #endif +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/xchatc.h" +#include "fe-gtk.h" #include "xtext.h" #define charlen(str) g_utf8_skip[*(guchar *)(str)] @@ -89,6 +94,20 @@ static GtkWidgetClass *parent_class = NULL; +/* + * offsets_t is used for retaining search information. + * It is stored in the 'data' member of a GList, + * as chained from ent->marks. It saves starting and + * ending+1 offset of a found occurrence. + */ +typedef union offsets_u { + struct offsets_s { + guint16 start; + guint16 end; + } o; + guint32 u; +} offsets_t; + struct textentry { struct textentry *next; @@ -108,6 +127,7 @@ guchar tag; guchar pad1; guchar pad2; /* 32-bit align : 44 bytes total */ + GList *marks; /* List of found strings */ }; enum @@ -149,6 +169,14 @@ static unsigned char * gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, int *newlen, int *mb_ret, int strip_hidden); +/* Forward declarations for all search functions */ +static int gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off); +static void gtk_xtext_search_textentry (xtext_buffer *, textentry *, int); +static void gtk_xtext_search_textentry_del (xtext_buffer *, textentry *); +static void gtk_xtext_search_textentry_fini (gpointer, gpointer); +static void gtk_xtext_search_fini (xtext_buffer *); +static gboolean gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gboolean case_match, gboolean backward); +textentry * gtk_xtext_search (GtkXText *xtext, const gchar *text, textentry *hint, gboolean case_match, gboolean bwd); /* some utility functions first */ @@ -1120,18 +1148,6 @@ } } -static void -gtk_xtext_selection_clear_full (xtext_buffer *buf) -{ - textentry *ent = buf->text_first; - while (ent) - { - ent->mark_start = -1; - ent->mark_end = -1; - ent = ent->next; - } -} - static int gtk_xtext_selection_clear (xtext_buffer *buf) { @@ -2882,6 +2898,54 @@ xtext->nc = 0; } +/* + * gtk_xtext_search_offset (buf, ent, off) -- + * Look for arg offset in arg textentry + * Return one or more flags: + * GTK_MATCH_MID if we are in a match + * GTK_MATCH_START if we're at the first byte of it + * GTK_MATCH_END if we at the first byte past it + * GTK_MATCH_CUR if it is the current match + */ +#define GTK_MATCH_START 1 +#define GTK_MATCH_MID 2 +#define GTK_MATCH_END 4 +#define GTK_MATCH_CUR 8 +static int +gtk_xtext_search_offset (xtext_buffer *buf, textentry *ent, unsigned int off) +{ + GList *gl; + offsets_t o; + int flags = 0; + + for (gl = g_list_first (ent->marks); gl; gl = g_list_next (gl)) + { + o.u = GPOINTER_TO_UINT (gl->data); + if (off < o.o.start || off > o.o.end) + continue; + flags = GTK_MATCH_MID; + if (off == o.o.start) + flags |= GTK_MATCH_START; + if (off == o.o.end) + { + gl = g_list_next (gl); + if (gl) + { + o.u = GPOINTER_TO_UINT (gl->data); + if (off == o.o.start) // If subseq match is adjacent + flags |= (gl == buf->curmark)? GTK_MATCH_CUR: 0; + else // If subseq match is not adjacent + flags |= GTK_MATCH_END; + } else // If there is no subseq match + flags |= GTK_MATCH_END; + } else // If not yet at the end of this match + if (gl == buf->curmark) + flags |= GTK_MATCH_CUR; + break; + } + return flags; +} + /* render a single line, which WONT wrap, and parse mIRC colors */ static int @@ -2896,6 +2960,7 @@ int offset; int mark = FALSE; int ret = 1; + int k; xtext->in_hilight = FALSE; @@ -2977,6 +3042,36 @@ } #endif + if (left_only == FALSE && // If not the timestamp call, and + (ent->mark_start == -1 || // also if not in a selection: + ent->mark_start > offset + i || + ent->mark_end < offset + i)) + { + k = gtk_xtext_search_offset (xtext->buffer, ent, offset + i); + if (k) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext->underline = (k & GTK_MATCH_CUR)? TRUE: FALSE; + if (k & (GTK_MATCH_START | GTK_MATCH_MID)) + { + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext->backcolor = TRUE; + mark = TRUE; + } + if (k & GTK_MATCH_END) + { + xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, gc, xtext->col_fore); + xtext->backcolor = (xtext->col_back != XTEXT_BG)? TRUE: FALSE; + xtext->underline = FALSE; + mark = FALSE; + } + } + } + if ((xtext->parsing_color && isdigit (str[i]) && xtext->nc < 2) || (xtext->parsing_color && str[i] == ',' && isdigit (str[i+1]) && xtext->nc < 3 && !xtext->parsing_backcolor)) { @@ -4746,6 +4841,7 @@ } } + static void gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent) { @@ -4766,6 +4862,9 @@ if (buffer->marker_pos == ent) buffer->marker_pos = NULL; + if (ent->marks) + gtk_xtext_search_textentry_del (buffer, ent); + free (ent); } @@ -4780,7 +4879,10 @@ if (!ent) return; buffer->num_lines -= ent->lines_taken; - buffer->pagetop_line -= ent->lines_taken; + if (ent == buffer->pagetop_ent) + buffer->pagetop_ent = NULL; + else + buffer->pagetop_line -= ent->lines_taken; buffer->last_pixel_pos -= (ent->lines_taken * buffer->xtext->fontsize); buffer->text_first = ent->next; if (buffer->text_first) @@ -4848,6 +4950,8 @@ else { /* delete all */ + if (buf->search_found) + gtk_xtext_search_fini (buf); if (buf->xtext->auto_indent) buf->indent = MARGIN; buf->scrollbar_down = TRUE; @@ -4907,86 +5011,212 @@ xtext->buffer->marker_seen = TRUE; } -textentry * -gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward) +/* Search a single textentry for occurrence(s) of search arg string */ +static void +gtk_xtext_search_textentry (xtext_buffer *buf, textentry *ent, int pre) { - textentry *ent, *fent; - int line; - gchar *str, *nee, *hay; /* needle in haystack */ + gchar *nee, *hay; /* looking for a needle in a haystack */ + gchar *str; /* text string to be searched */ + gchar *text; /* what to look for in it */ + offsets_t marks; + gchar *pos; + gint off, len, lhay, lnee; + GList *gl = NULL; - gtk_xtext_selection_clear_full (xtext->buffer); - xtext->buffer->last_ent_start = NULL; - xtext->buffer->last_ent_end = NULL; + str = ent->str; - /* set up text comparand for Case Match or Ignore */ - if (case_match) - nee = g_strdup (text); + if ((text = buf->search_text) == NULL) + return; + /* Save some time by preserving the massaged search string */ + if (buf->search_nee == NULL) + { + if (buf->search_match) + buf->search_nee = g_strdup (text); + else + buf->search_nee = g_utf8_casefold (text, strlen(text)); + } + nee = buf->search_nee; + + if (buf->search_match) + hay = g_strdup (str); else - nee = g_utf8_casefold (text, strlen (text)); + hay = g_utf8_casefold (str, strlen (str)); + lhay = strlen (hay); + lnee = strlen (nee); + off = 0; - /* Validate that start gives a currently valid ent pointer */ - ent = xtext->buffer->text_first; - while (ent) + for (pos = hay, len = lhay; len; pos = hay + off, len = lhay - off) { - if (ent == start) + str = g_strstr_len (pos, len, nee); + if (str == NULL) break; - ent = ent->next; + off = str - hay; + marks.o.start = off; + off += lnee; + marks.o.end = off; + gl = g_list_append (gl, GUINT_TO_POINTER (marks.u)); } - if (!ent) - start = NULL; + g_free (hay); + ent->marks = gl; + if (gl) + { + buf->search_found = (pre? g_list_prepend: g_list_append) (buf->search_found, ent); + if (pre == FALSE && buf->hintsearch == NULL) + buf->hintsearch = ent; + } + return; +} - /* Choose first ent to look at */ - if (start) - ent = backward? start->prev: start->next; +/* Free all search information for a textentry */ +static void +gtk_xtext_search_textentry_del (xtext_buffer *buf, textentry *ent) +{ + g_list_free (ent->marks); + ent->marks = NULL; + if (buf->cursearch && buf->cursearch->data == ent) + { + buf->cursearch = NULL; + buf->curmark = NULL; + } + if (buf->pagetop_ent == ent) + buf->pagetop_ent = NULL; + buf->search_found = g_list_remove (buf->search_found, ent); +} + +/* Used only by glist_foreach */ +static void +gtk_xtext_search_textentry_fini (gpointer entp, gpointer bufp) +{ + textentry *ent = entp; + + g_list_free (ent->marks); + ent->marks = NULL; +} + +/* Free all search information for all textentrys and the xtext_buffer */ +static void +gtk_xtext_search_fini (xtext_buffer *buf) +{ + g_list_foreach (buf->search_found, gtk_xtext_search_textentry_fini, 0); + g_list_free (buf->search_found); + buf->search_found = NULL; + g_free (buf->search_text); + buf->search_text = NULL; + g_free (buf->search_nee); + buf->search_nee = NULL; + buf->search_match = 0; + buf->cursearch = NULL; + buf->curmark = NULL; +} + +/* Returns TRUE if the base search information exists and is still okay to use */ +static gboolean +gtk_xtext_search_init (xtext_buffer *buf, const gchar *text, gboolean case_match, gboolean backward) +{ + if (buf->search_found && + strcmp (buf->search_text, text) == 0 && + buf->search_match == case_match) + return TRUE; + if (buf->cursearch && buf->cursearch->data) + buf->hintsearch = buf->cursearch->data; else - ent = backward? xtext->buffer->text_last: xtext->buffer->text_first; + buf->hintsearch = NULL; + gtk_xtext_search_fini (buf); + buf->search_text = g_strdup (text); + buf->search_match = case_match; + buf->search_bwd = backward; + buf->cursearch = NULL; + buf->curmark = NULL; + return FALSE; +} - /* Search from there to one end or the other until found */ - while (ent) +textentry * +gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *hint, gboolean case_match, gboolean bwd) +{ + textentry *ent = NULL, *fent; + xtext_buffer *buf = xtext->buffer; + + if (text == NULL || text[0] == 0) // Let a null string reset the search results. { - /* If Case Ignore, fold before & free after calling strstr */ - if (case_match) - hay = g_strdup (ent->str); - else - hay = g_utf8_casefold (ent->str, strlen (ent->str)); - /* Try to find the needle in this haystack */ - str = g_strstr_len (hay, strlen (hay), nee); - g_free (hay); - if (str) - break; - ent = backward? ent->prev: ent->next; + gtk_xtext_search_fini (buf); + goto checkvisible; } - fent = ent; + if (gtk_xtext_search_init (buf, text, case_match, bwd) == FALSE) // If a new search: + { + for (ent = buf->text_first; ent; ent = ent->next) + gtk_xtext_search_textentry (buf, ent, TRUE); + buf->search_found = g_list_reverse (buf->search_found); + } - /* Save distance to start, end of found string */ - if (ent) + /* Now base search results are in place. */ + if (buf->search_found) { - ent->mark_start = str - hay; - ent->mark_end = ent->mark_start + strlen (nee); - - /* is the match visible? Might need to scroll */ - if (!gtk_xtext_check_ent_visibility (xtext, ent, 0)) + if (buf->cursearch == NULL) { - ent = xtext->buffer->text_first; - line = 0; + ent = g_list_find (buf->search_found, buf->hintsearch); while (ent) + if (ent->marks == NULL) + ent = bwd? ent->prev: ent->next; + else + break; + buf->cursearch = ent; + if (buf->cursearch == NULL) + buf->cursearch = bwd? g_list_last (buf->search_found) + : g_list_first (buf->search_found); + ent = buf->cursearch->data; + buf->curmark = bwd? g_list_last (ent->marks) + : g_list_first (ent->marks); + } else + { + ent = buf->cursearch->data; + if (buf->curmark == NULL) + buf->curmark = bwd? g_list_last (ent->marks) + : g_list_first (ent->marks); + else { - line += ent->lines_taken; - ent = ent->next; - if (ent == fent) - break; + buf->curmark = bwd? g_list_previous (buf->curmark) + : g_list_next (buf->curmark); } - while (line > xtext->adj->upper - xtext->adj->page_size) - line--; - if (backward) - line -= xtext->adj->page_size - ent->lines_taken; - xtext->adj->value = line; - xtext->buffer->scrollbar_down = FALSE; - gtk_adjustment_changed (xtext->adj); + if (buf->curmark == NULL) + { + /* We've returned all the matches for this text entry. */ + ent = NULL; + buf->cursearch = bwd? g_list_previous (buf->cursearch) + : g_list_next (buf->cursearch); + if (buf->cursearch) + { + ent = buf->cursearch->data; + buf->curmark = bwd? g_list_last (ent->marks) + : g_list_first (ent->marks); + } + } } } +checkvisible: + fent = ent; + buf->hintsearch = fent; + + if (!gtk_xtext_check_ent_visibility (xtext, fent, 0)) + { + GtkAdjustment *adj = xtext->adj; + int value, subtr; - g_free (nee); + buf->pagetop_ent = NULL; + for (value = 0, ent = buf->text_first; ent && ent != fent; ent = ent->next) + value += ent->lines_taken; + if (value > adj->upper - adj->page_size) + value = adj->upper - adj->page_size; + else if (bwd && ent) + { + subtr = adj->page_size - ent->lines_taken + 1; + if (value >= subtr) + value -= subtr; + else + value = 0; + } + gtk_adjustment_set_value (adj, value); + } + gtk_widget_queue_draw (GTK_WIDGET (xtext)); return fent; @@ -5053,6 +5283,7 @@ ent->mark_start = -1; ent->mark_end = -1; ent->next = NULL; + ent->marks = NULL; if (ent->indent < MARGIN) ent->indent = MARGIN; /* 2 pixels is the left margin */ @@ -5114,6 +5345,7 @@ if (buf->old_value < 0) buf->old_value = 0; } + gtk_xtext_search_textentry (buf, ent, FALSE); } /* the main two public functions */ @@ -5440,6 +5672,9 @@ if (buf->xtext->selection_buffer == buf) buf->xtext->selection_buffer = NULL; + if (buf->search_found) + gtk_xtext_search_fini (buf); + ent = buf->text_first; while (ent) { Index: WORKxchat/src/fe-gtk/xtext.h =================================================================== --- WORKxchat/src/fe-gtk/xtext.h (revision 1451) +++ WORKxchat/src/fe-gtk/xtext.h (working copy) @@ -77,6 +77,15 @@ unsigned int grid_dirty:1; unsigned int marker_seen:1; unsigned int reset_marker_pos:1; + + GList *search_found; /* list of textentries where search found strings */ + GList *cursearch; /* GList whose 'data' pts to current textentry */ + textentry *hintsearch; /* textentry found for last search */ + GList *curmark; /* current item in ent->marks */ + gchar *search_text; /* desired text to search for */ + gchar *search_nee; /* prepared needle to look in haystack for */ + unsigned int search_match; + unsigned int search_bwd; } xtext_buffer; struct _GtkXText Index: WORKxchat/src/fe-gtk/menu.c =================================================================== --- WORKxchat/src/fe-gtk/menu.c (revision 1451) +++ WORKxchat/src/fe-gtk/menu.c (working copy) @@ -1198,6 +1198,33 @@ } static void +menu_search_next () +{ + GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext); + xtext_buffer *buf = xtext->buffer; + + gtk_xtext_search (xtext, buf->search_text, NULL, buf->search_match, FALSE); +} + +static void +menu_search_prev () +{ + GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext); + xtext_buffer *buf = xtext->buffer; + + gtk_xtext_search (xtext, buf->search_text, NULL, buf->search_match, TRUE); +} + +static void +menu_search_reset () +{ + GtkXText *xtext = GTK_XTEXT (current_sess->gui->xtext); + xtext_buffer *buf = xtext->buffer; + + gtk_xtext_search (xtext, NULL, buf->text_last, buf->search_match, 0); +} + +static void menu_resetmarker (GtkWidget * wid, gpointer none) { gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext)); @@ -1648,6 +1675,8 @@ {N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l}, #define SEARCH_OFFSET 67 {N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f}, + {N_("Search Next" ), menu_search_next, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_g}, + {N_("Search Reset" ), menu_search_reset, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_F}, {N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1}, {N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 69 */ @@ -2188,7 +2217,8 @@ mymenu[i].key, mymenu[i].key == GDK_F1 ? 0 : mymenu[i].key == GDK_w ? close_mask : - GDK_CONTROL_MASK, + (mymenu[i].key >= GDK_A && mymenu[i].key <= GDK_Z) ? + GDK_CONTROL_MASK | GDK_SHIFT_MASK : GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); if (mymenu[i].callback) g_signal_connect (G_OBJECT (item), "activate", Index: WORKxchat/src/fe-gtk/search.c =================================================================== --- WORKxchat/src/fe-gtk/search.c (revision 1451) +++ WORKxchat/src/fe-gtk/search.c (working copy) @@ -60,11 +60,22 @@ last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, last, case_match, search_backward); - if (!last) + if (!last && text[0] != 0) fe_message (_("Search hit end, not found."), FE_MSG_ERROR); } static void +search_reset_cb (GtkWidget * button, session * sess) +{ + GtkEntry *entry; + const gchar *text; + + entry = g_object_get_data (G_OBJECT (button), "e"); + text = gtk_entry_get_text (entry); + search_search (sess, ""); +} + +static void search_find_cb (GtkWidget * button, session * sess) { GtkEntry *entry; @@ -153,6 +164,14 @@ _("_Find")); g_object_set_data (G_OBJECT (wid), "e", entry); + hbox = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4); + gtk_widget_show (hbox); + + wid = gtkutil_button (hbox, "gtk-reset", 0, search_reset_cb, sess, + _("_Reset")); + add_tip (wid, "Reset highlighted search items"); + g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (search_key_cb), win); gtk_widget_show (win);