From 39ee1b7890e06779b47f0fc11925d12caa206c39 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Sun, 17 Jan 2010 17:47:08 -0500 Subject: Give all preview panes a search bar. Use Shift+Ctrl+F as the accelerator for consistency with the mailer. --- mail/Makefile.am | 4 - mail/e-mail-browser.c | 30 +- mail/e-mail-search-bar.c | 823 ---------------- mail/e-mail-search-bar.h | 87 -- mail/e-searching-tokenizer.c | 1195 ----------------------- mail/e-searching-tokenizer.h | 92 -- modules/addressbook/e-book-shell-content.c | 61 +- modules/addressbook/e-book-shell-content.h | 7 +- modules/addressbook/e-book-shell-view-actions.c | 37 +- modules/addressbook/e-book-shell-view-actions.h | 2 + modules/addressbook/e-book-shell-view.c | 4 + modules/calendar/e-memo-shell-content.c | 59 +- modules/calendar/e-memo-shell-content.h | 7 +- modules/calendar/e-memo-shell-view-actions.c | 37 +- modules/calendar/e-memo-shell-view-actions.h | 2 + modules/calendar/e-memo-shell-view-private.c | 7 +- modules/calendar/e-memo-shell-view.c | 13 +- modules/calendar/e-task-shell-content.c | 59 +- modules/calendar/e-task-shell-content.h | 7 +- modules/calendar/e-task-shell-view-actions.c | 37 +- modules/calendar/e-task-shell-view-actions.h | 2 + modules/calendar/e-task-shell-view-private.c | 7 +- modules/calendar/e-task-shell-view.c | 13 +- modules/mail/e-mail-shell-content.c | 45 +- ui/evolution-contacts.ui | 2 + ui/evolution-memos.ui | 2 + ui/evolution-tasks.ui | 2 + widgets/misc/Makefile.am | 6 + widgets/misc/e-preview-pane.c | 275 ++++++ widgets/misc/e-preview-pane.h | 74 ++ widgets/misc/e-search-bar.c | 825 ++++++++++++++++ widgets/misc/e-search-bar.h | 87 ++ widgets/misc/e-searching-tokenizer.c | 1195 +++++++++++++++++++++++ widgets/misc/e-searching-tokenizer.h | 92 ++ 34 files changed, 2813 insertions(+), 2384 deletions(-) delete mode 100644 mail/e-mail-search-bar.c delete mode 100644 mail/e-mail-search-bar.h delete mode 100644 mail/e-searching-tokenizer.c delete mode 100644 mail/e-searching-tokenizer.h create mode 100644 widgets/misc/e-preview-pane.c create mode 100644 widgets/misc/e-preview-pane.h create mode 100644 widgets/misc/e-search-bar.c create mode 100644 widgets/misc/e-search-bar.h create mode 100644 widgets/misc/e-searching-tokenizer.c create mode 100644 widgets/misc/e-searching-tokenizer.h diff --git a/mail/Makefile.am b/mail/Makefile.am index 2e6a69a7f6..c31b4cd406 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -48,11 +48,9 @@ mailinclude_HEADERS = \ e-mail-migrate.h \ e-mail-reader.h \ e-mail-reader-utils.h \ - e-mail-search-bar.h \ e-mail-sidebar.h \ e-mail-store.h \ e-mail-tag-editor.h \ - e-searching-tokenizer.h \ em-account-editor.h \ em-composer-utils.h \ em-config.h \ @@ -107,11 +105,9 @@ libevolution_mail_la_SOURCES = \ e-mail-migrate.c \ e-mail-reader.c \ e-mail-reader-utils.c \ - e-mail-search-bar.c \ e-mail-sidebar.c \ e-mail-store.c \ e-mail-tag-editor.c \ - e-searching-tokenizer.c \ em-account-editor.c \ em-composer-utils.c \ em-config.c \ diff --git a/mail/e-mail-browser.c b/mail/e-mail-browser.c index ab27694778..b0172ad3c2 100644 --- a/mail/e-mail-browser.c +++ b/mail/e-mail-browser.c @@ -29,10 +29,10 @@ #include "e-util/e-plugin-ui.h" #include "e-util/gconf-bridge.h" #include "shell/e-shell.h" +#include "widgets/misc/e-preview-pane.h" #include "mail/e-mail-reader.h" #include "mail/e-mail-reader-utils.h" -#include "mail/e-mail-search-bar.h" #include "mail/em-folder-tree-model.h" #include "mail/em-format-html-display.h" #include "mail/message-list.h" @@ -388,6 +388,7 @@ mail_browser_constructed (GObject *object) EMailReader *reader; EShellBackend *shell_backend; EShell *shell; + ESearchBar *search_bar; GConfBridge *bridge; GtkAccelGroup *accel_group; GtkActionGroup *action_group; @@ -468,15 +469,6 @@ mail_browser_constructed (GObject *object) priv->statusbar = g_object_ref (widget); gtk_widget_show (widget); - widget = e_mail_search_bar_new (web_view); - gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->search_bar = g_object_ref (widget); - gtk_widget_hide (widget); - - g_signal_connect_swapped ( - widget, "changed", - G_CALLBACK (em_format_redraw), html_display); - widget = gtk_ui_manager_get_widget (ui_manager, "/main-menu"); gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); priv->main_menu = g_object_ref (widget); @@ -487,20 +479,18 @@ mail_browser_constructed (GObject *object) priv->main_toolbar = g_object_ref (widget); gtk_widget_show (widget); - 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_widget_show (GTK_WIDGET (web_view)); + + widget = e_preview_pane_new (web_view); gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); gtk_widget_show (widget); - container = widget; + search_bar = e_preview_pane_get_search_bar (E_PREVIEW_PANE (widget)); + priv->search_bar = g_object_ref (search_bar); - widget = GTK_WIDGET (EM_FORMAT_HTML (html_display)->html); - gtk_container_add (GTK_CONTAINER (container), widget); - gtk_widget_show (widget); + g_signal_connect_swapped ( + search_bar, "changed", + G_CALLBACK (em_format_redraw), priv->html_display); /* Bind GObject properties to GConf keys. */ diff --git a/mail/e-mail-search-bar.c b/mail/e-mail-search-bar.c deleted file mode 100644 index c4b552a8cc..0000000000 --- a/mail/e-mail-search-bar.c +++ /dev/null @@ -1,823 +0,0 @@ -/* - * e-mail-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 - * - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#include "e-mail-search-bar.h" - -#include -#include -#include - -#include "e-util/e-binding.h" - -#define E_MAIL_SEARCH_BAR_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), E_TYPE_MAIL_SEARCH_BAR, EMailSearchBarPrivate)) - -struct _EMailSearchBarPrivate { - 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 -mail_search_bar_update_matches (EMailSearchBar *search_bar) -{ - ESearchingTokenizer *tokenizer; - GtkWidget *matches_label; - gint matches; - gchar *text; - - tokenizer = e_mail_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 -mail_search_bar_update_tokenizer (EMailSearchBar *search_bar) -{ - ESearchingTokenizer *tokenizer; - gboolean case_sensitive; - gchar *active_search; - - tokenizer = e_mail_search_bar_get_tokenizer (search_bar); - case_sensitive = e_mail_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_mail_search_bar_changed (search_bar); -} - -static void -mail_search_bar_find (EMailSearchBar *search_bar, - gboolean search_forward) -{ - EWebView *web_view; - GtkWidget *widget; - gboolean case_sensitive; - gboolean new_search; - gboolean wrapped = FALSE; - gchar *text; - - web_view = e_mail_search_bar_get_web_view (search_bar); - case_sensitive = e_mail_search_bar_get_case_sensitive (search_bar); - text = e_mail_search_bar_get_text (search_bar); - - if (text == NULL || *text == '\0') { - e_mail_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; - mail_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 -mail_search_bar_changed_cb (EMailSearchBar *search_bar) -{ - g_object_notify (G_OBJECT (search_bar), "text"); -} - -static void -mail_search_bar_find_next_cb (EMailSearchBar *search_bar) -{ - mail_search_bar_find (search_bar, TRUE); -} - -static void -mail_search_bar_find_previous_cb (EMailSearchBar *search_bar) -{ - mail_search_bar_find (search_bar, FALSE); -} - -static void -mail_search_bar_icon_release_cb (EMailSearchBar *search_bar, - GtkEntryIconPosition icon_pos, - GdkEvent *event) -{ - g_return_if_fail (icon_pos == GTK_ENTRY_ICON_SECONDARY); - - e_mail_search_bar_clear (search_bar); - gtk_widget_grab_focus (search_bar->priv->entry); -} - -static void -mail_search_bar_toggled_cb (EMailSearchBar *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 -mail_search_bar_set_web_view (EMailSearchBar *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_mail_search_bar_get_tokenizer (search_bar); - gtk_html_set_tokenizer (html, HTML_TOKENIZER (tokenizer)); -} - -static void -mail_search_bar_set_property (GObject *object, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - switch (property_id) { - case PROP_CASE_SENSITIVE: - e_mail_search_bar_set_case_sensitive ( - E_MAIL_SEARCH_BAR (object), - g_value_get_boolean (value)); - return; - - case PROP_TEXT: - e_mail_search_bar_set_text ( - E_MAIL_SEARCH_BAR (object), - g_value_get_string (value)); - return; - - case PROP_WEB_VIEW: - mail_search_bar_set_web_view ( - E_MAIL_SEARCH_BAR (object), - g_value_get_object (value)); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -mail_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_mail_search_bar_get_active_search ( - E_MAIL_SEARCH_BAR (object))); - return; - - case PROP_CASE_SENSITIVE: - g_value_set_boolean ( - value, e_mail_search_bar_get_case_sensitive ( - E_MAIL_SEARCH_BAR (object))); - return; - - case PROP_TEXT: - g_value_take_string ( - value, e_mail_search_bar_get_text ( - E_MAIL_SEARCH_BAR (object))); - return; - - case PROP_WEB_VIEW: - g_value_set_object ( - value, e_mail_search_bar_get_web_view ( - E_MAIL_SEARCH_BAR (object))); - return; - } - - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); -} - -static void -mail_search_bar_dispose (GObject *object) -{ - EMailSearchBarPrivate *priv; - - priv = E_MAIL_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 -mail_search_bar_finalize (GObject *object) -{ - EMailSearchBarPrivate *priv; - - priv = E_MAIL_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 -mail_search_bar_constructed (GObject *object) -{ - EMailSearchBarPrivate *priv; - - priv = E_MAIL_SEARCH_BAR_GET_PRIVATE (object); - - e_mutual_binding_new ( - object, "case-sensitive", - priv->case_sensitive_button, "active"); -} - -static void -mail_search_bar_show (GtkWidget *widget) -{ - EMailSearchBar *search_bar; - - search_bar = E_MAIL_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); - - mail_search_bar_update_tokenizer (search_bar); -} - -static void -mail_search_bar_hide (GtkWidget *widget) -{ - EMailSearchBar *search_bar; - - search_bar = E_MAIL_SEARCH_BAR (widget); - - /* Chain up to parent's hide() method. */ - GTK_WIDGET_CLASS (parent_class)->hide (widget); - - mail_search_bar_update_tokenizer (search_bar); -} - -static gboolean -mail_search_bar_key_press_event (GtkWidget *widget, - GdkEventKey *event) -{ - if (event->keyval == GDK_Escape) { - gtk_widget_hide (widget); - return TRUE; - } - - /* Chain up to parent's key_press_event() method. */ - return GTK_WIDGET_CLASS (parent_class)-> - key_press_event (widget, event); -} - -static void -mail_search_bar_clear (EMailSearchBar *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); - - mail_search_bar_update_tokenizer (search_bar); - - g_object_notify (G_OBJECT (search_bar), "active-search"); -} - -static void -mail_search_bar_class_init (EMailSearchBarClass *class) -{ - GObjectClass *object_class; - GtkWidgetClass *widget_class; - - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMailSearchBarPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->set_property = mail_search_bar_set_property; - object_class->get_property = mail_search_bar_get_property; - object_class->dispose = mail_search_bar_dispose; - object_class->finalize = mail_search_bar_finalize; - object_class->constructed = mail_search_bar_constructed; - - widget_class = GTK_WIDGET_CLASS (class); - widget_class->show = mail_search_bar_show; - widget_class->hide = mail_search_bar_hide; - widget_class->key_press_event = mail_search_bar_key_press_event; - - class->clear = mail_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 (EMailSearchBarClass, 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 (EMailSearchBarClass, clear), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); -} - -static void -mail_search_bar_init (EMailSearchBar *search_bar) -{ - GtkWidget *label; - GtkWidget *widget; - GtkWidget *container; - - search_bar->priv = E_MAIL_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 (mail_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 (mail_search_bar_find_next_cb), search_bar); - - g_signal_connect_swapped ( - widget, "changed", - G_CALLBACK (mail_search_bar_changed_cb), search_bar); - - g_signal_connect_swapped ( - widget, "icon-release", - G_CALLBACK (mail_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 (mail_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 (mail_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 (mail_search_bar_toggled_cb), search_bar); - - g_signal_connect_swapped ( - widget, "toggled", - G_CALLBACK (mail_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_mail_search_bar_get_type (void) -{ - static GType type = 0; - - if (G_UNLIKELY (type == 0)) { - static const GTypeInfo type_info = { - sizeof (EMailSearchBarClass), - (GBaseInitFunc) NULL, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) mail_search_bar_class_init, - (GClassFinalizeFunc) NULL, - NULL, /* class_data */ - sizeof (EMailSearchBar), - 0, /* n_preallocs */ - (GInstanceInitFunc) mail_search_bar_init, - NULL /* value_table */ - }; - - type = g_type_register_static ( - GTK_TYPE_HBOX, "EMailSearchBar", &type_info, 0); - } - - return type; -} - -GtkWidget * -e_mail_search_bar_new (EWebView *web_view) -{ - g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL); - - return g_object_new ( - E_TYPE_MAIL_SEARCH_BAR, "web-view", web_view, NULL); -} - -void -e_mail_search_bar_clear (EMailSearchBar *search_bar) -{ - g_return_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar)); - - g_signal_emit (search_bar, signals[CLEAR], 0); -} - -void -e_mail_search_bar_changed (EMailSearchBar *search_bar) -{ - g_return_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar)); - - g_signal_emit (search_bar, signals[CHANGED], 0); -} - -EWebView * -e_mail_search_bar_get_web_view (EMailSearchBar *search_bar) -{ - g_return_val_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar), NULL); - - return search_bar->priv->web_view; -} - -ESearchingTokenizer * -e_mail_search_bar_get_tokenizer (EMailSearchBar *search_bar) -{ - g_return_val_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar), NULL); - - return search_bar->priv->tokenizer; -} - -gboolean -e_mail_search_bar_get_active_search (EMailSearchBar *search_bar) -{ - g_return_val_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar), FALSE); - - return (search_bar->priv->active_search != NULL); -} - -gboolean -e_mail_search_bar_get_case_sensitive (EMailSearchBar *search_bar) -{ - GtkToggleButton *button; - - g_return_val_if_fail (E_IS_MAIL_SEARCH_BAR (search_bar), FALSE); - - button = GTK_TOGGLE_BUTTON (search_bar->priv->case_sensitive_button); - - return gtk_toggle_button_get_active (button); -} - -void -e_mail_search_bar_set_case_sensitive (EMailSearchBar *search_bar, - gboolean case_sensitive) -{ - GtkToggleButton *button; - - g_return_if_fail (E_IS_MAIL_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_mail_search_bar_get_text (EMailSearchBar *search_bar) -{ - GtkEntry *entry; - const gchar *text; - - g_return_val_if_fail (E_IS_MAIL_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_mail_search_bar_set_text (EMailSearchBar *search_bar, - const gchar *text) -{ - GtkEntry *entry; - - g_return_if_fail (E_IS_MAIL_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/mail/e-mail-search-bar.h b/mail/e-mail-search-bar.h deleted file mode 100644 index 69fa746687..0000000000 --- a/mail/e-mail-search-bar.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * e-mail-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 - * - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef E_MAIL_SEARCH_BAR_H -#define E_MAIL_SEARCH_BAR_H - -#include -#include -#include - -/* Standard GObject macros */ -#define E_TYPE_MAIL_SEARCH_BAR \ - (e_mail_search_bar_get_type ()) -#define E_MAIL_SEARCH_BAR(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), E_TYPE_MAIL_SEARCH_BAR, EMailSearchBar)) -#define E_MAIL_SEARCH_BAR_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), E_TYPE_MAIL_SEARCH_BAR, EMailSearchBarClass)) -#define E_IS_MAIL_SEARCH_BAR(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), E_TYPE_MAIL_SEARCH_BAR)) -#define E_IS_MAIL_SEARCH_BAR_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), E_TYPE_MAIL_SEARCH_BAR)) -#define E_MAIL_SEARCH_BAR_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), E_TYPE_MAIL_SEARCH_BAR, EMailSearchBarClass)) - -G_BEGIN_DECLS - -typedef struct _EMailSearchBar EMailSearchBar; -typedef struct _EMailSearchBarClass EMailSearchBarClass; -typedef struct _EMailSearchBarPrivate EMailSearchBarPrivate; - -struct _EMailSearchBar { - GtkHBox parent; - EMailSearchBarPrivate *priv; -}; - -struct _EMailSearchBarClass { - GtkHBoxClass parent_class; - - /* Signals */ - void (*changed) (EMailSearchBar *search_bar); - void (*clear) (EMailSearchBar *search_bar); -}; - -GType e_mail_search_bar_get_type (void); -GtkWidget * e_mail_search_bar_new (EWebView *web_view); -void e_mail_search_bar_clear (EMailSearchBar *search_bar); -void e_mail_search_bar_changed (EMailSearchBar *search_bar); -EWebView * e_mail_search_bar_get_web_view (EMailSearchBar *search_bar); -ESearchingTokenizer * - e_mail_search_bar_get_tokenizer (EMailSearchBar *search_bar); -gboolean e_mail_search_bar_get_active_search - (EMailSearchBar *search_bar); -gboolean e_mail_search_bar_get_case_sensitive - (EMailSearchBar *search_bar); -void e_mail_search_bar_set_case_sensitive - (EMailSearchBar *search_bar, - gboolean case_sensitive); -gchar * e_mail_search_bar_get_text (EMailSearchBar *search_bar); -void e_mail_search_bar_set_text (EMailSearchBar *search_bar, - const gchar *text); - -G_END_DECLS - -#endif /* E_MAIL_SEARCH_BAR_H */ diff --git a/mail/e-searching-tokenizer.c b/mail/e-searching-tokenizer.c deleted file mode 100644 index 8920a7dd21..0000000000 --- a/mail/e-searching-tokenizer.c +++ /dev/null @@ -1,1195 +0,0 @@ -/* - * 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 - * - * - * Authors: - * Developed by Jon Trowbridge - * Rewritten significantly to handle multiple strings and improve performance - * by Michael Zucchi - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#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;iroot; - 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;imatches; - 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 (ilast = 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", (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", (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;isubmatchp;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;istrv->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;istrv->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;istrv->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", TAG_ESCAPE, col); - tage = g_alloca(20); - sprintf(tage, "%c", 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/mail/e-searching-tokenizer.h b/mail/e-searching-tokenizer.h deleted file mode 100644 index 7eb603a25d..0000000000 --- a/mail/e-searching-tokenizer.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * - * 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 - * - * - * Authors: - * Jon Trowbridge - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef E_SEARCHING_TOKENIZER_H -#define E_SEARCHING_TOKENIZER_H - -#include -#include - -/* 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 */ diff --git a/modules/addressbook/e-book-shell-content.c b/modules/addressbook/e-book-shell-content.c index a7a616fc18..55b833269e 100644 --- a/modules/addressbook/e-book-shell-content.c +++ b/modules/addressbook/e-book-shell-content.c @@ -28,6 +28,7 @@ #include "e-util/gconf-bridge.h" #include "shell/e-shell-utils.h" #include "widgets/misc/e-paned.h" +#include "widgets/misc/e-preview-pane.h" #include "e-book-shell-view.h" #define E_BOOK_SHELL_CONTENT_GET_PRIVATE(obj) \ @@ -37,7 +38,7 @@ struct _EBookShellContentPrivate { GtkWidget *paned; GtkWidget *notebook; - GtkWidget *preview; + GtkWidget *preview_pane; GtkOrientation orientation; @@ -171,9 +172,9 @@ book_shell_content_dispose (GObject *object) priv->notebook = NULL; } - if (priv->preview != NULL) { - g_object_unref (priv->preview); - priv->preview = NULL; + if (priv->preview_pane != NULL) { + g_object_unref (priv->preview_pane); + priv->preview_pane = NULL; } /* Chain up to parent's dispose() method. */ @@ -221,32 +222,24 @@ book_shell_content_constructed (GObject *object) priv->notebook = g_object_ref (widget); gtk_widget_show (widget); - 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_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); - gtk_widget_show (widget); - - e_binding_new (object, "preview-visible", widget, "visible"); - - container = widget; - widget = eab_contact_display_new (); eab_contact_display_set_mode ( EAB_CONTACT_DISPLAY (widget), EAB_CONTACT_DISPLAY_RENDER_NORMAL); e_shell_configure_web_view (shell, E_WEB_VIEW (widget)); - gtk_container_add (GTK_CONTAINER (container), widget); - priv->preview = g_object_ref (widget); gtk_widget_show (widget); g_signal_connect_swapped ( widget, "send-message", G_CALLBACK (book_shell_content_send_message_cb), object); + widget = e_preview_pane_new (E_WEB_VIEW (widget)); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); + + e_binding_new (object, "preview-visible", widget, "visible"); + /* Bind GObject properties to GConf keys. */ bridge = gconf_bridge_get (); @@ -569,24 +562,19 @@ e_book_shell_content_set_current_view (EBookShellContent *book_shell_content, g_object_notify (G_OBJECT (book_shell_content), "current-view"); } -EABContactDisplay * -e_book_shell_content_get_preview (EBookShellContent *book_shell_content) -{ - g_return_val_if_fail ( - E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL); - - return EAB_CONTACT_DISPLAY (book_shell_content->priv->preview); -} - EContact * e_book_shell_content_get_preview_contact (EBookShellContent *book_shell_content) { + EPreviewPane *preview_pane; EABContactDisplay *display; + EWebView *web_view; g_return_val_if_fail ( E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL); - display = EAB_CONTACT_DISPLAY (book_shell_content->priv->preview); + preview_pane = E_PREVIEW_PANE (book_shell_content->priv->preview_pane); + web_view = e_preview_pane_get_web_view (preview_pane); + display = EAB_CONTACT_DISPLAY (web_view); return eab_contact_display_get_contact (display); } @@ -595,16 +583,29 @@ void e_book_shell_content_set_preview_contact (EBookShellContent *book_shell_content, EContact *preview_contact) { + EPreviewPane *preview_pane; EABContactDisplay *display; + EWebView *web_view; g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content)); - display = EAB_CONTACT_DISPLAY (book_shell_content->priv->preview); + preview_pane = E_PREVIEW_PANE (book_shell_content->priv->preview_pane); + web_view = e_preview_pane_get_web_view (preview_pane); + display = EAB_CONTACT_DISPLAY (web_view); eab_contact_display_set_contact (display, preview_contact); g_object_notify (G_OBJECT (book_shell_content), "preview-contact"); } +EPreviewPane * +e_book_shell_content_get_preview_pane (EBookShellContent *book_shell_content) +{ + g_return_val_if_fail ( + E_IS_BOOK_SHELL_CONTENT (book_shell_content), NULL); + + return E_PREVIEW_PANE (book_shell_content->priv->preview_pane); +} + gboolean e_book_shell_content_get_preview_visible (EBookShellContent *book_shell_content) { diff --git a/modules/addressbook/e-book-shell-content.h b/modules/addressbook/e-book-shell-content.h index 6ed5b38a62..595e366736 100644 --- a/modules/addressbook/e-book-shell-content.h +++ b/modules/addressbook/e-book-shell-content.h @@ -28,6 +28,8 @@ #include #include +#include + #include "addressbook/gui/widgets/e-addressbook-view.h" #include "eab-composer-util.h" @@ -90,14 +92,13 @@ EAddressbookView * void e_book_shell_content_set_current_view (EBookShellContent *book_shell_content, EAddressbookView *addressbook_view); -EABContactDisplay * - e_book_shell_content_get_preview - (EBookShellContent *book_shell_content); EContact * e_book_shell_content_get_preview_contact (EBookShellContent *book_shell_content); void e_book_shell_content_set_preview_contact (EBookShellContent *book_shell_content, EContact *preview_contact); +EPreviewPane * e_book_shell_content_get_preview_pane + (EBookShellContent *book_shell_content); gboolean e_book_shell_content_get_preview_visible (EBookShellContent *book_shell_content); void e_book_shell_content_set_preview_visible diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c index e791f8e08d..32b227c723 100644 --- a/modules/addressbook/e-book-shell-view-actions.c +++ b/modules/addressbook/e-book-shell-view-actions.c @@ -340,6 +340,19 @@ action_contact_delete_cb (GtkAction *action, e_selectable_delete_selection (E_SELECTABLE (view)); } +static void +action_contact_find_cb (GtkAction *action, + EBookShellView *book_shell_view) +{ + EBookShellContent *book_shell_content; + EPreviewPane *preview_pane; + + book_shell_content = book_shell_view->priv->book_shell_content; + preview_pane = e_book_shell_content_get_preview_pane (book_shell_content); + + e_preview_pane_show_search_bar (preview_pane); +} + static void action_contact_forward_cb (GtkAction *action, EBookShellView *book_shell_view) @@ -722,6 +735,13 @@ static GtkActionEntry contact_entries[] = { N_("Delete selected contacts"), G_CALLBACK (action_contact_delete_cb) }, + { "contact-find", + GTK_STOCK_FIND, + N_("_Find in Contact..."), + "f", + N_("Search for text in the displayed contact"), + G_CALLBACK (action_contact_find_cb) }, + { "contact-forward", "mail-forward", N_("_Forward Contact..."), @@ -961,7 +981,8 @@ e_book_shell_view_actions_init (EBookShellView *book_shell_view) EShellView *shell_view; EShellWindow *shell_window; EShellSearchbar *searchbar; - EABContactDisplay *contact_preview; + EPreviewPane *preview_pane; + EWebView *web_view; GtkActionGroup *action_group; GConfBridge *bridge; GtkAction *action; @@ -973,7 +994,8 @@ e_book_shell_view_actions_init (EBookShellView *book_shell_view) book_shell_content = book_shell_view->priv->book_shell_content; searchbar = e_book_shell_content_get_searchbar (book_shell_content); - contact_preview = e_book_shell_content_get_preview (book_shell_content); + preview_pane = e_book_shell_content_get_preview_pane (book_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); /* Contact Actions */ action_group = ACTION_GROUP (CONTACTS); @@ -1050,14 +1072,9 @@ e_book_shell_view_actions_init (EBookShellView *book_shell_view) ACTION (CONTACT_PREVIEW), "active", ACTION (CONTACT_VIEW_VERTICAL), "sensitive"); - e_web_view_set_open_proxy ( - E_WEB_VIEW (contact_preview), ACTION (CONTACT_OPEN)); - - e_web_view_set_print_proxy ( - E_WEB_VIEW (contact_preview), ACTION (CONTACT_PRINT)); - - e_web_view_set_save_as_proxy ( - E_WEB_VIEW (contact_preview), ACTION (CONTACT_SAVE_AS)); + e_web_view_set_open_proxy (web_view, ACTION (CONTACT_OPEN)); + e_web_view_set_print_proxy (web_view, ACTION (CONTACT_PRINT)); + e_web_view_set_save_as_proxy (web_view, ACTION (CONTACT_SAVE_AS)); } void diff --git a/modules/addressbook/e-book-shell-view-actions.h b/modules/addressbook/e-book-shell-view-actions.h index 250ec5fa99..ef40dd169f 100644 --- a/modules/addressbook/e-book-shell-view-actions.h +++ b/modules/addressbook/e-book-shell-view-actions.h @@ -49,6 +49,8 @@ E_SHELL_WINDOW_ACTION ((window), "contact-copy") #define E_SHELL_WINDOW_ACTION_CONTACT_DELETE(window) \ E_SHELL_WINDOW_ACTION ((window), "contact-delete") +#define E_SHELL_WINDOW_ACTION_CONTACT_FIND(window) \ + E_SHELL_WINDOW_ACTION ((window), "contact-find") #define E_SHELL_WINDOW_ACTION_CONTACT_FORWARD(window) \ E_SHELL_WINDOW_ACTION ((window), "contact-forward") #define E_SHELL_WINDOW_ACTION_CONTACT_MOVE(window) \ diff --git a/modules/addressbook/e-book-shell-view.c b/modules/addressbook/e-book-shell-view.c index 555ab13abf..3ecea2c49f 100644 --- a/modules/addressbook/e-book-shell-view.c +++ b/modules/addressbook/e-book-shell-view.c @@ -327,6 +327,10 @@ book_shell_view_update_actions (EShellView *shell_view) sensitive = source_is_editable && any_contacts_selected; gtk_action_set_sensitive (action, sensitive); + action = ACTION (CONTACT_FIND); + sensitive = single_contact_selected; + gtk_action_set_sensitive (action, sensitive); + action = ACTION (CONTACT_FORWARD); sensitive = any_contacts_selected; gtk_action_set_sensitive (action, sensitive); diff --git a/modules/calendar/e-memo-shell-content.c b/modules/calendar/e-memo-shell-content.c index 852c0bbf42..e34af1fbf8 100644 --- a/modules/calendar/e-memo-shell-content.c +++ b/modules/calendar/e-memo-shell-content.c @@ -29,8 +29,10 @@ #include "shell/e-shell-utils.h" #include "widgets/menus/gal-view-etable.h" #include "widgets/misc/e-paned.h" +#include "widgets/misc/e-preview-pane.h" #include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" #include "calendar/gui/e-cal-model-memos.h" #include "calendar/gui/e-memo-table.h" @@ -50,7 +52,7 @@ struct _EMemoShellContentPrivate { GtkWidget *paned; GtkWidget *memo_table; - GtkWidget *memo_preview; + GtkWidget *preview_pane; ECalModel *memo_model; GalViewInstance *view_instance; @@ -192,10 +194,15 @@ memo_shell_content_cursor_change_cb (EMemoShellContent *memo_shell_content, ECalModel *memo_model; ECalModelComponent *comp_data; ECalComponent *comp; + EPreviewPane *preview_pane; + EWebView *web_view; const gchar *uid; memo_model = e_memo_shell_content_get_memo_model (memo_shell_content); - memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content); + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); if (e_table_selected_count (table) != 1) { e_cal_component_preview_clear (memo_preview); @@ -223,8 +230,13 @@ memo_shell_content_selection_change_cb (EMemoShellContent *memo_shell_content, ETable *table) { ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; + EWebView *web_view; + + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); - memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); /* XXX Old code emits a "selection-changed" signal here. */ @@ -347,9 +359,9 @@ memo_shell_content_dispose (GObject *object) priv->memo_table = NULL; } - if (priv->memo_preview != NULL) { - g_object_unref (priv->memo_preview); - priv->memo_preview = NULL; + if (priv->preview_pane != NULL) { + g_object_unref (priv->preview_pane); + priv->preview_pane = NULL; } if (priv->memo_model != NULL) { @@ -445,25 +457,10 @@ memo_shell_content_constructed (GObject *object) container = priv->paned; - 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_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); - gtk_widget_show (widget); - - e_binding_new (object, "preview-visible", widget, "visible"); - - container = widget; - widget = e_cal_component_preview_new (); e_cal_component_preview_set_default_timezone ( E_CAL_COMPONENT_PREVIEW (widget), timezone); e_shell_configure_web_view (shell, E_WEB_VIEW (widget)); - gtk_container_add (GTK_CONTAINER (container), widget); - priv->memo_preview = g_object_ref (widget); gtk_widget_show (widget); g_signal_connect_swapped ( @@ -471,6 +468,13 @@ memo_shell_content_constructed (GObject *object) G_CALLBACK (e_shell_taskbar_set_message), shell_taskbar); + widget = e_preview_pane_new (E_WEB_VIEW (widget)); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); + + e_binding_new (object, "preview-visible", widget, "visible"); + /* Configure the memo table. */ e_table_set_state ( @@ -681,23 +685,22 @@ e_memo_shell_content_get_memo_model (EMemoShellContent *memo_shell_content) return memo_shell_content->priv->memo_model; } -ECalComponentPreview * -e_memo_shell_content_get_memo_preview (EMemoShellContent *memo_shell_content) +EMemoTable * +e_memo_shell_content_get_memo_table (EMemoShellContent *memo_shell_content) { g_return_val_if_fail ( E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); - return E_CAL_COMPONENT_PREVIEW ( - memo_shell_content->priv->memo_preview); + return E_MEMO_TABLE (memo_shell_content->priv->memo_table); } -EMemoTable * -e_memo_shell_content_get_memo_table (EMemoShellContent *memo_shell_content) +EPreviewPane * +e_memo_shell_content_get_preview_pane (EMemoShellContent *memo_shell_content) { g_return_val_if_fail ( E_IS_MEMO_SHELL_CONTENT (memo_shell_content), NULL); - return E_MEMO_TABLE (memo_shell_content->priv->memo_table); + return E_PREVIEW_PANE (memo_shell_content->priv->preview_pane); } gboolean diff --git a/modules/calendar/e-memo-shell-content.h b/modules/calendar/e-memo-shell-content.h index 16aae229b2..4567a94aa9 100644 --- a/modules/calendar/e-memo-shell-content.h +++ b/modules/calendar/e-memo-shell-content.h @@ -27,9 +27,9 @@ #include #include -#include #include +#include /* Standard GObject macros */ #define E_TYPE_MEMO_SHELL_CONTENT \ @@ -78,11 +78,10 @@ void e_memo_shell_content_register_type GtkWidget * e_memo_shell_content_new(EShellView *shell_view); ECalModel * e_memo_shell_content_get_memo_model (EMemoShellContent *memo_shell_conent); -ECalComponentPreview * - e_memo_shell_content_get_memo_preview - (EMemoShellContent *memo_shell_content); EMemoTable * e_memo_shell_content_get_memo_table (EMemoShellContent *memo_shell_content); +EPreviewPane * e_memo_shell_content_get_preview_pane + (EMemoShellContent *memo_shell_content); gboolean e_memo_shell_content_get_preview_visible (EMemoShellContent *memo_shell_content); void e_memo_shell_content_set_preview_visible diff --git a/modules/calendar/e-memo-shell-view-actions.c b/modules/calendar/e-memo-shell-view-actions.c index 1c0a7ef9ef..24aced8528 100644 --- a/modules/calendar/e-memo-shell-view-actions.c +++ b/modules/calendar/e-memo-shell-view-actions.c @@ -55,6 +55,19 @@ action_memo_delete_cb (GtkAction *action, e_selectable_delete_selection (E_SELECTABLE (memo_table)); } +static void +action_memo_find_cb (GtkAction *action, + EMemoShellView *memo_shell_view) +{ + EMemoShellContent *memo_shell_content; + EPreviewPane *preview_pane; + + memo_shell_content = memo_shell_view->priv->memo_shell_content; + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + + e_preview_pane_show_search_bar (preview_pane); +} + static void action_memo_forward_cb (GtkAction *action, EMemoShellView *memo_shell_view) @@ -564,6 +577,13 @@ static GtkActionEntry memo_entries[] = { N_("Delete selected memos"), G_CALLBACK (action_memo_delete_cb) }, + { "memo-find", + GTK_STOCK_FIND, + N_("_Find in Memo..."), + "f", + N_("Search for text in the displayed memo"), + G_CALLBACK (action_memo_find_cb) }, + { "memo-forward", "mail-forward", N_("_Forward as iCalendar..."), @@ -831,7 +851,8 @@ e_memo_shell_view_actions_init (EMemoShellView *memo_shell_view) EShellView *shell_view; EShellWindow *shell_window; EShellSearchbar *searchbar; - ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; + EWebView *web_view; GtkActionGroup *action_group; GConfBridge *bridge; GtkAction *action; @@ -843,7 +864,8 @@ e_memo_shell_view_actions_init (EMemoShellView *memo_shell_view) memo_shell_content = memo_shell_view->priv->memo_shell_content; searchbar = e_memo_shell_content_get_searchbar (memo_shell_content); - memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content); + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); /* Memo Actions */ action_group = ACTION_GROUP (MEMOS); @@ -920,14 +942,9 @@ e_memo_shell_view_actions_init (EMemoShellView *memo_shell_view) ACTION (MEMO_PREVIEW), "active", ACTION (MEMO_VIEW_VERTICAL), "sensitive"); - e_web_view_set_open_proxy ( - E_WEB_VIEW (memo_preview), ACTION (MEMO_OPEN)); - - e_web_view_set_print_proxy ( - E_WEB_VIEW (memo_preview), ACTION (MEMO_PRINT)); - - e_web_view_set_save_as_proxy ( - E_WEB_VIEW (memo_preview), ACTION (MEMO_SAVE_AS)); + e_web_view_set_open_proxy (web_view, ACTION (MEMO_OPEN)); + e_web_view_set_print_proxy (web_view, ACTION (MEMO_PRINT)); + e_web_view_set_save_as_proxy (web_view, ACTION (MEMO_SAVE_AS)); } void diff --git a/modules/calendar/e-memo-shell-view-actions.h b/modules/calendar/e-memo-shell-view-actions.h index 5b49ca06b1..c9d144b5d4 100644 --- a/modules/calendar/e-memo-shell-view-actions.h +++ b/modules/calendar/e-memo-shell-view-actions.h @@ -27,6 +27,8 @@ /* Memo Actions */ #define E_SHELL_WINDOW_ACTION_MEMO_DELETE(window) \ E_SHELL_WINDOW_ACTION ((window), "memo-delete") +#define E_SHELL_WINDOW_ACTION_MEMO_FIND(window) \ + E_SHELL_WINDOW_ACTION ((window), "memo-find") #define E_SHELL_WINDOW_ACTION_MEMO_FORWARD(window) \ E_SHELL_WINDOW_ACTION ((window), "memo-forward") #define E_SHELL_WINDOW_ACTION_MEMO_NEW(window) \ diff --git a/modules/calendar/e-memo-shell-view-private.c b/modules/calendar/e-memo-shell-view-private.c index 583af2af17..9e2be0fe7d 100644 --- a/modules/calendar/e-memo-shell-view-private.c +++ b/modules/calendar/e-memo-shell-view-private.c @@ -430,18 +430,23 @@ e_memo_shell_view_update_timezone (EMemoShellView *memo_shell_view) EMemoShellContent *memo_shell_content; EMemoShellSidebar *memo_shell_sidebar; ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; + EWebView *web_view; icaltimezone *timezone; ECalModel *model; GList *clients, *iter; memo_shell_content = memo_shell_view->priv->memo_shell_content; - memo_preview = e_memo_shell_content_get_memo_preview (memo_shell_content); + preview_pane = e_memo_shell_content_get_preview_pane (memo_shell_content); model = e_memo_shell_content_get_memo_model (memo_shell_content); timezone = e_cal_model_get_timezone (model); memo_shell_sidebar = memo_shell_view->priv->memo_shell_sidebar; clients = e_memo_shell_sidebar_get_clients (memo_shell_sidebar); + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); + for (iter = clients; iter != NULL; iter = iter->next) { ECal *client = iter->data; diff --git a/modules/calendar/e-memo-shell-view.c b/modules/calendar/e-memo-shell-view.c index 07a58aa1c9..870d5aedc8 100644 --- a/modules/calendar/e-memo-shell-view.c +++ b/modules/calendar/e-memo-shell-view.c @@ -61,7 +61,9 @@ memo_shell_view_execute_search (EShellView *shell_view) EActionComboBox *combo_box; GtkRadioAction *action; ECalComponentPreview *memo_preview; + EPreviewPane *preview_pane; EMemoTable *memo_table; + EWebView *web_view; ECalModel *model; gchar *query; gchar *temp; @@ -155,8 +157,11 @@ memo_shell_view_execute_search (EShellView *shell_view) e_cal_model_set_search_query (model, query); g_free (query); - memo_preview = - e_memo_shell_content_get_memo_preview (memo_shell_content); + preview_pane = + e_memo_shell_content_get_preview_pane (memo_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + memo_preview = E_CAL_COMPONENT_PREVIEW (web_view); e_cal_component_preview_clear (memo_preview); } @@ -220,6 +225,10 @@ memo_shell_view_update_actions (EShellView *shell_view) label = _("Delete Memo"); g_object_set (action, "label", label, NULL); + action = ACTION (MEMO_FIND); + sensitive = single_memo_selected; + gtk_action_set_sensitive (action, sensitive); + action = ACTION (MEMO_FORWARD); sensitive = single_memo_selected; gtk_action_set_sensitive (action, sensitive); diff --git a/modules/calendar/e-task-shell-content.c b/modules/calendar/e-task-shell-content.c index 11bc28af62..fd76fe7513 100644 --- a/modules/calendar/e-task-shell-content.c +++ b/modules/calendar/e-task-shell-content.c @@ -29,8 +29,10 @@ #include "shell/e-shell-utils.h" #include "widgets/menus/gal-view-etable.h" #include "widgets/misc/e-paned.h" +#include "widgets/misc/e-preview-pane.h" #include "calendar/gui/comp-util.h" +#include "calendar/gui/e-cal-component-preview.h" #include "calendar/gui/e-cal-model-tasks.h" #define E_TASK_SHELL_CONTENT_GET_PRIVATE(obj) \ @@ -50,7 +52,7 @@ struct _ETaskShellContentPrivate { GtkWidget *paned; GtkWidget *task_table; - GtkWidget *task_preview; + GtkWidget *preview_pane; ECalModel *task_model; GalViewInstance *view_instance; @@ -192,10 +194,15 @@ task_shell_content_cursor_change_cb (ETaskShellContent *task_shell_content, ECalModel *task_model; ECalModelComponent *comp_data; ECalComponent *comp; + EPreviewPane *preview_pane; + EWebView *web_view; const gchar *uid; task_model = e_task_shell_content_get_task_model (task_shell_content); - task_preview = e_task_shell_content_get_task_preview (task_shell_content); + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); if (e_table_selected_count (table) != 1) { e_cal_component_preview_clear (task_preview); @@ -223,8 +230,13 @@ task_shell_content_selection_change_cb (ETaskShellContent *task_shell_content, ETable *table) { ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; + EWebView *web_view; + + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); - task_preview = e_task_shell_content_get_task_preview (task_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); if (e_table_selected_count (table) != 1) e_cal_component_preview_clear (task_preview); @@ -345,9 +357,9 @@ task_shell_content_dispose (GObject *object) priv->task_table = NULL; } - if (priv->task_preview != NULL) { - g_object_unref (priv->task_preview); - priv->task_preview = NULL; + if (priv->preview_pane != NULL) { + g_object_unref (priv->preview_pane); + priv->preview_pane = NULL; } if (priv->task_model != NULL) { @@ -442,25 +454,10 @@ task_shell_content_constructed (GObject *object) container = priv->paned; - 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_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); - gtk_widget_show (widget); - - e_binding_new (object, "preview-visible", widget, "visible"); - - container = widget; - widget = e_cal_component_preview_new (); e_cal_component_preview_set_default_timezone ( E_CAL_COMPONENT_PREVIEW (widget), timezone); e_shell_configure_web_view (shell, E_WEB_VIEW (widget)); - gtk_container_add (GTK_CONTAINER (container), widget); - priv->task_preview = g_object_ref (widget); gtk_widget_show (widget); g_signal_connect_swapped ( @@ -468,6 +465,13 @@ task_shell_content_constructed (GObject *object) G_CALLBACK (e_shell_taskbar_set_message), shell_taskbar); + widget = e_preview_pane_new (E_WEB_VIEW (widget)); + gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); + priv->preview_pane = g_object_ref (widget); + gtk_widget_show (widget); + + e_binding_new (object, "preview-visible", widget, "visible"); + /* Configure the task table. */ e_table_set_state ( @@ -703,23 +707,22 @@ e_task_shell_content_get_task_model (ETaskShellContent *task_shell_content) return task_shell_content->priv->task_model; } -ECalComponentPreview * -e_task_shell_content_get_task_preview (ETaskShellContent *task_shell_content) +ETaskTable * +e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content) { g_return_val_if_fail ( E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); - return E_CAL_COMPONENT_PREVIEW ( - task_shell_content->priv->task_preview); + return E_TASK_TABLE (task_shell_content->priv->task_table); } -ETaskTable * -e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content) +EPreviewPane * +e_task_shell_content_get_preview_pane (ETaskShellContent *task_shell_content) { g_return_val_if_fail ( E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL); - return E_TASK_TABLE (task_shell_content->priv->task_table); + return E_PREVIEW_PANE (task_shell_content->priv->preview_pane); } gboolean diff --git a/modules/calendar/e-task-shell-content.h b/modules/calendar/e-task-shell-content.h index dd39f8c6ce..d4e67d1b95 100644 --- a/modules/calendar/e-task-shell-content.h +++ b/modules/calendar/e-task-shell-content.h @@ -27,10 +27,10 @@ #include #include -#include #include #include +#include /* Standard GObject macros */ #define E_TYPE_TASK_SHELL_CONTENT \ @@ -82,11 +82,10 @@ void e_task_shell_content_register_type GtkWidget * e_task_shell_content_new(EShellView *shell_view); ECalModel * e_task_shell_content_get_task_model (ETaskShellContent *task_shell_content); -ECalComponentPreview * - e_task_shell_content_get_task_preview - (ETaskShellContent *task_shell_content); ETaskTable * e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content); +EPreviewPane * e_task_shell_content_get_preview_pane + (ETaskShellContent *task_shell_content); gboolean e_task_shell_content_get_preview_visible (ETaskShellContent *task_shell_content); void e_task_shell_content_set_preview_visible diff --git a/modules/calendar/e-task-shell-view-actions.c b/modules/calendar/e-task-shell-view-actions.c index bd3d0627ff..c18882dd2b 100644 --- a/modules/calendar/e-task-shell-view-actions.c +++ b/modules/calendar/e-task-shell-view-actions.c @@ -86,6 +86,19 @@ action_task_delete_cb (GtkAction *action, e_selectable_delete_selection (E_SELECTABLE (task_table)); } +static void +action_task_find_cb (GtkAction *action, + ETaskShellView *task_shell_view) +{ + ETaskShellContent *task_shell_content; + EPreviewPane *preview_pane; + + task_shell_content = task_shell_view->priv->task_shell_content; + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + + e_preview_pane_show_search_bar (preview_pane); +} + static void action_task_forward_cb (GtkAction *action, ETaskShellView *task_shell_view) @@ -688,6 +701,13 @@ static GtkActionEntry task_entries[] = { N_("Delete selected tasks"), G_CALLBACK (action_task_delete_cb) }, + { "task-find", + GTK_STOCK_FIND, + N_("_Find in Task..."), + "f", + N_("Search for text in the displayed task"), + G_CALLBACK (action_task_find_cb) }, + { "task-forward", "mail-forward", N_("_Forward as iCalendar..."), @@ -1030,7 +1050,8 @@ e_task_shell_view_actions_init (ETaskShellView *task_shell_view) EShellView *shell_view; EShellWindow *shell_window; EShellSearchbar *searchbar; - ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; + EWebView *web_view; GtkActionGroup *action_group; GConfBridge *bridge; GtkAction *action; @@ -1042,7 +1063,8 @@ e_task_shell_view_actions_init (ETaskShellView *task_shell_view) task_shell_content = task_shell_view->priv->task_shell_content; searchbar = e_task_shell_content_get_searchbar (task_shell_content); - task_preview = e_task_shell_content_get_task_preview (task_shell_content); + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); + web_view = e_preview_pane_get_web_view (preview_pane); /* Task Actions */ action_group = ACTION_GROUP (TASKS); @@ -1119,14 +1141,9 @@ e_task_shell_view_actions_init (ETaskShellView *task_shell_view) ACTION (TASK_PREVIEW), "active", ACTION (TASK_VIEW_VERTICAL), "sensitive"); - e_web_view_set_open_proxy ( - E_WEB_VIEW (task_preview), ACTION (TASK_OPEN)); - - e_web_view_set_print_proxy ( - E_WEB_VIEW (task_preview), ACTION (TASK_PRINT)); - - e_web_view_set_save_as_proxy ( - E_WEB_VIEW (task_preview), ACTION (TASK_SAVE_AS)); + e_web_view_set_open_proxy (web_view, ACTION (TASK_OPEN)); + e_web_view_set_print_proxy (web_view, ACTION (TASK_PRINT)); + e_web_view_set_save_as_proxy (web_view, ACTION (TASK_SAVE_AS)); } void diff --git a/modules/calendar/e-task-shell-view-actions.h b/modules/calendar/e-task-shell-view-actions.h index f323fb38f9..afa9fe3e1b 100644 --- a/modules/calendar/e-task-shell-view-actions.h +++ b/modules/calendar/e-task-shell-view-actions.h @@ -29,6 +29,8 @@ E_SHELL_WINDOW_ACTION ((window), "task-assign") #define E_SHELL_WINDOW_ACTION_TASK_DELETE(window) \ E_SHELL_WINDOW_ACTION ((window), "task-delete") +#define E_SHELL_WINDOW_ACTION_TASK_FIND(window) \ + E_SHELL_WINDOW_ACTION ((window), "task-find") #define E_SHELL_WINDOW_ACTION_TASK_FORWARD(window) \ E_SHELL_WINDOW_ACTION ((window), "task-forward") #define E_SHELL_WINDOW_ACTION_TASK_MARK_COMPLETE(window) \ diff --git a/modules/calendar/e-task-shell-view-private.c b/modules/calendar/e-task-shell-view-private.c index 6704a3b4c0..4b04185d0d 100644 --- a/modules/calendar/e-task-shell-view-private.c +++ b/modules/calendar/e-task-shell-view-private.c @@ -580,18 +580,23 @@ e_task_shell_view_update_timezone (ETaskShellView *task_shell_view) ETaskShellContent *task_shell_content; ETaskShellSidebar *task_shell_sidebar; ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; + EWebView *web_view; icaltimezone *timezone; ECalModel *model; GList *clients, *iter; task_shell_content = task_shell_view->priv->task_shell_content; - task_preview = e_task_shell_content_get_task_preview (task_shell_content); + preview_pane = e_task_shell_content_get_preview_pane (task_shell_content); model = e_task_shell_content_get_task_model (task_shell_content); timezone = e_cal_model_get_timezone (model); task_shell_sidebar = task_shell_view->priv->task_shell_sidebar; clients = e_task_shell_sidebar_get_clients (task_shell_sidebar); + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); + for (iter = clients; iter != NULL; iter = iter->next) { ECal *client = iter->data; diff --git a/modules/calendar/e-task-shell-view.c b/modules/calendar/e-task-shell-view.c index 36b9db5b56..b2146f5e59 100644 --- a/modules/calendar/e-task-shell-view.c +++ b/modules/calendar/e-task-shell-view.c @@ -100,7 +100,9 @@ task_shell_view_execute_search (EShellView *shell_view) EActionComboBox *combo_box; GtkRadioAction *action; ECalComponentPreview *task_preview; + EPreviewPane *preview_pane; ETaskTable *task_table; + EWebView *web_view; ECalModel *model; time_t start_range; time_t end_range; @@ -268,8 +270,11 @@ task_shell_view_execute_search (EShellView *shell_view) e_cal_model_set_search_query (model, query); g_free (query); - task_preview = - e_task_shell_content_get_task_preview (task_shell_content); + preview_pane = + e_task_shell_content_get_preview_pane (task_shell_content); + + web_view = e_preview_pane_get_web_view (preview_pane); + task_preview = E_CAL_COMPONENT_PREVIEW (web_view); e_cal_component_preview_clear (task_preview); } @@ -348,6 +353,10 @@ task_shell_view_update_actions (EShellView *shell_view) label = _("Delete Task"); g_object_set (action, "label", label, NULL); + action = ACTION (TASK_FIND); + sensitive = single_task_selected; + gtk_action_set_sensitive (action, sensitive); + action = ACTION (TASK_FORWARD); sensitive = single_task_selected; gtk_action_set_sensitive (action, sensitive); diff --git a/modules/mail/e-mail-shell-content.c b/modules/mail/e-mail-shell-content.c index 5ba85b27b7..423f3ec0c2 100644 --- a/modules/mail/e-mail-shell-content.c +++ b/modules/mail/e-mail-shell-content.c @@ -30,6 +30,8 @@ #include "widgets/menus/gal-view-etable.h" #include "widgets/menus/gal-view-instance.h" #include "widgets/misc/e-paned.h" +#include "widgets/misc/e-preview-pane.h" +#include "widgets/misc/e-search-bar.h" #include "em-utils.h" #include "mail-config.h" @@ -37,7 +39,6 @@ #include "message-list.h" #include "e-mail-reader.h" -#include "e-mail-search-bar.h" #include "e-mail-shell-backend.h" #include "e-mail-shell-view-actions.h" @@ -361,6 +362,7 @@ mail_shell_content_constructed (GObject *object) EShellContent *shell_content; EShellBackend *shell_backend; EShellView *shell_view; + ESearchBar *search_bar; EMailReader *reader; GtkWidget *message_list; GConfBridge *bridge; @@ -390,9 +392,7 @@ mail_shell_content_constructed (GObject *object) priv->paned = g_object_ref (widget); gtk_widget_show (widget); - e_binding_new ( - object, "orientation", - widget, "orientation"); + e_binding_new (object, "orientation", widget, "orientation"); container = priv->paned; @@ -415,34 +415,19 @@ mail_shell_content_constructed (GObject *object) container = priv->paned; - widget = gtk_vbox_new (FALSE, 1); + gtk_widget_show (GTK_WIDGET (web_view)); + + widget = e_preview_pane_new (web_view); gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE); gtk_widget_show (widget); - e_binding_new ( - object, "preview-visible", - widget, "visible"); - - container = widget; - - 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_container_add (GTK_CONTAINER (widget), GTK_WIDGET (web_view)); - gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0); - gtk_widget_show (GTK_WIDGET (web_view)); - gtk_widget_show (widget); + e_binding_new (object, "preview-visible", widget, "visible"); - widget = e_mail_search_bar_new (web_view); - gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0); - priv->search_bar = g_object_ref (widget); - gtk_widget_hide (widget); + search_bar = e_preview_pane_get_search_bar (E_PREVIEW_PANE (widget)); + priv->search_bar = g_object_ref (search_bar); g_signal_connect_swapped ( - widget, "changed", + search_bar, "changed", G_CALLBACK (em_format_redraw), priv->html_display); /* Load the view instance. */ @@ -928,13 +913,13 @@ void e_mail_shell_content_set_search_strings (EMailShellContent *mail_shell_content, GSList *search_strings) { - EMailSearchBar *search_bar; + ESearchBar *search_bar; ESearchingTokenizer *tokenizer; g_return_if_fail (E_IS_MAIL_SHELL_CONTENT (mail_shell_content)); - search_bar = E_MAIL_SEARCH_BAR (mail_shell_content->priv->search_bar); - tokenizer = e_mail_search_bar_get_tokenizer (search_bar); + search_bar = E_SEARCH_BAR (mail_shell_content->priv->search_bar); + tokenizer = e_search_bar_get_tokenizer (search_bar); e_searching_tokenizer_set_secondary_case_sensitivity (tokenizer, FALSE); e_searching_tokenizer_set_secondary_search_string (tokenizer, NULL); @@ -945,7 +930,7 @@ e_mail_shell_content_set_search_strings (EMailShellContent *mail_shell_content, search_strings = g_slist_next (search_strings); } - e_mail_search_bar_changed (search_bar); + e_search_bar_changed (search_bar); } void diff --git a/ui/evolution-contacts.ui b/ui/evolution-contacts.ui index 1dc66b44bc..11dc1f20d0 100644 --- a/ui/evolution-contacts.ui +++ b/ui/evolution-contacts.ui @@ -15,6 +15,8 @@ + + diff --git a/ui/evolution-memos.ui b/ui/evolution-memos.ui index c20ebc5334..dbe42c7714 100644 --- a/ui/evolution-memos.ui +++ b/ui/evolution-memos.ui @@ -13,6 +13,8 @@ + + diff --git a/ui/evolution-tasks.ui b/ui/evolution-tasks.ui index 4483c95a1e..62d9f023b7 100644 --- a/ui/evolution-tasks.ui +++ b/ui/evolution-tasks.ui @@ -14,6 +14,8 @@ + + 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-preview-pane.h" + +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_PREVIEW_PANE_H +#define E_PREVIEW_PANE_H + +#include +#include +#include + +/* 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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#include "e-search-bar.h" + +#include +#include +#include + +#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 + * + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_SEARCH_BAR_H +#define E_SEARCH_BAR_H + +#include +#include +#include + +/* 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 + * + * + * Authors: + * Developed by Jon Trowbridge + * Rewritten significantly to handle multiple strings and improve performance + * by Michael Zucchi + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#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;iroot; + 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;imatches; + 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 (ilast = 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", (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", (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;isubmatchp;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;istrv->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;istrv->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;istrv->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", TAG_ESCAPE, col); + tage = g_alloca(20); + sprintf(tage, "%c", 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 + * + * + * Authors: + * Jon Trowbridge + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef E_SEARCHING_TOKENIZER_H +#define E_SEARCHING_TOKENIZER_H + +#include +#include + +/* 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 */ -- cgit v1.2.3