aboutsummaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2010-01-18 06:47:08 +0800
committerMatthew Barnes <mbarnes@redhat.com>2010-01-18 12:54:06 +0800
commit39ee1b7890e06779b47f0fc11925d12caa206c39 (patch)
tree563aeba407e45e91cb9986caa159d11dca7e4823 /widgets
parent3e7c7808cc65c22bc40a7d1d30ffa0044097a6ff (diff)
downloadgsoc2013-evolution-39ee1b7890e06779b47f0fc11925d12caa206c39.tar.gz
gsoc2013-evolution-39ee1b7890e06779b47f0fc11925d12caa206c39.tar.zst
gsoc2013-evolution-39ee1b7890e06779b47f0fc11925d12caa206c39.zip
Give all preview panes a search bar.
Use Shift+Ctrl+F as the accelerator for consistency with the mailer.
Diffstat (limited to 'widgets')
-rw-r--r--widgets/misc/Makefile.am6
-rw-r--r--widgets/misc/e-preview-pane.c275
-rw-r--r--widgets/misc/e-preview-pane.h74
-rw-r--r--widgets/misc/e-search-bar.c825
-rw-r--r--widgets/misc/e-search-bar.h87
-rw-r--r--widgets/misc/e-searching-tokenizer.c1195
-rw-r--r--widgets/misc/e-searching-tokenizer.h92
7 files changed, 2554 insertions, 0 deletions
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index 5853039fb0..44d8c9a577 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -58,7 +58,10 @@ widgetsinclude_HEADERS = \
e-popup-action.h \
e-popup-menu.h \
e-preferences-window.h \
+ e-preview-pane.h \
e-printable.h \
+ e-search-bar.h \
+ e-searching-tokenizer.h \
e-selectable.h \
e-selection-model.h \
e-selection-model-array.h \
@@ -133,7 +136,10 @@ libemiscwidgets_la_SOURCES = \
e-popup-action.c \
e-popup-menu.c \
e-preferences-window.c \
+ e-preview-pane.c \
e-printable.c \
+ e-search-bar.c \
+ e-searching-tokenizer.c \
e-selectable.c \
e-selection-model.c \
e-selection-model-array.c \
diff --git a/widgets/misc/e-preview-pane.c b/widgets/misc/e-preview-pane.c
new file mode 100644
index 0000000000..240bfbd824
--- /dev/null
+++ b/widgets/misc/e-preview-pane.c
@@ -0,0 +1,275 @@
+/*
+ * e-preview-pane.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-preview-pane.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#define E_PREVIEW_PANE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_PREVIEW_PANE, EPreviewPanePrivate))
+
+struct _EPreviewPanePrivate {
+ GtkWidget *web_view;
+ GtkWidget *search_bar;
+};
+
+enum {
+ PROP_0,
+ PROP_SEARCH_BAR,
+ PROP_WEB_VIEW
+};
+
+enum {
+ SHOW_SEARCH_BAR,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+
+static void
+preview_pane_set_web_view (EPreviewPane *preview_pane,
+ EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (preview_pane->priv->web_view == NULL);
+
+ preview_pane->priv->web_view = g_object_ref_sink (web_view);
+}
+
+static void
+preview_pane_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_WEB_VIEW:
+ preview_pane_set_web_view (
+ E_PREVIEW_PANE (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+preview_pane_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SEARCH_BAR:
+ g_value_set_object (
+ value, e_preview_pane_get_search_bar (
+ E_PREVIEW_PANE (object)));
+ return;
+
+ case PROP_WEB_VIEW:
+ g_value_set_object (
+ value, e_preview_pane_get_web_view (
+ E_PREVIEW_PANE (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+preview_pane_dispose (GObject *object)
+{
+ EPreviewPanePrivate *priv;
+
+ priv = E_PREVIEW_PANE_GET_PRIVATE (object);
+
+ if (priv->search_bar != NULL) {
+ g_object_unref (priv->search_bar);
+ priv->search_bar = NULL;
+ }
+
+ if (priv->web_view != NULL) {
+ g_object_unref (priv->web_view);
+ priv->web_view = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+preview_pane_constructed (GObject *object)
+{
+ EPreviewPanePrivate *priv;
+ GtkWidget *widget;
+
+ priv = E_PREVIEW_PANE_GET_PRIVATE (object);
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (widget),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (object), widget, TRUE, TRUE, 0);
+ gtk_container_add (GTK_CONTAINER (widget), priv->web_view);
+ gtk_widget_show (widget);
+
+ widget = e_search_bar_new (E_WEB_VIEW (priv->web_view));
+ gtk_box_pack_start (GTK_BOX (object), widget, FALSE, FALSE, 0);
+ priv->search_bar = g_object_ref (widget);
+ gtk_widget_hide (widget);
+}
+
+static void
+preview_pane_show_search_bar (EPreviewPane *preview_pane)
+{
+ GtkWidget *search_bar;
+
+ search_bar = preview_pane->priv->search_bar;
+
+ if (!gtk_widget_get_visible (search_bar))
+ gtk_widget_show (search_bar);
+}
+
+static void
+preview_pane_class_init (EPreviewPaneClass *class)
+{
+ GObjectClass *object_class;
+ GtkBindingSet *binding_set;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (EPreviewPanePrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = preview_pane_set_property;
+ object_class->get_property = preview_pane_get_property;
+ object_class->dispose = preview_pane_dispose;
+ object_class->constructed = preview_pane_constructed;
+
+ class->show_search_bar = preview_pane_show_search_bar;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SEARCH_BAR,
+ g_param_spec_object (
+ "search-bar",
+ "Search Bar",
+ NULL,
+ E_TYPE_SEARCH_BAR,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_WEB_VIEW,
+ g_param_spec_object (
+ "web-view",
+ "Web View",
+ NULL,
+ E_TYPE_WEB_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ signals[SHOW_SEARCH_BAR] = g_signal_new (
+ "show-search-bar",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EPreviewPaneClass, show_search_bar),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (class);
+
+ gtk_binding_entry_add_signal (
+ binding_set, GDK_f, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
+ "show-search-bar", 0);
+}
+
+static void
+preview_pane_init (EPreviewPane *preview_pane)
+{
+ preview_pane->priv = E_PREVIEW_PANE_GET_PRIVATE (preview_pane);
+
+ gtk_box_set_spacing (GTK_BOX (preview_pane), 1);
+}
+
+GType
+e_preview_pane_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (EPreviewPaneClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) preview_pane_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (EPreviewPane),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) preview_pane_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_VBOX, "EPreviewPane", &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_preview_pane_new (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return g_object_new (
+ E_TYPE_PREVIEW_PANE,
+ "web-view", web_view, NULL);
+}
+
+EWebView *
+e_preview_pane_get_web_view (EPreviewPane *preview_pane)
+{
+ g_return_val_if_fail (E_IS_PREVIEW_PANE (preview_pane), NULL);
+
+ return E_WEB_VIEW (preview_pane->priv->web_view);
+}
+
+ESearchBar *
+e_preview_pane_get_search_bar (EPreviewPane *preview_pane)
+{
+ g_return_val_if_fail (E_IS_PREVIEW_PANE (preview_pane), NULL);
+
+ return E_SEARCH_BAR (preview_pane->priv->search_bar);
+}
+
+void
+e_preview_pane_show_search_bar (EPreviewPane *preview_pane)
+{
+ g_return_if_fail (E_IS_PREVIEW_PANE (preview_pane));
+
+ g_signal_emit (preview_pane, signals[SHOW_SEARCH_BAR], 0);
+}
diff --git a/widgets/misc/e-preview-pane.h b/widgets/misc/e-preview-pane.h
new file mode 100644
index 0000000000..bd965ed307
--- /dev/null
+++ b/widgets/misc/e-preview-pane.h
@@ -0,0 +1,74 @@
+/*
+ * e-preview-pane.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_PREVIEW_PANE_H
+#define E_PREVIEW_PANE_H
+
+#include <gtk/gtk.h>
+#include <misc/e-search-bar.h>
+#include <misc/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_PREVIEW_PANE \
+ (e_preview_pane_get_type ())
+#define E_PREVIEW_PANE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_PREVIEW_PANE, EPreviewPane))
+#define E_PREVIEW_PANE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_PREVIEW_PANE, EPreviewPaneClass))
+#define E_IS_PREVIEW_PANE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_PREVIEW_PANE))
+#define E_IS_PREVIEW_PANE_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_PREVIEW_PANE))
+#define E_PREVIEW_PANE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_PREVIEW_PANE, EPreviewPaneClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPreviewPane EPreviewPane;
+typedef struct _EPreviewPaneClass EPreviewPaneClass;
+typedef struct _EPreviewPanePrivate EPreviewPanePrivate;
+
+struct _EPreviewPane {
+ GtkVBox parent;
+ EPreviewPanePrivate *priv;
+};
+
+struct _EPreviewPaneClass {
+ GtkVBoxClass parent_class;
+
+ /* Signals */
+ void (*show_search_bar) (EPreviewPane *preview_pane);
+};
+
+GType e_preview_pane_get_type (void);
+GtkWidget * e_preview_pane_new (EWebView *web_view);
+EWebView * e_preview_pane_get_web_view (EPreviewPane *preview_pane);
+ESearchBar * e_preview_pane_get_search_bar (EPreviewPane *preview_pane);
+void e_preview_pane_show_search_bar (EPreviewPane *preview_pane);
+
+G_END_DECLS
+
+#endif /* E_PREVIEW_PANE_H */
diff --git a/widgets/misc/e-search-bar.c b/widgets/misc/e-search-bar.c
new file mode 100644
index 0000000000..120f114bb8
--- /dev/null
+++ b/widgets/misc/e-search-bar.c
@@ -0,0 +1,825 @@
+/*
+ * e-search-bar.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "e-search-bar.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtkhtml/gtkhtml-search.h>
+
+#include "e-util/e-binding.h"
+
+#define E_SEARCH_BAR_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SEARCH_BAR, ESearchBarPrivate))
+
+struct _ESearchBarPrivate {
+ EWebView *web_view;
+ GtkWidget *entry;
+ GtkWidget *case_sensitive_button;
+ GtkWidget *wrapped_next_box;
+ GtkWidget *wrapped_prev_box;
+ GtkWidget *matches_label;
+
+ ESearchingTokenizer *tokenizer;
+ gchar *active_search;
+
+ guint rerun_search : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_SEARCH,
+ PROP_CASE_SENSITIVE,
+ PROP_TEXT,
+ PROP_WEB_VIEW
+};
+
+enum {
+ CHANGED,
+ CLEAR,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+
+static void
+search_bar_update_matches (ESearchBar *search_bar)
+{
+ ESearchingTokenizer *tokenizer;
+ GtkWidget *matches_label;
+ gint matches;
+ gchar *text;
+
+ tokenizer = e_search_bar_get_tokenizer (search_bar);
+ matches_label = search_bar->priv->matches_label;
+
+ matches = e_searching_tokenizer_match_count (tokenizer);
+ text = g_strdup_printf (_("Matches: %d"), matches);
+
+ gtk_label_set_text (GTK_LABEL (matches_label), text);
+ gtk_widget_show (matches_label);
+
+ g_free (text);
+}
+
+static void
+search_bar_update_tokenizer (ESearchBar *search_bar)
+{
+ ESearchingTokenizer *tokenizer;
+ gboolean case_sensitive;
+ gchar *active_search;
+
+ tokenizer = e_search_bar_get_tokenizer (search_bar);
+ case_sensitive = e_search_bar_get_case_sensitive (search_bar);
+
+ if (GTK_WIDGET_VISIBLE (search_bar))
+ active_search = search_bar->priv->active_search;
+ else
+ active_search = NULL;
+
+ e_searching_tokenizer_set_primary_case_sensitivity (
+ tokenizer, case_sensitive);
+ e_searching_tokenizer_set_primary_search_string (
+ tokenizer, active_search);
+
+ e_search_bar_changed (search_bar);
+}
+
+static void
+search_bar_find (ESearchBar *search_bar,
+ gboolean search_forward)
+{
+ EWebView *web_view;
+ GtkWidget *widget;
+ gboolean case_sensitive;
+ gboolean new_search;
+ gboolean wrapped = FALSE;
+ gchar *text;
+
+ web_view = e_search_bar_get_web_view (search_bar);
+ case_sensitive = e_search_bar_get_case_sensitive (search_bar);
+ text = e_search_bar_get_text (search_bar);
+
+ if (text == NULL || *text == '\0') {
+ e_search_bar_clear (search_bar);
+ g_free (text);
+ return;
+ }
+
+ new_search =
+ (search_bar->priv->active_search == NULL) ||
+ (g_strcmp0 (text, search_bar->priv->active_search) != 0);
+
+ /* XXX On a new search, the HTMLEngine's search state gets
+ * destroyed when we redraw the message with highlighted
+ * matches (EMHTMLStream's write() method triggers this,
+ * but it's really GtkHtml's fault). That's why the first
+ * match isn't selected automatically. It also causes
+ * gtk_html_engine_search_next() to return FALSE, which we
+ * assume to mean the search wrapped.
+ *
+ * So to avoid mistakenly thinking the search wrapped when
+ * it hasn't, we have to trap the first button click after a
+ * search and re-run the search to recreate the HTMLEngine's
+ * search state, so that gtk_html_engine_search_next() will
+ * succeed. */
+ if (new_search) {
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = text;
+ search_bar->priv->rerun_search = TRUE;
+ search_bar_update_tokenizer (search_bar);
+ } else if (search_bar->priv->rerun_search) {
+ gtk_html_engine_search (
+ GTK_HTML (web_view),
+ search_bar->priv->active_search,
+ case_sensitive, search_forward, FALSE);
+ search_bar->priv->rerun_search = FALSE;
+ g_free (text);
+ } else {
+ gtk_html_engine_search_set_forward (
+ GTK_HTML (web_view), search_forward);
+ if (!gtk_html_engine_search_next (GTK_HTML (web_view)))
+ wrapped = TRUE;
+ g_free (text);
+ }
+
+ if (new_search || wrapped)
+ gtk_html_engine_search (
+ GTK_HTML (web_view),
+ search_bar->priv->active_search,
+ case_sensitive, search_forward, FALSE);
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+
+ /* Update wrapped label visibility. */
+
+ widget = search_bar->priv->wrapped_next_box;
+
+ if (wrapped && search_forward)
+ gtk_widget_show (widget);
+ else
+ gtk_widget_hide (widget);
+
+ widget = search_bar->priv->wrapped_prev_box;
+
+ if (wrapped && !search_forward)
+ gtk_widget_show (widget);
+ else
+ gtk_widget_hide (widget);
+}
+
+static void
+search_bar_changed_cb (ESearchBar *search_bar)
+{
+ g_object_notify (G_OBJECT (search_bar), "text");
+}
+
+static void
+search_bar_find_next_cb (ESearchBar *search_bar)
+{
+ search_bar_find (search_bar, TRUE);
+}
+
+static void
+search_bar_find_previous_cb (ESearchBar *search_bar)
+{
+ search_bar_find (search_bar, FALSE);
+}
+
+static void
+search_bar_icon_release_cb (ESearchBar *search_bar,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event)
+{
+ g_return_if_fail (icon_pos == GTK_ENTRY_ICON_SECONDARY);
+
+ e_search_bar_clear (search_bar);
+ gtk_widget_grab_focus (search_bar->priv->entry);
+}
+
+static void
+search_bar_toggled_cb (ESearchBar *search_bar)
+{
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = NULL;
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+ g_object_notify (G_OBJECT (search_bar), "case-sensitive");
+}
+
+static void
+search_bar_set_web_view (ESearchBar *search_bar,
+ EWebView *web_view)
+{
+ GtkHTML *html;
+ ESearchingTokenizer *tokenizer;
+
+ g_return_if_fail (search_bar->priv->web_view == NULL);
+
+ search_bar->priv->web_view = g_object_ref (web_view);
+
+ html = GTK_HTML (web_view);
+ tokenizer = e_search_bar_get_tokenizer (search_bar);
+ gtk_html_set_tokenizer (html, HTML_TOKENIZER (tokenizer));
+}
+
+static void
+search_bar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CASE_SENSITIVE:
+ e_search_bar_set_case_sensitive (
+ E_SEARCH_BAR (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_TEXT:
+ e_search_bar_set_text (
+ E_SEARCH_BAR (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_WEB_VIEW:
+ search_bar_set_web_view (
+ E_SEARCH_BAR (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+search_bar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_SEARCH:
+ g_value_set_boolean (
+ value, e_search_bar_get_active_search (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_CASE_SENSITIVE:
+ g_value_set_boolean (
+ value, e_search_bar_get_case_sensitive (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_TEXT:
+ g_value_take_string (
+ value, e_search_bar_get_text (
+ E_SEARCH_BAR (object)));
+ return;
+
+ case PROP_WEB_VIEW:
+ g_value_set_object (
+ value, e_search_bar_get_web_view (
+ E_SEARCH_BAR (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+search_bar_dispose (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ if (priv->web_view != NULL) {
+ g_object_unref (priv->web_view);
+ priv->web_view = NULL;
+ }
+
+ if (priv->entry != NULL) {
+ g_object_unref (priv->entry);
+ priv->entry = NULL;
+ }
+
+ if (priv->case_sensitive_button != NULL) {
+ g_object_unref (priv->case_sensitive_button);
+ priv->case_sensitive_button = NULL;
+ }
+
+ if (priv->wrapped_next_box != NULL) {
+ g_object_unref (priv->wrapped_next_box);
+ priv->wrapped_next_box = NULL;
+ }
+
+ if (priv->wrapped_prev_box != NULL) {
+ g_object_unref (priv->wrapped_prev_box);
+ priv->wrapped_prev_box = NULL;
+ }
+
+ if (priv->matches_label != NULL) {
+ g_object_unref (priv->matches_label);
+ priv->matches_label = NULL;
+ }
+
+ if (priv->tokenizer != NULL) {
+ g_object_unref (priv->tokenizer);
+ priv->tokenizer = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+search_bar_finalize (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ g_free (priv->active_search);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+search_bar_constructed (GObject *object)
+{
+ ESearchBarPrivate *priv;
+
+ priv = E_SEARCH_BAR_GET_PRIVATE (object);
+
+ e_mutual_binding_new (
+ object, "case-sensitive",
+ priv->case_sensitive_button, "active");
+}
+
+static void
+search_bar_show (GtkWidget *widget)
+{
+ ESearchBar *search_bar;
+
+ search_bar = E_SEARCH_BAR (widget);
+
+ /* Chain up to parent's show() method. */
+ GTK_WIDGET_CLASS (parent_class)->show (widget);
+
+ gtk_widget_grab_focus (search_bar->priv->entry);
+
+ search_bar_update_tokenizer (search_bar);
+}
+
+static void
+search_bar_hide (GtkWidget *widget)
+{
+ ESearchBar *search_bar;
+
+ search_bar = E_SEARCH_BAR (widget);
+
+ /* Chain up to parent's hide() method. */
+ GTK_WIDGET_CLASS (parent_class)->hide (widget);
+
+ search_bar_update_tokenizer (search_bar);
+}
+
+static gboolean
+search_bar_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GtkWidgetClass *widget_class;
+
+ if (event->keyval == GDK_Escape) {
+ gtk_widget_hide (widget);
+ return TRUE;
+ }
+
+ /* Chain up to parent's key_press_event() method. */
+ widget_class = GTK_WIDGET_CLASS (parent_class);
+ return widget_class->key_press_event (widget, event);
+}
+
+static void
+search_bar_clear (ESearchBar *search_bar)
+{
+ g_free (search_bar->priv->active_search);
+ search_bar->priv->active_search = NULL;
+
+ gtk_entry_set_text (GTK_ENTRY (search_bar->priv->entry), "");
+
+ gtk_widget_hide (search_bar->priv->wrapped_next_box);
+ gtk_widget_hide (search_bar->priv->wrapped_prev_box);
+ gtk_widget_hide (search_bar->priv->matches_label);
+
+ search_bar_update_tokenizer (search_bar);
+
+ g_object_notify (G_OBJECT (search_bar), "active-search");
+}
+
+static void
+search_bar_class_init (ESearchBarClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ESearchBarPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = search_bar_set_property;
+ object_class->get_property = search_bar_get_property;
+ object_class->dispose = search_bar_dispose;
+ object_class->finalize = search_bar_finalize;
+ object_class->constructed = search_bar_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = search_bar_show;
+ widget_class->hide = search_bar_hide;
+ widget_class->key_press_event = search_bar_key_press_event;
+
+ class->clear = search_bar_clear;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_SEARCH,
+ g_param_spec_boolean (
+ "active-search",
+ "Active Search",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CASE_SENSITIVE,
+ g_param_spec_boolean (
+ "case-sensitive",
+ "Case Sensitive",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_TEXT,
+ g_param_spec_string (
+ "text",
+ "Search Text",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_WEB_VIEW,
+ g_param_spec_object (
+ "web-view",
+ "Web View",
+ NULL,
+ E_TYPE_WEB_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ signals[CHANGED] = g_signal_new (
+ "changed",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ESearchBarClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[CLEAR] = g_signal_new (
+ "clear",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ESearchBarClass, clear),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+search_bar_init (ESearchBar *search_bar)
+{
+ GtkWidget *label;
+ GtkWidget *widget;
+ GtkWidget *container;
+
+ search_bar->priv = E_SEARCH_BAR_GET_PRIVATE (search_bar);
+ search_bar->priv->tokenizer = e_searching_tokenizer_new ();
+
+ g_signal_connect_swapped (
+ search_bar->priv->tokenizer, "match",
+ G_CALLBACK (search_bar_update_matches), search_bar);
+
+ gtk_box_set_spacing (GTK_BOX (search_bar), 12);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 1);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (widget, _("Close the find bar"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (gtk_widget_hide), search_bar);
+
+ widget = gtk_label_new_with_mnemonic (_("Fin_d:"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 3);
+ gtk_widget_show (widget);
+
+ label = widget;
+
+ widget = gtk_entry_new ();
+ gtk_entry_set_icon_from_stock (
+ GTK_ENTRY (widget), GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_CLEAR);
+ gtk_entry_set_icon_tooltip_text (
+ GTK_ENTRY (widget), GTK_ENTRY_ICON_SECONDARY,
+ _("Clear the search"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
+ gtk_widget_set_size_request (widget, 200, -1);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ search_bar->priv->entry = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ e_binding_new (
+ search_bar, "active-search",
+ widget, "secondary-icon-sensitive");
+
+ g_signal_connect_swapped (
+ widget, "activate",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (search_bar_changed_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "icon-release",
+ G_CALLBACK (search_bar_icon_release_cb), search_bar);
+
+ widget = gtk_button_new_with_mnemonic (_("_Previous"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Find the previous occurrence of the phrase"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ e_binding_new (search_bar, "active-search", widget, "sensitive");
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (search_bar_find_previous_cb), search_bar);
+
+ widget = gtk_button_new_with_mnemonic (_("_Next"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget), gtk_image_new_from_stock (
+ GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (
+ widget, _("Find the next occurrence of the phrase"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ e_binding_new (search_bar, "active-search", widget, "sensitive");
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ widget = gtk_check_button_new_with_mnemonic (_("Mat_ch case"));
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ search_bar->priv->case_sensitive_button = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (search_bar_toggled_cb), search_bar);
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (search_bar_find_next_cb), search_bar);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ search_bar->priv->wrapped_next_box = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "wrapped", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (
+ _("Reached bottom of page, continued from top"));
+ gtk_label_set_ellipsize (
+ GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_hbox_new (FALSE, 6);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ search_bar->priv->wrapped_prev_box = g_object_ref (widget);
+ gtk_widget_hide (widget);
+
+ container = widget;
+
+ widget = gtk_image_new_from_icon_name (
+ "wrapped", GTK_ICON_SIZE_MENU);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = gtk_label_new (
+ _("Reached top of page, continued from bottom"));
+ gtk_label_set_ellipsize (
+ GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ container = GTK_WIDGET (search_bar);
+
+ widget = gtk_label_new (NULL);
+ gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 12);
+ search_bar->priv->matches_label = g_object_ref (widget);
+ gtk_widget_show (widget);
+}
+
+GType
+e_search_bar_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (ESearchBarClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) search_bar_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ESearchBar),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) search_bar_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ GTK_TYPE_HBOX, "ESearchBar", &type_info, 0);
+ }
+
+ return type;
+}
+
+GtkWidget *
+e_search_bar_new (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return g_object_new (
+ E_TYPE_SEARCH_BAR, "web-view", web_view, NULL);
+}
+
+void
+e_search_bar_clear (ESearchBar *search_bar)
+{
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ g_signal_emit (search_bar, signals[CLEAR], 0);
+}
+
+void
+e_search_bar_changed (ESearchBar *search_bar)
+{
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ g_signal_emit (search_bar, signals[CHANGED], 0);
+}
+
+EWebView *
+e_search_bar_get_web_view (ESearchBar *search_bar)
+{
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL);
+
+ return search_bar->priv->web_view;
+}
+
+ESearchingTokenizer *
+e_search_bar_get_tokenizer (ESearchBar *search_bar)
+{
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL);
+
+ return search_bar->priv->tokenizer;
+}
+
+gboolean
+e_search_bar_get_active_search (ESearchBar *search_bar)
+{
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), FALSE);
+
+ return (search_bar->priv->active_search != NULL);
+}
+
+gboolean
+e_search_bar_get_case_sensitive (ESearchBar *search_bar)
+{
+ GtkToggleButton *button;
+
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), FALSE);
+
+ button = GTK_TOGGLE_BUTTON (search_bar->priv->case_sensitive_button);
+
+ return gtk_toggle_button_get_active (button);
+}
+
+void
+e_search_bar_set_case_sensitive (ESearchBar *search_bar,
+ gboolean case_sensitive)
+{
+ GtkToggleButton *button;
+
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ button = GTK_TOGGLE_BUTTON (search_bar->priv->case_sensitive_button);
+
+ gtk_toggle_button_set_active (button, case_sensitive);
+
+ g_object_notify (G_OBJECT (search_bar), "case-sensitive");
+}
+
+gchar *
+e_search_bar_get_text (ESearchBar *search_bar)
+{
+ GtkEntry *entry;
+ const gchar *text;
+
+ g_return_val_if_fail (E_IS_SEARCH_BAR (search_bar), NULL);
+
+ entry = GTK_ENTRY (search_bar->priv->entry);
+ text = gtk_entry_get_text (entry);
+
+ return g_strstrip (g_strdup (text));
+}
+
+void
+e_search_bar_set_text (ESearchBar *search_bar,
+ const gchar *text)
+{
+ GtkEntry *entry;
+
+ g_return_if_fail (E_IS_SEARCH_BAR (search_bar));
+
+ entry = GTK_ENTRY (search_bar->priv->entry);
+
+ if (text == NULL)
+ text = "";
+
+ /* This will trigger a "notify::text" signal. */
+ gtk_entry_set_text (entry, text);
+}
diff --git a/widgets/misc/e-search-bar.h b/widgets/misc/e-search-bar.h
new file mode 100644
index 0000000000..87e1023baf
--- /dev/null
+++ b/widgets/misc/e-search-bar.h
@@ -0,0 +1,87 @@
+/*
+ * e-search-bar.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_SEARCH_BAR_H
+#define E_SEARCH_BAR_H
+
+#include <gtk/gtk.h>
+#include <misc/e-searching-tokenizer.h>
+#include <misc/e-web-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SEARCH_BAR \
+ (e_search_bar_get_type ())
+#define E_SEARCH_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SEARCH_BAR, ESearchBar))
+#define E_SEARCH_BAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SEARCH_BAR, ESearchBarClass))
+#define E_IS_SEARCH_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SEARCH_BAR))
+#define E_IS_SEARCH_BAR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SEARCH_BAR))
+#define E_SEARCH_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SEARCH_BAR, ESearchBarClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESearchBar ESearchBar;
+typedef struct _ESearchBarClass ESearchBarClass;
+typedef struct _ESearchBarPrivate ESearchBarPrivate;
+
+struct _ESearchBar {
+ GtkHBox parent;
+ ESearchBarPrivate *priv;
+};
+
+struct _ESearchBarClass {
+ GtkHBoxClass parent_class;
+
+ /* Signals */
+ void (*changed) (ESearchBar *search_bar);
+ void (*clear) (ESearchBar *search_bar);
+};
+
+GType e_search_bar_get_type (void);
+GtkWidget * e_search_bar_new (EWebView *web_view);
+void e_search_bar_clear (ESearchBar *search_bar);
+void e_search_bar_changed (ESearchBar *search_bar);
+EWebView * e_search_bar_get_web_view (ESearchBar *search_bar);
+ESearchingTokenizer *
+ e_search_bar_get_tokenizer (ESearchBar *search_bar);
+gboolean e_search_bar_get_active_search
+ (ESearchBar *search_bar);
+gboolean e_search_bar_get_case_sensitive
+ (ESearchBar *search_bar);
+void e_search_bar_set_case_sensitive
+ (ESearchBar *search_bar,
+ gboolean case_sensitive);
+gchar * e_search_bar_get_text (ESearchBar *search_bar);
+void e_search_bar_set_text (ESearchBar *search_bar,
+ const gchar *text);
+
+G_END_DECLS
+
+#endif /* E_SEARCH_BAR_H */
diff --git a/widgets/misc/e-searching-tokenizer.c b/widgets/misc/e-searching-tokenizer.c
new file mode 100644
index 0000000000..8920a7dd21
--- /dev/null
+++ b/widgets/misc/e-searching-tokenizer.c
@@ -0,0 +1,1195 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Developed by Jon Trowbridge <trow@ximian.com>
+ * Rewritten significantly to handle multiple strings and improve performance
+ * by Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "e-searching-tokenizer.h"
+
+#include "libedataserver/e-memory.h"
+
+#define d(x)
+
+#define E_SEARCHING_TOKENIZER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerPrivate))
+
+enum {
+ MATCH_SIGNAL,
+ LAST_SIGNAL
+};
+
+static gpointer parent_class;
+static guint signals[LAST_SIGNAL];
+
+/* Utility functions */
+
+/* This is faster and safer than glib2's utf8 abomination, but isn't exported from camel as yet */
+static inline guint32
+camel_utf8_getc(const guchar **ptr)
+{
+ register guchar *p = (guchar *)*ptr;
+ register guchar c, r;
+ register guint32 v, m;
+
+again:
+ r = *p++;
+loop:
+ if (r < 0x80) {
+ *ptr = p;
+ v = r;
+ } else if (r < 0xfe) { /* valid start char? */
+ v = r;
+ m = 0x7f80; /* used to mask out the length bits */
+ do {
+ c = *p++;
+ if ((c & 0xc0) != 0x80) {
+ r = c;
+ goto loop;
+ }
+ v = (v<<6) | (c & 0x3f);
+ r<<=1;
+ m<<=5;
+ } while (r & 0x40);
+
+ *ptr = p;
+
+ v &= ~m;
+ } else {
+ goto again;
+ }
+
+ return v;
+}
+
+/* note: our tags of interest are 7 bit ascii, only, no need to do any fancy utf8 stuff */
+/* tags should be upper case
+ if this list gets longer than 10 entries, consider binary search */
+static const gchar *ignored_tags[] = {
+ "B", "I", "FONT", "TT", "EM", /* and more? */};
+
+static gint
+ignore_tag (const gchar *tag)
+{
+ gchar *t = g_alloca(strlen(tag)+1), c, *out;
+ const gchar *in;
+ gint i;
+
+ /* we could use a aho-corasick matcher here too ... but we wont */
+
+ /* normalise tag into 't'.
+ Note we use the property that the only tags we're interested in
+ are 7 bit ascii to shortcut and simplify case insensitivity */
+ in = tag+2; /* skip: TAG_ESCAPE '<' */
+ if (*in == '/')
+ in++;
+ out = t;
+ while ((c = *in++)) {
+ if (c >= 'A' && c <= 'Z')
+ *out++ = c;
+ else if (c >= 'a' && c <= 'z')
+ *out++ = c & 0xdf; /* convert ASCII to upper case */
+ else
+ /* maybe should check for > or ' ' etc? */
+ break;
+ }
+ *out = 0;
+
+ for (i = 0; i < G_N_ELEMENTS (ignored_tags); i++) {
+ if (strcmp (t, ignored_tags[i]) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ********************************************************************** */
+
+/* Aho-Corasick search tree implmeentation */
+
+/* next state if we match a character */
+struct _match {
+ struct _match *next;
+ guint32 ch;
+ struct _state *match;
+};
+
+/* tree state node */
+struct _state {
+ struct _match *matches;
+ guint final; /* max no of chars we just matched */
+ struct _state *fail; /* where to try next if we fail */
+ struct _state *next; /* next on this level? */
+};
+
+/* base tree structure */
+struct _trie {
+ struct _state root;
+ gint max_depth;
+
+ EMemChunk *state_chunks;
+ EMemChunk *match_chunks;
+};
+
+/* will be enabled only if debug is enabled */
+#if d(1) -1 != -1
+static void
+dump_trie (struct _state *s, gint d)
+{
+ gchar *p = g_alloca(d*2+1);
+ struct _match *m;
+
+ memset(p, ' ', d*2);
+ p[d*2]=0;
+
+ printf("%s[state] %p: %d fail->%p\n", p, s, s->final, s->fail);
+ m = s->matches;
+ while (m) {
+ printf(" %s'%c' -> %p\n", p, m->ch, m->match);
+ if (m->match)
+ dump_trie (m->match, d+1);
+ m = m->next;
+ }
+}
+#endif
+
+/* This builds an Aho-Corasick search trie for a set of utf8 words */
+/* See
+ http://www-sr.informatik.uni-tuebingen.de/~buehler/AC/AC.html
+ for a neat demo */
+
+static inline struct _match *
+g (struct _state *q, guint32 c)
+{
+ struct _match *m = q->matches;
+
+ while (m && m->ch != c)
+ m = m->next;
+
+ return m;
+}
+
+static struct _trie *
+build_trie (gint nocase, gint len, guchar **words)
+{
+ struct _state *q, *qt, *r;
+ const guchar *word;
+ struct _match *m, *n = NULL;
+ gint i, depth;
+ guint32 c;
+ struct _trie *trie;
+ gint state_depth_max, state_depth_size;
+ struct _state **state_depth;
+
+ trie = g_malloc(sizeof(*trie));
+ trie->root.matches = NULL;
+ trie->root.final = 0;
+ trie->root.fail = NULL;
+ trie->root.next = NULL;
+
+ trie->state_chunks = e_memchunk_new (8, sizeof(struct _state));
+ trie->match_chunks = e_memchunk_new (8, sizeof(struct _match));
+
+ /* This will correspond to the length of the longest pattern */
+ state_depth_size = 0;
+ state_depth_max = 64;
+ state_depth = g_malloc(sizeof(*state_depth[0])*64);
+ state_depth[0] = NULL;
+
+ /* Step 1: Build trie */
+
+ /* This just builds a tree that merges all common prefixes into the same branch */
+
+ for (i=0;i<len;i++) {
+ word = words[i];
+ q = &trie->root;
+ depth = 0;
+ while ((c = camel_utf8_getc (&word))) {
+ if (nocase)
+ c = g_unichar_tolower (c);
+ m = g (q, c);
+ if (m == NULL) {
+ m = e_memchunk_alloc(trie->match_chunks);
+ m->ch = c;
+ m->next = q->matches;
+ q->matches = m;
+ q = m->match = e_memchunk_alloc(trie->state_chunks);
+ q->matches = NULL;
+ q->fail = &trie->root;
+ q->final = 0;
+ if (state_depth_max < depth) {
+ state_depth_max += 64;
+ state_depth = g_realloc(state_depth, sizeof(*state_depth[0])*state_depth_max);
+ }
+ if (state_depth_size < depth) {
+ state_depth[depth] = NULL;
+ state_depth_size = depth;
+ }
+ q->next = state_depth[depth];
+ state_depth[depth] = q;
+ } else {
+ q = m->match;
+ }
+ depth++;
+ }
+ q->final = depth;
+ }
+
+ d(printf("Dumping trie:\n"));
+ d(dump_trie (&trie->root, 0));
+
+ /* Step 2: Build failure graph */
+
+ /* This searches for the longest substring which is a prefix of another string and
+ builds a graph of failure links so you can find multiple substrings concurrently,
+ using aho-corasick's algorithm */
+
+ for (i=0;i<state_depth_size;i++) {
+ q = state_depth[i];
+ while (q) {
+ m = q->matches;
+ while (m) {
+ c = m->ch;
+ qt = m->match;
+ r = q->fail;
+ while (r && (n = g (r, c)) == NULL)
+ r = r->fail;
+ if (r != NULL) {
+ qt->fail = n->match;
+ if (qt->fail->final > qt->final)
+ qt->final = qt->fail->final;
+ } else {
+ if ((n = g (&trie->root, c)))
+ qt->fail = n->match;
+ else
+ qt->fail = &trie->root;
+ }
+ m = m->next;
+ }
+ q = q->next;
+ }
+ }
+
+ d (printf("After failure analysis\n"));
+ d (dump_trie (&trie->root, 0));
+
+ g_free (state_depth);
+
+ trie->max_depth = state_depth_size;
+
+ return trie;
+}
+
+static void
+free_trie (struct _trie *t)
+{
+ e_memchunk_destroy(t->match_chunks);
+ e_memchunk_destroy(t->state_chunks);
+
+ g_free (t);
+}
+
+/* ********************************************************************** */
+
+/* html token searcher */
+
+struct _token {
+ struct _token *next;
+ struct _token *prev;
+ guint offset;
+ /* we need to copy the token for memory management, so why not copy it whole */
+ gchar tok[1];
+};
+
+/* stack of submatches currently being scanned, used for merging */
+struct _submatch {
+ guint offstart, offend; /* in bytes */
+};
+
+/* flags for new func */
+#define SEARCH_CASE (1)
+#define SEARCH_BOLD (2)
+
+struct _searcher {
+ struct _trie *t;
+
+ gchar *(*next_token)(); /* callbacks for more tokens */
+ gpointer next_data;
+
+ gint words; /* how many words */
+ gchar *tags, *tage; /* the tag we used to highlight */
+
+ gint flags; /* case sensitive or not */
+
+ struct _state *state; /* state is the current trie state */
+
+ gint matchcount;
+
+ GQueue input; /* pending 'input' tokens, processed but might match */
+ GQueue output; /* output tokens ready for source */
+
+ struct _token *current; /* for token output memory management */
+
+ guint32 offset; /* current offset through searchable stream? */
+ guint32 offout; /* last output position */
+
+ guint lastp; /* current position in rotating last buffer */
+ guint32 *last; /* buffer that goes back last 'n' positions */
+ guint32 last_mask; /* bitmask for efficient rotation calculation */
+
+ guint submatchp; /* submatch stack */
+ struct _submatch *submatches;
+};
+
+static void
+searcher_set_tokenfunc(struct _searcher *s, gchar *(*next)(), gpointer data)
+{
+ s->next_token = next;
+ s->next_data = data;
+}
+
+static struct _searcher *
+searcher_new (gint flags, gint argc, guchar **argv, const gchar *tags, const gchar *tage)
+{
+ gint i, m;
+ struct _searcher *s;
+
+ s = g_malloc(sizeof(*s));
+
+ s->t = build_trie ((flags&SEARCH_CASE) == 0, argc, argv);
+ s->words = argc;
+ s->tags = g_strdup (tags);
+ s->tage = g_strdup (tage);
+ s->flags = flags;
+ s->state = &s->t->root;
+ s->matchcount = 0;
+
+ g_queue_init (&s->input);
+ g_queue_init (&s->output);
+ s->current = NULL;
+
+ s->offset = 0;
+ s->offout = 0;
+
+ /* rotating queue of previous character positions */
+ m = s->t->max_depth+1;
+ i = 2;
+ while (i<m)
+ i<<=2;
+ s->last = g_malloc(sizeof(s->last[0])*i);
+ s->last_mask = i-1;
+ s->lastp = 0;
+
+ /* a stack of possible submatches */
+ s->submatchp = 0;
+ s->submatches = g_malloc(sizeof(s->submatches[0])*argc+1);
+
+ return s;
+}
+
+static void
+searcher_free (struct _searcher *s)
+{
+ struct _token *t;
+
+ while ((t = g_queue_pop_head (&s->input)) != NULL)
+ g_free (t);
+ while ((t = g_queue_pop_head (&s->output)) != NULL)
+ g_free (t);
+ g_free (s->tags);
+ g_free (s->tage);
+ g_free (s->last);
+ g_free (s->submatches);
+ free_trie (s->t);
+ g_free (s);
+}
+
+static struct _token *
+append_token (GQueue *queue, const gchar *tok, gint len)
+{
+ struct _token *token;
+
+ if (len == -1)
+ len = strlen(tok);
+ token = g_malloc(sizeof(*token) + len+1);
+ token->offset = 0; /* set by caller when required */
+ memcpy(token->tok, tok, len);
+ token->tok[len] = 0;
+ g_queue_push_tail (queue, token);
+
+ return token;
+}
+
+#define free_token(x) (g_free (x))
+
+static void
+output_token(struct _searcher *s, struct _token *token)
+{
+ gint offend;
+ gint left, pre;
+
+ if (token->tok[0] == TAG_ESCAPE) {
+ if (token->offset >= s->offout) {
+ d (printf("moving tag token '%s' from input to output\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ g_queue_push_tail (&s->output, token);
+ } else {
+ d (printf("discarding tag token '%s' from input\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ free_token(token);
+ }
+ } else {
+ offend = token->offset + strlen(token->tok);
+ left = offend-s->offout;
+ if (left > 0) {
+ pre = s->offout - token->offset;
+ if (pre>0)
+ memmove (token->tok, token->tok+pre, left+1);
+ d (printf("adding partial remaining/failed '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ s->offout = offend;
+ g_queue_push_tail (&s->output, token);
+ } else {
+ d (printf("discarding whole token '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ free_token(token);
+ }
+ }
+}
+
+static struct _token *
+find_token(struct _searcher *s, gint start)
+{
+ GList *link;
+
+ /* find token which is start token, from end of list back */
+ link = g_queue_peek_tail_link (&s->input);
+ while (link != NULL) {
+ struct _token *token = link->data;
+
+ if (token->offset <= start)
+ return token;
+
+ link = g_list_previous (link);
+ }
+
+ return NULL;
+}
+
+static void
+output_match(struct _searcher *s, guint start, guint end)
+{
+ register struct _token *token;
+ struct _token *starttoken, *endtoken;
+ gchar b[8];
+
+ d (printf("output match: %d-%d at %d\n", start, end, s->offout));
+
+ starttoken = find_token(s, start);
+ endtoken = find_token(s, end);
+
+ if (starttoken == NULL || endtoken == NULL) {
+ d (printf("Cannot find match history for match %d-%d\n", start, end));
+ return;
+ }
+
+ d (printf("start in token '%s'\n", starttoken->tok[0]==TAG_ESCAPE?starttoken->tok+1:starttoken->tok));
+ d (printf("end in token '%s'\n", endtoken->tok[0]==TAG_ESCAPE?endtoken->tok+1:endtoken->tok));
+
+ /* output pending stuff that didn't match afterall */
+ while ((struct _token *) g_queue_peek_head (&s->input) != starttoken) {
+ token = g_queue_pop_head (&s->input);
+ d (printf("appending failed match '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ output_token(s, token);
+ }
+
+ /* output any pre-match text */
+ if (s->offout < start) {
+ token = append_token(&s->output, starttoken->tok + (s->offout-starttoken->offset), start-s->offout);
+ d (printf("adding pre-match text '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ s->offout = start;
+ }
+
+ /* output highlight/bold */
+ if (s->flags & SEARCH_BOLD) {
+ sprintf(b, "%c<b>", (gchar)TAG_ESCAPE);
+ append_token(&s->output, b, -1);
+ }
+ if (s->tags)
+ append_token(&s->output, s->tags, -1);
+
+ /* output match node (s) */
+ if (starttoken != endtoken) {
+ while ((struct _token *) g_queue_peek_head (&s->input) != endtoken) {
+ token = g_queue_pop_head (&s->input);
+ d (printf("appending (partial) match node (head) '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ output_token(s, token);
+ }
+ }
+
+ /* any remaining partial content */
+ if (s->offout < end) {
+ token = append_token(&s->output, endtoken->tok+(s->offout-endtoken->offset), end-s->offout);
+ d (printf("appending (partial) match node (tail) '%s'\n", token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+ s->offout = end;
+ }
+
+ /* end highlight */
+ if (s->tage)
+ append_token(&s->output, s->tage, -1);
+
+ /* and close bold if we need to */
+ if (s->flags & SEARCH_BOLD) {
+ sprintf(b, "%c</b>", (gchar)TAG_ESCAPE);
+ append_token(&s->output, b, -1);
+ }
+}
+
+/* output any sub-pending blocks */
+static void
+output_subpending (struct _searcher *s)
+{
+ gint i;
+
+ for (i=s->submatchp-1;i>=0;i--)
+ output_match(s, s->submatches[i].offstart, s->submatches[i].offend);
+ s->submatchp = 0;
+}
+
+/* returns true if a merge took place */
+static gint
+merge_subpending (struct _searcher *s, gint offstart, gint offend)
+{
+ gint i;
+
+ /* merges overlapping or abutting match strings */
+ if (s->submatchp &&
+ s->submatches[s->submatchp-1].offend >= offstart) {
+
+ /* go from end, any that match 'invalidate' follow-on ones too */
+ for (i=s->submatchp-1;i>=0;i--) {
+ if (s->submatches[i].offend >= offstart) {
+ if (offstart < s->submatches[i].offstart)
+ s->submatches[i].offstart = offstart;
+ s->submatches[i].offend = offend;
+ if (s->submatchp > i)
+ s->submatchp = i+1;
+ }
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+push_subpending (struct _searcher *s, gint offstart, gint offend)
+{
+ /* This is really an assertion, we just ignore the last pending match instead of crashing though */
+ if (s->submatchp >= s->words) {
+ d (printf("ERROR: submatch pending stack overflow\n"));
+ s->submatchp = s->words-1;
+ }
+
+ s->submatches[s->submatchp].offstart = offstart;
+ s->submatches[s->submatchp].offend = offend;
+ s->submatchp++;
+}
+
+/* move any (partial) tokens from input to output if they are beyond the current output position */
+static void
+output_pending (struct _searcher *s)
+{
+ struct _token *token;
+
+ while ((token = g_queue_pop_head (&s->input)) != NULL)
+ output_token(s, token);
+}
+
+/* flushes any nodes we cannot possibly match anymore */
+static void
+flush_extra(struct _searcher *s)
+{
+ guint start;
+ gint i;
+ struct _token *starttoken, *token;
+
+ /* find earliest gchar that can be in contention */
+ start = s->offset - s->t->max_depth;
+ for (i=0;i<s->submatchp;i++)
+ if (s->submatches[i].offstart < start)
+ start = s->submatches[i].offstart;
+
+ /* now, flush out any tokens which are before this point */
+ starttoken = find_token(s, start);
+ if (starttoken == NULL)
+ return;
+
+ while ((struct _token *) g_queue_peek_head (&s->input) != starttoken) {
+ token = g_queue_pop_head (&s->input);
+ output_token(s, token);
+ }
+}
+
+static gchar *
+searcher_next_token(struct _searcher *s)
+{
+ struct _token *token;
+ const guchar *tok, *stok, *pre_tok;
+ struct _trie *t = s->t;
+ struct _state *q = s->state;
+ struct _match *m = NULL;
+ gint offstart, offend;
+ guint32 c;
+
+ while (g_queue_is_empty (&s->output)) {
+ /* get next token */
+ tok = (guchar *)s->next_token(s->next_data);
+ if (tok == NULL) {
+ output_subpending (s);
+ output_pending (s);
+ break;
+ }
+
+ /* we dont always have to copy each token, e.g. if we dont match anything */
+ token = append_token(&s->input, (gchar *)tok, -1);
+ token->offset = s->offset;
+ tok = (guchar *)token->tok;
+
+ d (printf("new token %d '%s'\n", token->offset, token->tok[0]==TAG_ESCAPE?token->tok+1:token->tok));
+
+ /* tag test, reset state on unknown tags */
+ if (tok[0] == TAG_ESCAPE) {
+ if (!ignore_tag ((gchar *)tok)) {
+ /* force reset */
+ output_subpending (s);
+ output_pending (s);
+ q = &t->root;
+ }
+
+ continue;
+ }
+
+ /* process whole token */
+ pre_tok = stok = tok;
+ while ((c = camel_utf8_getc (&tok))) {
+ if ((s->flags & SEARCH_CASE) == 0)
+ c = g_unichar_tolower (c);
+ while (q && (m = g (q, c)) == NULL)
+ q = q->fail;
+ if (q == NULL) {
+ /* mismatch ... reset state */
+ output_subpending (s);
+ q = &t->root;
+ } else if (m != NULL) {
+ /* keep track of previous offsets of utf8 chars, rotating buffer */
+ s->last[s->lastp] = s->offset + (pre_tok-stok);
+ s->lastp = (s->lastp+1)&s->last_mask;
+
+ q = m->match;
+ /* we have a match of q->final characters for a matching word */
+ if (q->final) {
+ s->matchcount++;
+
+ /* use the last buffer to find the real offset of this gchar */
+ offstart = s->last[(s->lastp - q->final)&s->last_mask];
+ offend = s->offset + (tok - stok);
+
+ if (q->matches == NULL) {
+ if (s->submatchp == 0) {
+ /* nothing pending, always put something in so we can try merge */
+ push_subpending (s, offstart, offend);
+ } else if (!merge_subpending (s, offstart, offend)) {
+ /* can't merge, output what we have, and start againt */
+ output_subpending (s);
+ push_subpending (s, offstart, offend);
+ /*output_match(s, offstart, offend);*/
+ } else if (g_queue_get_length (&s->input) > 8) {
+ /* we're continuing to match and merge, but we have a lot of stuff
+ waiting, so flush it out now since this is a safe point to do it */
+ output_subpending (s);
+ }
+ } else {
+ /* merge/add subpending */
+ if (!merge_subpending (s, offstart, offend))
+ push_subpending (s, offstart, offend);
+ }
+ }
+ }
+ pre_tok = tok;
+ }
+
+ s->offset += (pre_tok-stok);
+
+ flush_extra(s);
+ }
+
+ s->state = q;
+
+ if (s->current)
+ free_token(s->current);
+
+ s->current = token = g_queue_pop_head (&s->output);
+
+ return token ? g_strdup (token->tok) : NULL;
+}
+
+static gchar *
+searcher_peek_token(struct _searcher *s)
+{
+ gchar *tok;
+
+ /* we just get it and then put it back, it's fast enuf */
+ tok = searcher_next_token(s);
+ if (tok) {
+ /* need to clear this so we dont free it while its still active */
+ g_queue_push_head (&s->output, s->current);
+ s->current = NULL;
+ }
+
+ return tok;
+}
+
+static gint
+searcher_pending (struct _searcher *s)
+{
+ return !(g_queue_is_empty (&s->input) && g_queue_is_empty (&s->output));
+}
+
+/* ********************************************************************** */
+
+struct _search_info {
+ GPtrArray *strv;
+ gchar *color;
+ guint size:8;
+ guint flags:8;
+};
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
+
+static struct _search_info *
+search_info_new (void)
+{
+ struct _search_info *s;
+
+ s = g_malloc0(sizeof(struct _search_info));
+ s->strv = g_ptr_array_new ();
+
+ return s;
+}
+
+static void
+search_info_set_flags(struct _search_info *si, guint flags, guint mask)
+{
+ si->flags = (si->flags & ~mask) | (flags & mask);
+}
+
+static void
+search_info_set_color (struct _search_info *si, const gchar *color)
+{
+ g_free (si->color);
+ si->color = g_strdup (color);
+}
+
+static void
+search_info_add_string (struct _search_info *si, const gchar *s)
+{
+ const guchar *start;
+ guint32 c;
+
+ if (s && s[0]) {
+ const guchar *us = (guchar *) s;
+ /* strip leading whitespace */
+ start = us;
+ while ((c = camel_utf8_getc (&us))) {
+ if (!g_unichar_isspace (c)) {
+ break;
+ }
+ start = us;
+ }
+ /* should probably also strip trailing, but i'm lazy today */
+ if (start[0])
+ g_ptr_array_add (si->strv, g_strdup ((gchar *)start));
+ }
+}
+
+static void
+search_info_clear (struct _search_info *si)
+{
+ gint i;
+
+ for (i=0;i<si->strv->len;i++)
+ g_free (si->strv->pdata[i]);
+
+ g_ptr_array_set_size (si->strv, 0);
+}
+
+static void
+search_info_free (struct _search_info *si)
+{
+ gint i;
+
+ for (i=0;i<si->strv->len;i++)
+ g_free (si->strv->pdata[i]);
+
+ g_ptr_array_free (si->strv, TRUE);
+ g_free (si->color);
+ g_free (si);
+}
+
+static struct _search_info *
+search_info_clone (struct _search_info *si)
+{
+ struct _search_info *out;
+ gint i;
+
+ out = search_info_new ();
+ for (i=0;i<si->strv->len;i++)
+ g_ptr_array_add (out->strv, g_strdup (si->strv->pdata[i]));
+ out->color = g_strdup (si->color);
+ out->flags = si->flags;
+ out->size = si->size;
+
+ return out;
+}
+
+static struct _searcher *
+search_info_to_searcher (struct _search_info *si)
+{
+ gchar *tags, *tage;
+ const gchar *col;
+
+ if (si->strv->len == 0)
+ return NULL;
+
+ if (si->color == NULL)
+ col = "red";
+ else
+ col = si->color;
+
+ tags = g_alloca(20+strlen(col));
+ sprintf(tags, "%c<font color=\"%s\">", TAG_ESCAPE, col);
+ tage = g_alloca(20);
+ sprintf(tage, "%c</font>", TAG_ESCAPE);
+
+ return searcher_new (si->flags, si->strv->len, (guchar **)si->strv->pdata, tags, tage);
+}
+
+/* ********************************************************************** */
+
+struct _ESearchingTokenizerPrivate {
+ struct _search_info *primary, *secondary;
+ struct _searcher *engine;
+};
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
+
+/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
+
+/* blah blah the htmltokeniser doesn't like being asked
+ for a token if it doens't hvae any! */
+static gchar *
+get_token (HTMLTokenizer *tokenizer)
+{
+ HTMLTokenizerClass *class = HTML_TOKENIZER_CLASS (parent_class);
+
+ if (class->has_more (tokenizer))
+ return class->next_token (tokenizer);
+
+ return NULL;
+}
+
+/* proxy matched event, not sure what its for otherwise */
+static void
+matched (ESearchingTokenizer *tokenizer)
+{
+ /*++tokenizer->priv->match_count;*/
+ g_signal_emit (tokenizer, signals[MATCH_SIGNAL], 0);
+}
+
+/* ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** */
+
+static void
+searching_tokenizer_finalize (GObject *object)
+{
+ ESearchingTokenizerPrivate *priv;
+
+ priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (object);
+
+ search_info_free (priv->primary);
+ search_info_free (priv->secondary);
+
+ if (priv->engine != NULL)
+ searcher_free (priv->engine);
+
+ /* Chain up to parent's finalize () method. */
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+searching_tokenizer_begin (HTMLTokenizer *tokenizer,
+ const gchar *content_type)
+{
+ ESearchingTokenizerPrivate *priv;
+
+ priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer);
+
+ /* reset search */
+ if (priv->engine != NULL) {
+ searcher_free (priv->engine);
+ priv->engine = NULL;
+ }
+
+ if ((priv->engine = search_info_to_searcher (priv->primary))
+ || (priv->engine = search_info_to_searcher (priv->secondary))) {
+ searcher_set_tokenfunc(priv->engine, get_token, tokenizer);
+ }
+ /* else - no engine, no search active */
+
+ /* Chain up to parent's begin() method. */
+ HTML_TOKENIZER_CLASS (parent_class)->begin (tokenizer, content_type);
+}
+
+static gchar *
+searching_tokenizer_peek_token (HTMLTokenizer *tokenizer)
+{
+ ESearchingTokenizerPrivate *priv;
+
+ priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer);
+
+ if (priv->engine != NULL)
+ return searcher_peek_token (priv->engine);
+
+ /* Chain up to parent's peek_token() method. */
+ return HTML_TOKENIZER_CLASS (parent_class)->peek_token (tokenizer);
+}
+
+static gchar *
+searching_tokenizer_next_token (HTMLTokenizer *tokenizer)
+{
+ ESearchingTokenizerPrivate *priv;
+ gint oldmatched;
+ gchar *token;
+
+ priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer);
+
+ /* If no search is active, just use the default method. */
+ if (priv->engine == NULL)
+ return HTML_TOKENIZER_CLASS (parent_class)->next_token (tokenizer);
+
+ oldmatched = priv->engine->matchcount;
+
+ token = searcher_next_token (priv->engine);
+
+ /* not sure if this has to be accurate or just say we had some matches */
+ if (oldmatched != priv->engine->matchcount)
+ g_signal_emit (tokenizer, signals[MATCH_SIGNAL], 0);
+
+ return token;
+}
+
+static gboolean
+searching_tokenizer_has_more (HTMLTokenizer *tokenizer)
+{
+ ESearchingTokenizerPrivate *priv;
+
+ priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer);
+
+ return (priv->engine != NULL && searcher_pending (priv->engine)) ||
+ HTML_TOKENIZER_CLASS (parent_class)->has_more (tokenizer);
+}
+
+static HTMLTokenizer *
+searching_tokenizer_clone (HTMLTokenizer *tokenizer)
+{
+ ESearchingTokenizer *orig_st;
+ ESearchingTokenizer *new_st;
+
+ orig_st = E_SEARCHING_TOKENIZER (tokenizer);
+ new_st = e_searching_tokenizer_new ();
+
+ search_info_free (new_st->priv->primary);
+ search_info_free (new_st->priv->secondary);
+
+ new_st->priv->primary = search_info_clone (orig_st->priv->primary);
+ new_st->priv->secondary = search_info_clone (orig_st->priv->secondary);
+
+ g_signal_connect_swapped (
+ new_st, "match", G_CALLBACK (matched), orig_st);
+
+ return HTML_TOKENIZER (new_st);
+}
+static void
+searching_tokenizer_class_init (ESearchingTokenizerClass *class)
+{
+ GObjectClass *object_class;
+ HTMLTokenizerClass *tokenizer_class;
+
+ parent_class = g_type_class_peek_parent (class);
+ g_type_class_add_private (class, sizeof (ESearchingTokenizerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = searching_tokenizer_finalize;
+
+ tokenizer_class = HTML_TOKENIZER_CLASS (class);
+ tokenizer_class->begin = searching_tokenizer_begin;
+ tokenizer_class->peek_token = searching_tokenizer_peek_token;
+ tokenizer_class->next_token = searching_tokenizer_next_token;
+ tokenizer_class->has_more = searching_tokenizer_has_more;
+ tokenizer_class->clone = searching_tokenizer_clone;
+
+ signals[MATCH_SIGNAL] = g_signal_new (
+ "match",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ESearchingTokenizerClass, match),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+searching_tokenizer_init (ESearchingTokenizer *tokenizer)
+{
+ tokenizer->priv = E_SEARCHING_TOKENIZER_GET_PRIVATE (tokenizer);
+
+ tokenizer->priv->primary = search_info_new ();
+ search_info_set_flags (
+ tokenizer->priv->primary,
+ SEARCH_BOLD, SEARCH_CASE | SEARCH_BOLD);
+ search_info_set_color (tokenizer->priv->primary, "red");
+
+ tokenizer->priv->secondary = search_info_new ();
+ search_info_set_flags(
+ tokenizer->priv->secondary,
+ SEARCH_BOLD, SEARCH_CASE | SEARCH_BOLD);
+ search_info_set_color (tokenizer->priv->secondary, "purple");
+}
+
+GType
+e_searching_tokenizer_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ static const GTypeInfo type_info = {
+ sizeof (ESearchingTokenizerClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) searching_tokenizer_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (ESearchingTokenizer),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) searching_tokenizer_init,
+ NULL /* value_table */
+ };
+
+ type = g_type_register_static (
+ HTML_TYPE_TOKENIZER, "ESearchingTokenizer",
+ &type_info, 0);
+ }
+
+ return type;
+}
+
+ESearchingTokenizer *
+e_searching_tokenizer_new (void)
+{
+ return g_object_new (E_TYPE_SEARCHING_TOKENIZER, NULL);
+}
+
+void
+e_searching_tokenizer_set_primary_search_string (ESearchingTokenizer *tokenizer,
+ const gchar *primary_string)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_clear (tokenizer->priv->primary);
+ search_info_add_string (tokenizer->priv->primary, primary_string);
+}
+
+void
+e_searching_tokenizer_add_primary_search_string (ESearchingTokenizer *tokenizer,
+ const gchar *primary_string)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_add_string (tokenizer->priv->primary, primary_string);
+}
+
+void
+e_searching_tokenizer_set_primary_case_sensitivity (ESearchingTokenizer *tokenizer,
+ gboolean case_sensitive)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_set_flags (
+ tokenizer->priv->primary,
+ case_sensitive ? SEARCH_CASE : 0, SEARCH_CASE);
+}
+
+void
+e_searching_tokenizer_set_secondary_search_string (ESearchingTokenizer *tokenizer,
+ const gchar *secondary_string)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_clear (tokenizer->priv->secondary);
+ search_info_add_string (tokenizer->priv->secondary, secondary_string);
+}
+
+void
+e_searching_tokenizer_add_secondary_search_string (ESearchingTokenizer *tokenizer,
+ const gchar *secondary_string)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_add_string (tokenizer->priv->secondary, secondary_string);
+}
+
+void
+e_searching_tokenizer_set_secondary_case_sensitivity (ESearchingTokenizer *tokenizer,
+ gboolean case_sensitive)
+{
+ g_return_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer));
+
+ search_info_set_flags (
+ tokenizer->priv->secondary,
+ case_sensitive ? SEARCH_CASE : 0, SEARCH_CASE);
+}
+
+/* Note: only returns the primary search string count */
+gint
+e_searching_tokenizer_match_count (ESearchingTokenizer *tokenizer)
+{
+ g_return_val_if_fail (E_IS_SEARCHING_TOKENIZER (tokenizer), -1);
+
+ if (tokenizer->priv->engine && tokenizer->priv->primary->strv->len)
+ return tokenizer->priv->engine->matchcount;
+
+ return 0;
+}
diff --git a/widgets/misc/e-searching-tokenizer.h b/widgets/misc/e-searching-tokenizer.h
new file mode 100644
index 0000000000..7eb603a25d
--- /dev/null
+++ b/widgets/misc/e-searching-tokenizer.h
@@ -0,0 +1,92 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ *
+ * Authors:
+ * Jon Trowbridge <trow@ximian.com>
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#ifndef E_SEARCHING_TOKENIZER_H
+#define E_SEARCHING_TOKENIZER_H
+
+#include <glib.h>
+#include <gtkhtml/htmltokenizer.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SEARCHING_TOKENIZER \
+ (e_searching_tokenizer_get_type ())
+#define E_SEARCHING_TOKENIZER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizer))
+#define E_SEARCHING_TOKENIZER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerClass))
+#define E_IS_SEARCHING_TOKENIZER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SEARCHING_TOKENIZER))
+#define E_IS_SEARCHING_TOKENIZER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SEARCHING_TOKENIZER))
+#define E_SEARCH_TOKENIZER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SEARCHING_TOKENIZER, ESearchingTokenizerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESearchingTokenizer ESearchingTokenizer;
+typedef struct _ESearchingTokenizerClass ESearchingTokenizerClass;
+typedef struct _ESearchingTokenizerPrivate ESearchingTokenizerPrivate;
+
+struct _ESearchingTokenizer {
+ HTMLTokenizer parent;
+ ESearchingTokenizerPrivate *priv;
+};
+
+struct _ESearchingTokenizerClass {
+ HTMLTokenizerClass parent_class;
+
+ void (*match) (ESearchingTokenizer *tokenizer);
+};
+
+GType e_searching_tokenizer_get_type (void);
+ESearchingTokenizer *
+ e_searching_tokenizer_new (void);
+void e_searching_tokenizer_set_primary_search_string
+ (ESearchingTokenizer *tokenizer,
+ const gchar *primary_string);
+void e_searching_tokenizer_add_primary_search_string
+ (ESearchingTokenizer *tokenizer,
+ const gchar *primary_string);
+void e_searching_tokenizer_set_primary_case_sensitivity
+ (ESearchingTokenizer *tokenizer,
+ gboolean case_sensitive);
+void e_searching_tokenizer_set_secondary_search_string
+ (ESearchingTokenizer *tokenizer,
+ const gchar *secondary_string);
+void e_searching_tokenizer_add_secondary_search_string
+ (ESearchingTokenizer *tokenizer,
+ const gchar *secondary_string);
+void e_searching_tokenizer_set_secondary_case_sensitivity
+ (ESearchingTokenizer *tokenizer,
+ gboolean case_sensitive);
+gint e_searching_tokenizer_match_count
+ (ESearchingTokenizer *tokenizer);
+
+G_END_DECLS
+
+#endif /* E_SEARCHING_TOKENIZER_H */