All pastes #1931662 Raw Edit

Miscellany

public text v1 · immutable
#1931662 ·published 2010-09-03 08:59 UTC
rendered paste body
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);