2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2004 Hiroyuki Yamamoto & the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
33 #include "prefs_common.h"
34 #include "description_window.h"
36 #include "matcher_parser.h"
37 #include "quicksearch.h"
38 #include "folderview.h"
40 #include "prefs_matcher.h"
42 #include "statusbar.h"
46 GtkWidget *hbox_search;
47 GtkWidget *search_type;
48 GtkWidget *search_type_opt;
49 GtkWidget *search_string_entry;
50 GtkWidget *search_condition_expression;
51 GtkWidget *search_description;
52 GtkWidget *clear_search;
56 MatcherList *matcher_list;
58 QuickSearchExecuteCallback callback;
59 gpointer callback_data;
63 gboolean deferred_free;
64 FolderItem *root_folder_item;
67 guint press_timeout_id;
70 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
71 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
72 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
73 static gchar *expand_search_string(const gchar *str);
75 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
77 return quicksearch->is_fast;
80 static void prepare_matcher(QuickSearch *quicksearch)
82 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
84 if (search_string == NULL || search_string[0] == '\0') {
85 quicksearch_set_active(quicksearch, FALSE);
88 if (quicksearch->matcher_list != NULL) {
89 if (quicksearch->matching) {
90 quicksearch->deferred_free = TRUE;
93 quicksearch->deferred_free = FALSE;
94 matcherlist_free(quicksearch->matcher_list);
95 quicksearch->matcher_list = NULL;
98 if (search_string == NULL || search_string[0] == '\0') {
102 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
105 newstr = expand_search_string(search_string);
106 if (newstr && newstr[0] != '\0') {
107 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
110 quicksearch->matcher_list = NULL;
111 quicksearch_set_active(quicksearch, FALSE);
116 quicksearch->is_fast = TRUE;
117 g_free(quicksearch->search_string);
118 quicksearch->search_string = g_strdup(search_string);
121 quicksearch_set_active(quicksearch, TRUE);
124 static void update_extended_buttons (QuickSearch *quicksearch)
126 GtkWidget *expr_btn = quicksearch->search_condition_expression;
127 GtkWidget *ext_btn = quicksearch->search_description;
129 g_return_if_fail(expr_btn != NULL);
130 g_return_if_fail(ext_btn != NULL);
132 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
133 gtk_widget_show(expr_btn);
134 gtk_widget_show(ext_btn);
136 gtk_widget_hide(expr_btn);
137 gtk_widget_hide(ext_btn);
141 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
144 qs->has_focus = TRUE;
148 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
151 qs->has_focus = FALSE;
152 qs->in_typing = FALSE;
156 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
158 return quicksearch->has_focus;
161 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
163 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
166 if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) {
167 prefs_common.summary_quicksearch_history =
168 add_history(prefs_common.summary_quicksearch_history,
170 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry),
171 prefs_common.summary_quicksearch_history);
174 prepare_matcher(quicksearch);
175 if (run_only_if_fast && !quicksearch->is_fast)
177 if (quicksearch->matcher_list == NULL &&
178 prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED &&
179 search_string && strlen(search_string) != 0)
181 quicksearch_set_running(quicksearch, TRUE);
182 if (quicksearch->callback != NULL)
183 quicksearch->callback(quicksearch, quicksearch->callback_data);
184 quicksearch_set_running(quicksearch, FALSE);
187 static int searchbar_changed_timeout(void *data)
189 QuickSearch *qs = (QuickSearch *)data;
190 if (qs && prefs_common.summary_quicksearch_dynamic) {
191 qs->in_typing = TRUE;
192 searchbar_run(qs, TRUE);
197 static gboolean searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
199 if (prefs_common.summary_quicksearch_dynamic) {
200 if (qs->press_timeout_id != -1) {
201 g_source_remove(qs->press_timeout_id);
203 qs->press_timeout_id = g_timeout_add(500,
204 searchbar_changed_timeout, qs);
210 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
211 QuickSearch *quicksearch)
213 if (event != NULL && event->keyval == GDK_Escape) {
217 quicksearch->in_typing = FALSE;
219 str = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
220 g_return_val_if_fail(str != NULL, TRUE);
222 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
224 summaryview_activate_quicksearch(
225 mainwindow_get_mainwindow()->summaryview,
228 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
229 gtk_widget_grab_focus(
230 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
236 if (event != NULL && event->keyval == GDK_Return) {
237 quicksearch->in_typing = FALSE;
238 /* add expression to history list and exec quicksearch */
239 searchbar_run(quicksearch, FALSE);
241 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
248 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
250 QuickSearch *quicksearch = (QuickSearch *)data;
251 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
253 prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
254 G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
255 GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
257 /* Show extended search description button, only when Extended is selected */
258 update_extended_buttons(quicksearch);
260 if (!search_string || strlen(search_string) == 0) {
264 prepare_matcher(quicksearch);
266 quicksearch_set_running(quicksearch, TRUE);
267 if (quicksearch->callback != NULL)
268 quicksearch->callback(quicksearch, quicksearch->callback_data);
269 quicksearch_set_running(quicksearch, FALSE);
273 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
275 QuickSearch *quicksearch = (QuickSearch *)data;
276 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
277 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
279 prefs_common.summary_quicksearch_recurse = checked;
281 /* reselect the search type */
282 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
283 prefs_common.summary_quicksearch_type);
285 if (!search_string || strlen(search_string) == 0) {
289 prepare_matcher(quicksearch);
291 quicksearch_set_running(quicksearch, TRUE);
292 if (quicksearch->callback != NULL)
293 quicksearch->callback(quicksearch, quicksearch->callback_data);
294 quicksearch_set_running(quicksearch, FALSE);
298 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
300 QuickSearch *quicksearch = (QuickSearch *)data;
301 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
303 prefs_common.summary_quicksearch_sticky = checked;
305 /* reselect the search type */
306 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
307 prefs_common.summary_quicksearch_type);
312 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
314 QuickSearch *quicksearch = (QuickSearch *)data;
315 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
317 prefs_common.summary_quicksearch_dynamic = checked;
319 /* reselect the search type */
320 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
321 prefs_common.summary_quicksearch_type);
327 * Strings describing how to use Extended Search
329 * When adding new lines, remember to put 2 strings for each line
331 static gchar *search_descr_strings[] = {
332 "a", N_("all messages"),
333 "ag #", N_("messages whose age is greater than #"),
334 "al #", N_("messages whose age is less than #"),
335 "b S", N_("messages which contain S in the message body"),
336 "B S", N_("messages which contain S in the whole message"),
337 "c S", N_("messages carbon-copied to S"),
338 "C S", N_("message is either to: or cc: to S"),
339 "D", N_("deleted messages"), /** how I can filter deleted messages **/
340 "e S", N_("messages which contain S in the Sender field"),
341 "E S", N_("true if execute \"S\" succeeds"),
342 "f S", N_("messages originating from user S"),
343 "F", N_("forwarded messages"),
344 "h S", N_("messages which contain header S"),
345 "i S", N_("messages which contain S in Message-ID header"),
346 "I S", N_("messages which contain S in inreplyto header"),
347 "k #", N_("messages which are marked with color #"),
348 "L", N_("locked messages"),
349 "n S", N_("messages which are in newsgroup S"),
350 "N", N_("new messages"),
351 "O", N_("old messages"),
352 "p", N_("incomplete messages (not entirely downloaded)"),
353 "r", N_("messages which have been replied to"),
354 "R", N_("read messages"),
355 "s S", N_("messages which contain S in subject"),
356 "se #", N_("messages whose score is equal to #"),
357 "sg #", N_("messages whose score is greater than #"),
358 "sl #", N_("messages whose score is lower than #"),
359 "Se #", N_("messages whose size is equal to #"),
360 "Sg #", N_("messages whose size is greater than #"),
361 "Ss #", N_("messages whose size is smaller than #"),
362 "t S", N_("messages which have been sent to S"),
363 "T", N_("marked messages"),
364 "U", N_("unread messages"),
365 "x S", N_("messages which contain S in References header"),
366 "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
367 "y S", N_("messages which contain S in X-Label header"),
369 "&", N_("logical AND operator"),
370 "|", N_("logical OR operator"),
371 "! or ~", N_("logical NOT operator"),
372 "%", N_("case sensitive search"),
374 " ", N_("all filtering expressions are allowed"),
378 static DescriptionWindow search_descr = {
382 N_("Extended Search"),
383 N_("Extended Search allows the user to define criteria that messages must "
384 "have in order to match and be displayed in the message list.\n\n"
385 "The following symbols can be used:"),
389 static void search_description_cb(GtkWidget *widget)
391 description_window_create(&search_descr);
394 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
396 QuickSearch *quicksearch = (QuickSearch *)data;
398 if (!quicksearch->active)
401 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
406 static void search_condition_expr_done(MatcherList * matchers)
411 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
413 if (matchers == NULL)
416 str = matcherlist_to_string(matchers);
419 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
420 prefs_common.summary_quicksearch_type, str);
423 /* add expression to history list and exec quicksearch */
424 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
428 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
430 const gchar * cond_str;
431 MatcherList * matchers = NULL;
433 g_return_val_if_fail(
434 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
437 /* re-use it the current quicksearch value if it's a condition expression,
438 otherwise ignore it silently */
439 cond_str = gtk_entry_get_text(
440 GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
441 search_string_entry)->entry));
442 if (*cond_str != '\0') {
443 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
446 prefs_matcher_open(matchers, search_condition_expr_done);
448 if (matchers != NULL)
449 matcherlist_free(matchers);
454 QuickSearch *quicksearch_new()
456 QuickSearch *quicksearch;
458 GtkWidget *hbox_search;
459 GtkWidget *search_type_opt;
460 GtkWidget *search_type;
461 GtkWidget *search_string_entry;
462 GtkWidget *search_hbox;
463 GtkWidget *search_description;
464 GtkWidget *clear_search;
465 GtkWidget *search_condition_expression;
467 GtkTooltips *tips = gtk_tooltips_new();
469 quicksearch = g_new0(QuickSearch, 1);
472 hbox_search = gtk_hbox_new(FALSE, 0);
474 search_type_opt = gtk_option_menu_new();
475 gtk_widget_show(search_type_opt);
476 gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
478 search_type = gtk_menu_new();
479 MENUITEM_ADD (search_type, menuitem, _("Subject"), QUICK_SEARCH_SUBJECT);
480 g_signal_connect(G_OBJECT(menuitem), "activate",
481 G_CALLBACK(searchtype_changed),
483 MENUITEM_ADD (search_type, menuitem, _("From"), QUICK_SEARCH_FROM);
484 g_signal_connect(G_OBJECT(menuitem), "activate",
485 G_CALLBACK(searchtype_changed),
487 MENUITEM_ADD (search_type, menuitem, _("To"), QUICK_SEARCH_TO);
488 g_signal_connect(G_OBJECT(menuitem), "activate",
489 G_CALLBACK(searchtype_changed),
491 MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
492 g_signal_connect(G_OBJECT(menuitem), "activate",
493 G_CALLBACK(searchtype_changed),
496 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
498 menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
499 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
501 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
502 prefs_common.summary_quicksearch_recurse);
504 g_signal_connect(G_OBJECT(menuitem), "activate",
505 G_CALLBACK(searchtype_recursive_changed),
508 menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
509 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
511 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
512 prefs_common.summary_quicksearch_sticky);
514 g_signal_connect(G_OBJECT(menuitem), "activate",
515 G_CALLBACK(searchtype_sticky_changed),
518 menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
519 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
521 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
522 prefs_common.summary_quicksearch_dynamic);
524 g_signal_connect(G_OBJECT(menuitem), "activate",
525 G_CALLBACK(searchtype_dynamic_changed),
528 gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
530 gtk_option_menu_set_history(GTK_OPTION_MENU(search_type_opt), prefs_common.summary_quicksearch_type);
532 gtk_widget_show(search_type);
534 search_string_entry = gtk_combo_new();
535 gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
536 gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
537 gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
538 if (prefs_common.summary_quicksearch_history)
539 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
540 prefs_common.summary_quicksearch_history);
541 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
542 gtk_widget_show(search_string_entry);
544 search_hbox = gtk_hbox_new(FALSE, 5);
546 #if GTK_CHECK_VERSION(2, 8, 0)
547 clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
549 clear_search = gtk_button_new_with_label(_(" Clear "));
551 gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
553 g_signal_connect(G_OBJECT(clear_search), "clicked",
554 G_CALLBACK(clear_search_cb), quicksearch);
555 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
557 _("Clear the current search"), NULL);
558 gtk_widget_show(clear_search);
560 #if GTK_CHECK_VERSION(2, 8, 0)
561 search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
563 search_condition_expression = gtk_button_new_with_label(" ... ");
565 gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
567 g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
568 G_CALLBACK(search_condition_expr),
570 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
571 search_condition_expression,
572 _("Edit search criteria"), NULL);
573 gtk_widget_show(search_condition_expression);
575 #if GTK_CHECK_VERSION(2, 8, 0)
576 search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
578 search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
580 gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
582 g_signal_connect(G_OBJECT(search_description), "clicked",
583 G_CALLBACK(search_description_cb), NULL);
584 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
586 _("Information about extended symbols"), NULL);
587 gtk_widget_show(search_description);
589 gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
590 gtk_widget_show(search_hbox);
592 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
594 G_CALLBACK(searchbar_pressed),
597 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
599 G_CALLBACK(searchbar_changed_cb),
602 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
604 G_CALLBACK(searchbar_focus_evt_in),
606 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
608 G_CALLBACK(searchbar_focus_evt_out),
611 quicksearch->hbox_search = hbox_search;
612 quicksearch->search_type = search_type;
613 quicksearch->search_type_opt = search_type_opt;
614 quicksearch->search_string_entry = search_string_entry;
615 quicksearch->search_condition_expression = search_condition_expression;
616 quicksearch->search_description = search_description;
617 quicksearch->matcher_list = NULL;
618 quicksearch->active = FALSE;
619 quicksearch->running = FALSE;
620 quicksearch->clear_search = clear_search;
621 quicksearch->in_typing = FALSE;
622 quicksearch->press_timeout_id = -1;
624 update_extended_buttons(quicksearch);
629 void quicksearch_relayout(QuickSearch *quicksearch)
631 switch (prefs_common.layout_mode) {
634 case WIDE_MSGLIST_LAYOUT:
635 #if GTK_CHECK_VERSION(2, 8, 0)
636 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO);
637 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT);
638 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR);
640 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _(" Extended Symbols... "));
641 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), " ... ");
642 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _(" Clear "));
645 case VERTICAL_LAYOUT:
646 #if GTK_CHECK_VERSION(2, 8, 0)
647 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), "");
648 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "");
649 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), "");
651 gtk_button_set_image(GTK_BUTTON(quicksearch->search_description),
652 gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON));
653 gtk_button_set_image(GTK_BUTTON(quicksearch->search_condition_expression),
654 gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON));
655 gtk_button_set_image(GTK_BUTTON(quicksearch->clear_search),
656 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
658 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _("Info"));
659 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "...");
660 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _("Clear"));
666 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
668 return quicksearch->hbox_search;
671 void quicksearch_show(QuickSearch *quicksearch)
673 prepare_matcher(quicksearch);
674 gtk_widget_show(quicksearch->hbox_search);
675 update_extended_buttons(quicksearch);
676 gtk_widget_grab_focus(
677 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
680 void quicksearch_hide(QuickSearch *quicksearch)
682 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
683 quicksearch_set_active(quicksearch, FALSE);
684 gtk_widget_hide(quicksearch->hbox_search);
687 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
688 const gchar *matchstring)
690 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
693 if (!matchstring || !(*matchstring))
694 quicksearch->in_typing = FALSE;
696 g_signal_handlers_block_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
697 G_CALLBACK(searchbar_changed_cb), quicksearch);
698 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
700 g_signal_handlers_unblock_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
701 G_CALLBACK(searchbar_changed_cb), quicksearch);
703 prefs_common.summary_quicksearch_type = type;
705 prepare_matcher(quicksearch);
707 quicksearch_set_running(quicksearch, TRUE);
708 if (quicksearch->callback != NULL)
709 quicksearch->callback(quicksearch, quicksearch->callback_data);
710 quicksearch_set_running(quicksearch, FALSE);
713 gboolean quicksearch_is_active(QuickSearch *quicksearch)
715 return quicksearch->active &&
716 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
717 || quicksearch->matcher_list != NULL);
720 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
722 static GdkColor yellow;
724 static GdkColor black;
725 static gboolean colors_initialised = FALSE;
726 gboolean error = FALSE;
728 if (!colors_initialised) {
729 gdk_color_parse("#f5f6be", &yellow);
730 gdk_color_parse("#000000", &black);
731 gdk_color_parse("#ff7070", &red);
732 colors_initialised = gdk_colormap_alloc_color(
733 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
734 colors_initialised &= gdk_colormap_alloc_color(
735 gdk_colormap_get_system(), &black, FALSE, TRUE);
736 colors_initialised &= gdk_colormap_alloc_color(
737 gdk_colormap_get_system(), &red, FALSE, TRUE);
740 quicksearch->active = active;
743 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
744 && quicksearch->matcher_list == NULL))
748 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
749 if (colors_initialised) {
750 gtk_widget_modify_base(
751 GTK_COMBO(quicksearch->search_string_entry)->entry,
752 GTK_STATE_NORMAL, error ? &red : &yellow);
753 gtk_widget_modify_text(
754 GTK_COMBO(quicksearch->search_string_entry)->entry,
755 GTK_STATE_NORMAL, &black);
758 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
759 if (colors_initialised) {
760 gtk_widget_modify_base(
761 GTK_COMBO(quicksearch->search_string_entry)->entry,
762 GTK_STATE_NORMAL, NULL);
763 gtk_widget_modify_text(
764 GTK_COMBO(quicksearch->search_string_entry)->entry,
765 GTK_STATE_NORMAL, NULL);
770 quicksearch_reset_cur_folder_item(quicksearch);
774 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
775 QuickSearchExecuteCallback callback,
778 quicksearch->callback = callback;
779 quicksearch->callback_data = data;
782 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
784 gchar *searched_header = NULL;
785 gboolean result = FALSE;
787 if (!quicksearch->active)
790 switch (prefs_common.summary_quicksearch_type) {
791 case QUICK_SEARCH_SUBJECT:
792 searched_header = msginfo->subject;
794 case QUICK_SEARCH_FROM:
795 searched_header = msginfo->from;
797 case QUICK_SEARCH_TO:
798 searched_header = msginfo->to;
800 case QUICK_SEARCH_EXTENDED:
803 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
806 quicksearch->matching = TRUE;
807 if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
808 quicksearch->search_string &&
809 searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
811 else if ((quicksearch->matcher_list != NULL) &&
812 matcherlist_match(quicksearch->matcher_list, msginfo))
815 quicksearch->matching = FALSE;
816 if (quicksearch->deferred_free) {
817 prepare_matcher(quicksearch);
823 /* allow Mutt-like patterns in quick search */
824 static gchar *expand_search_string(const gchar *search_string)
827 gchar term_char, save_char;
828 gchar *cmd_start, *cmd_end;
830 gchar *returnstr = NULL;
832 gboolean casesens, dontmatch;
833 /* list of allowed pattern abbreviations */
835 gchar *abbreviated; /* abbreviation */
836 gchar *command; /* actual matcher command */
837 gint numparams; /* number of params for cmd */
838 gboolean qualifier; /* do we append regexpcase */
839 gboolean quotes; /* do we need quotes */
842 { "a", "all", 0, FALSE, FALSE },
843 { "ag", "age_greater", 1, FALSE, FALSE },
844 { "al", "age_lower", 1, FALSE, FALSE },
845 { "b", "body_part", 1, TRUE, TRUE },
846 { "B", "message", 1, TRUE, TRUE },
847 { "c", "cc", 1, TRUE, TRUE },
848 { "C", "to_or_cc", 1, TRUE, TRUE },
849 { "D", "deleted", 0, FALSE, FALSE },
850 { "e", "header \"Sender\"", 1, TRUE, TRUE },
851 { "E", "execute", 1, FALSE, TRUE },
852 { "f", "from", 1, TRUE, TRUE },
853 { "F", "forwarded", 0, FALSE, FALSE },
854 { "h", "headers_part", 1, TRUE, TRUE },
855 { "i", "header \"Message-ID\"", 1, TRUE, TRUE },
856 { "I", "inreplyto", 1, TRUE, TRUE },
857 { "k", "colorlabel", 1, FALSE, FALSE },
858 { "L", "locked", 0, FALSE, FALSE },
859 { "n", "newsgroups", 1, TRUE, TRUE },
860 { "N", "new", 0, FALSE, FALSE },
861 { "O", "~new", 0, FALSE, FALSE },
862 { "r", "replied", 0, FALSE, FALSE },
863 { "R", "~unread", 0, FALSE, FALSE },
864 { "s", "subject", 1, TRUE, TRUE },
865 { "se", "score_equal", 1, FALSE, FALSE },
866 { "sg", "score_greater", 1, FALSE, FALSE },
867 { "sl", "score_lower", 1, FALSE, FALSE },
868 { "Se", "size_equal", 1, FALSE, FALSE },
869 { "Sg", "size_greater", 1, FALSE, FALSE },
870 { "Ss", "size_smaller", 1, FALSE, FALSE },
871 { "t", "to", 1, TRUE, TRUE },
872 { "T", "marked", 0, FALSE, FALSE },
873 { "U", "unread", 0, FALSE, FALSE },
874 { "x", "header \"References\"", 1, TRUE, TRUE },
875 { "X", "test", 1, FALSE, FALSE },
876 { "y", "header \"X-Label\"", 1, TRUE, TRUE },
877 { "&", "&", 0, FALSE, FALSE },
878 { "|", "|", 0, FALSE, FALSE },
879 { "p", "partial", 0, FALSE, FALSE },
880 { NULL, NULL, 0, FALSE, FALSE }
883 if (search_string == NULL)
886 copy_str = g_strdup(search_string);
888 matcherstr = g_string_sized_new(16);
889 cmd_start = copy_str;
890 while (cmd_start && *cmd_start) {
891 /* skip all white spaces */
892 while (*cmd_start && isspace((guchar)*cmd_start))
896 /* extract a command */
897 while (*cmd_end && !isspace((guchar)*cmd_end))
901 save_char = *cmd_end;
907 /* ~ and ! mean logical NOT */
908 if (*cmd_start == '~' || *cmd_start == '!')
913 /* % means case sensitive match */
914 if (*cmd_start == '%')
920 /* find matching abbreviation */
921 for (i = 0; cmds[i].command; i++) {
922 if (!strcmp(cmd_start, cmds[i].abbreviated)) {
923 /* restore character */
924 *cmd_end = save_char;
927 if (matcherstr->len > 0) {
928 g_string_append(matcherstr, " ");
931 g_string_append(matcherstr, "~");
932 g_string_append(matcherstr, cmds[i].command);
933 g_string_append(matcherstr, " ");
935 /* stop if no params required */
936 if (cmds[i].numparams == 0)
939 /* extract a parameter, allow quotes */
940 while (*cmd_end && isspace((guchar)*cmd_end))
944 if (*cmd_start == '"') {
951 /* extract actual parameter */
952 while ((*cmd_end) && (*cmd_end != term_char))
958 save_char = *cmd_end;
961 if (cmds[i].qualifier) {
963 g_string_append(matcherstr, "regexp ");
965 g_string_append(matcherstr, "regexpcase ");
968 /* do we need to add quotes ? */
969 if (cmds[i].quotes && term_char != '"')
970 g_string_append(matcherstr, "\"");
972 /* copy actual parameter */
973 g_string_append(matcherstr, cmd_start);
975 /* do we need to add quotes ? */
976 if (cmds[i].quotes && term_char != '"')
977 g_string_append(matcherstr, "\"");
979 /* restore original character */
980 *cmd_end = save_char;
993 /* return search string if no match is found to allow
994 all available filtering expressions in quicksearch */
995 if (matcherstr->len > 0) returnstr = matcherstr->str;
996 else returnstr = g_strdup(search_string);
998 g_string_free(matcherstr, FALSE);
1002 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1004 quicksearch->running = run;
1007 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1009 return quicksearch->running;
1012 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1014 GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
1015 glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1017 char *str = g_strdup(gtk_entry_get_text(entry));
1024 if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1025 /* remove selection */
1026 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1027 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1028 /* refresh string */
1030 str = g_strdup(gtk_entry_get_text(entry));
1034 if (!(c = gdk_keyval_to_unicode(val))) {
1038 char_len = g_unichar_to_utf8(c, key);
1041 key[char_len] = '\0';
1042 if (curpos < g_utf8_strlen(str, -1)) {
1043 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1044 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1046 new = g_strdup_printf("%s%s%s", begin, key, end);
1047 gtk_entry_set_text(entry, new);
1050 new = g_strdup_printf("%s%s", begin, key);
1051 gtk_entry_set_text(entry, new);
1055 gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1059 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1062 GSList *msglist = folder_item_get_msg_list(src);
1064 gboolean result = FALSE;
1065 gint num = 0, total = src->total_msgs;
1066 gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1068 statusbar_print_all(_("Searching in %s... \n"),
1069 src->path ? src->path : "(null)");
1070 folder_item_update_freeze();
1071 for (cur = msglist; cur != NULL; cur = cur->next) {
1072 MsgInfo *msg = (MsgInfo *)cur->data;
1073 statusbar_progress_all(num++,total, interval);
1074 if (quicksearch_match(quicksearch, msg)) {
1075 procmsg_msginfo_free(msg);
1079 procmsg_msginfo_free(msg);
1080 if (num % interval == 0)
1082 if (!quicksearch_is_active(quicksearch))
1085 folder_item_update_thaw();
1086 statusbar_progress_all(0,0,0);
1087 statusbar_pop_all();
1089 g_slist_free(msglist);
1093 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1094 FolderView *folderview,
1095 FolderItem *folder_item)
1097 FolderItem *cur = NULL;
1098 GNode *node = folder_item->node->children;
1100 if (!prefs_common.summary_quicksearch_recurse
1101 || quicksearch->in_typing == TRUE)
1104 for (; node != NULL; node = node->next) {
1105 cur = FOLDER_ITEM(node->data);
1106 if (quicksearch_match_subfolder(quicksearch, cur)) {
1107 folderview_update_search_icon(cur, TRUE);
1109 folderview_update_search_icon(cur, FALSE);
1111 if (cur->node->children)
1112 quicksearch_search_subfolders(quicksearch,
1116 quicksearch->root_folder_item = folder_item;
1117 if (!quicksearch_is_active(quicksearch))
1118 quicksearch_reset_cur_folder_item(quicksearch);
1121 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1122 FolderItem *folder_item)
1124 FolderItem *cur = NULL;
1125 GNode *node = (folder_item && folder_item->node) ?
1126 folder_item->node->children : NULL;
1128 for (; node != NULL; node = node->next) {
1129 cur = FOLDER_ITEM(node->data);
1130 folderview_update_search_icon(cur, FALSE);
1131 if (cur->node->children)
1132 quicksearch_reset_folder_items(quicksearch,
1137 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1139 if (quicksearch->root_folder_item)
1140 quicksearch_reset_folder_items(quicksearch,
1141 quicksearch->root_folder_item);
1143 quicksearch->root_folder_item = NULL;
1146 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1148 return quicksearch->in_typing;