2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 2004 Hiroyuki Yamamoto & the Sylpheed-Claws 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 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
68 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
69 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
71 static void prepare_matcher(QuickSearch *quicksearch)
73 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
75 if (search_string == NULL || search_string[0] == '\0') {
76 quicksearch_set_active(quicksearch, FALSE);
79 if (quicksearch->matcher_list != NULL) {
80 if (quicksearch->matching) {
81 quicksearch->deferred_free = TRUE;
84 quicksearch->deferred_free = FALSE;
85 matcherlist_free(quicksearch->matcher_list);
86 quicksearch->matcher_list = NULL;
89 if (search_string == NULL || search_string[0] == '\0') {
93 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
96 newstr = expand_search_string(search_string);
97 if (newstr && newstr[0] != '\0') {
98 quicksearch->matcher_list = matcher_parser_get_cond(newstr);
101 quicksearch->matcher_list = NULL;
102 quicksearch_set_active(quicksearch, FALSE);
107 g_free(quicksearch->search_string);
108 quicksearch->search_string = g_strdup(search_string);
111 quicksearch_set_active(quicksearch, TRUE);
114 static void update_extended_buttons (QuickSearch *quicksearch)
116 GtkWidget *expr_btn = quicksearch->search_condition_expression;
117 GtkWidget *ext_btn = quicksearch->search_description;
119 g_return_if_fail(expr_btn != NULL);
120 g_return_if_fail(ext_btn != NULL);
122 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
123 gtk_widget_show(expr_btn);
124 gtk_widget_show(ext_btn);
126 gtk_widget_hide(expr_btn);
127 gtk_widget_hide(ext_btn);
131 static gboolean searchbar_focus_evt(GtkWidget *widget, GdkEventFocus *event,
132 QuickSearch *quicksearch)
134 quicksearch->has_focus = (event && event->in);
138 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
140 return quicksearch->has_focus;
143 static void searchbar_run(QuickSearch *quicksearch)
145 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
147 if (search_string && strlen(search_string) != 0) {
148 prefs_common.summary_quicksearch_history =
149 add_history(prefs_common.summary_quicksearch_history,
151 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry),
152 prefs_common.summary_quicksearch_history);
155 prepare_matcher(quicksearch);
157 quicksearch_set_running(quicksearch, TRUE);
158 if (quicksearch->callback != NULL)
159 quicksearch->callback(quicksearch, quicksearch->callback_data);
160 quicksearch_set_running(quicksearch, FALSE);
163 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
164 QuickSearch *quicksearch)
166 if (event != NULL && event->keyval == GDK_Escape) {
167 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
171 if (event != NULL && event->keyval == GDK_Return) {
172 /* add expression to history list and exec quicksearch */
173 searchbar_run(quicksearch);
175 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
182 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
184 QuickSearch *quicksearch = (QuickSearch *)data;
185 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
187 prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
188 G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
189 GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
191 /* Show extended search description button, only when Extended is selected */
192 update_extended_buttons(quicksearch);
194 if (!search_string || strlen(search_string) == 0) {
198 prepare_matcher(quicksearch);
200 quicksearch_set_running(quicksearch, TRUE);
201 if (quicksearch->callback != NULL)
202 quicksearch->callback(quicksearch, quicksearch->callback_data);
203 quicksearch_set_running(quicksearch, FALSE);
207 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
209 QuickSearch *quicksearch = (QuickSearch *)data;
210 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
211 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
213 prefs_common.summary_quicksearch_recurse = checked;
215 /* reselect the search type */
216 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
217 prefs_common.summary_quicksearch_type);
219 if (!search_string || strlen(search_string) == 0) {
223 prepare_matcher(quicksearch);
225 quicksearch_set_running(quicksearch, TRUE);
226 if (quicksearch->callback != NULL)
227 quicksearch->callback(quicksearch, quicksearch->callback_data);
228 quicksearch_set_running(quicksearch, FALSE);
232 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
234 QuickSearch *quicksearch = (QuickSearch *)data;
235 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
237 prefs_common.summary_quicksearch_sticky = checked;
239 /* reselect the search type */
240 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
241 prefs_common.summary_quicksearch_type);
247 * Strings describing how to use Extended Search
249 * When adding new lines, remember to put 2 strings for each line
251 static gchar *search_descr_strings[] = {
252 "a", N_("all messages"),
253 "ag #", N_("messages whose age is greater than #"),
254 "al #", N_("messages whose age is less than #"),
255 "b S", N_("messages which contain S in the message body"),
256 "B S", N_("messages which contain S in the whole message"),
257 "c S", N_("messages carbon-copied to S"),
258 "C S", N_("message is either to: or cc: to S"),
259 "D", N_("deleted messages"), /** how I can filter deleted messages **/
260 "e S", N_("messages which contain S in the Sender field"),
261 "E S", N_("true if execute \"S\" succeeds"),
262 "f S", N_("messages originating from user S"),
263 "F", N_("forwarded messages"),
264 "h S", N_("messages which contain header S"),
265 "i S", N_("messages which contain S in Message-ID header"),
266 "I S", N_("messages which contain S in inreplyto header"),
267 "L", N_("locked messages"),
268 "n S", N_("messages which are in newsgroup S"),
269 "N", N_("new messages"),
270 "O", N_("old messages"),
271 "p", N_("incomplete messages (not entirely downloaded)"),
272 "r", N_("messages which have been replied to"),
273 "R", N_("read messages"),
274 "s S", N_("messages which contain S in subject"),
275 "se #", N_("messages whose score is equal to #"),
276 "sg #", N_("messages whose score is greater than #"),
277 "sl #", N_("messages whose score is lower than #"),
278 "Se #", N_("messages whose size is equal to #"),
279 "Sg #", N_("messages whose size is greater than #"),
280 "Ss #", N_("messages whose size is smaller than #"),
281 "t S", N_("messages which have been sent to S"),
282 "T", N_("marked messages"),
283 "U", N_("unread messages"),
284 "x S", N_("messages which contain S in References header"),
285 "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
286 "y S", N_("messages which contain S in X-Label header"),
288 "&", N_("logical AND operator"),
289 "|", N_("logical OR operator"),
290 "! or ~", N_("logical NOT operator"),
291 "%", N_("case sensitive search"),
293 " ", N_("all filtering expressions are allowed"),
297 static DescriptionWindow search_descr = {
301 N_("Extended Search"),
302 N_("Extended Search allows the user to define criteria that messages must "
303 "have in order to match and be displayed in the message list.\n\n"
304 "The following symbols can be used:"),
308 static void search_description_cb(GtkWidget *widget)
310 description_window_create(&search_descr);
313 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
315 QuickSearch *quicksearch = (QuickSearch *)data;
317 if (!quicksearch->active)
320 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
325 static void search_condition_expr_done(MatcherList * matchers)
330 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
332 if (matchers == NULL)
335 str = matcherlist_to_string(matchers);
338 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
339 prefs_common.summary_quicksearch_type, str);
342 /* add expression to history list and exec quicksearch */
343 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch);
347 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
349 const gchar * cond_str;
350 MatcherList * matchers = NULL;
352 g_return_val_if_fail(
353 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
356 /* re-use it the current quicksearch value if it's a condition expression,
357 otherwise ignore it silently */
358 cond_str = gtk_entry_get_text(
359 GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
360 search_string_entry)->entry));
361 if (*cond_str != '\0') {
362 matchers = matcher_parser_get_cond((gchar*)cond_str);
365 prefs_matcher_open(matchers, search_condition_expr_done);
367 if (matchers != NULL)
368 matcherlist_free(matchers);
373 QuickSearch *quicksearch_new()
375 QuickSearch *quicksearch;
377 GtkWidget *hbox_search;
378 GtkWidget *search_type_opt;
379 GtkWidget *search_type;
380 GtkWidget *search_string_entry;
381 GtkWidget *search_hbox;
382 GtkWidget *search_description;
383 GtkWidget *clear_search;
384 GtkWidget *search_condition_expression;
386 GtkTooltips *search_cond_expr_tip;
388 quicksearch = g_new0(QuickSearch, 1);
391 hbox_search = gtk_hbox_new(FALSE, 0);
393 search_type_opt = gtk_option_menu_new();
394 gtk_widget_show(search_type_opt);
395 gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
397 search_type = gtk_menu_new();
398 MENUITEM_ADD (search_type, menuitem, _("Subject"), QUICK_SEARCH_SUBJECT);
399 g_signal_connect(G_OBJECT(menuitem), "activate",
400 G_CALLBACK(searchtype_changed),
402 MENUITEM_ADD (search_type, menuitem, _("From"), QUICK_SEARCH_FROM);
403 g_signal_connect(G_OBJECT(menuitem), "activate",
404 G_CALLBACK(searchtype_changed),
406 MENUITEM_ADD (search_type, menuitem, _("To"), QUICK_SEARCH_TO);
407 g_signal_connect(G_OBJECT(menuitem), "activate",
408 G_CALLBACK(searchtype_changed),
410 MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
411 g_signal_connect(G_OBJECT(menuitem), "activate",
412 G_CALLBACK(searchtype_changed),
415 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
417 menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
418 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
420 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
421 prefs_common.summary_quicksearch_recurse);
423 g_signal_connect(G_OBJECT(menuitem), "activate",
424 G_CALLBACK(searchtype_recursive_changed),
427 menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
428 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
430 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
431 prefs_common.summary_quicksearch_sticky);
433 g_signal_connect(G_OBJECT(menuitem), "activate",
434 G_CALLBACK(searchtype_sticky_changed),
437 gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
439 gtk_option_menu_set_history(GTK_OPTION_MENU(search_type_opt), prefs_common.summary_quicksearch_type);
441 gtk_widget_show(search_type);
443 search_string_entry = gtk_combo_new();
444 gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
445 gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
446 gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
447 if (prefs_common.summary_quicksearch_history)
448 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
449 prefs_common.summary_quicksearch_history);
450 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
451 gtk_widget_show(search_string_entry);
453 search_hbox = gtk_hbox_new(FALSE, 5);
455 #if GTK_CHECK_VERSION(2, 8, 0)
456 clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
458 clear_search = gtk_button_new_with_label(_(" Clear "));
460 gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
462 g_signal_connect(G_OBJECT(clear_search), "clicked",
463 G_CALLBACK(clear_search_cb), quicksearch);
464 gtk_widget_show(clear_search);
466 #if GTK_CHECK_VERSION(2, 8, 0)
467 search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
469 search_condition_expression = gtk_button_new_with_label(" ... ");
471 gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
473 g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
474 G_CALLBACK(search_condition_expr),
476 search_cond_expr_tip = gtk_tooltips_new();
477 gtk_tooltips_set_tip(GTK_TOOLTIPS(search_cond_expr_tip),
478 search_condition_expression,
479 _("Edit search criteria"), NULL);
480 gtk_widget_show(search_condition_expression);
482 #if GTK_CHECK_VERSION(2, 8, 0)
483 search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
485 search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
487 gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
489 g_signal_connect(G_OBJECT(search_description), "clicked",
490 G_CALLBACK(search_description_cb), NULL);
491 gtk_widget_show(search_description);
493 gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
494 gtk_widget_show(search_hbox);
496 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
498 G_CALLBACK(searchbar_pressed),
500 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
502 G_CALLBACK(searchbar_focus_evt),
504 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
506 G_CALLBACK(searchbar_focus_evt),
509 quicksearch->hbox_search = hbox_search;
510 quicksearch->search_type = search_type;
511 quicksearch->search_type_opt = search_type_opt;
512 quicksearch->search_string_entry = search_string_entry;
513 quicksearch->search_condition_expression = search_condition_expression;
514 quicksearch->search_description = search_description;
515 quicksearch->matcher_list = NULL;
516 quicksearch->active = FALSE;
517 quicksearch->running = FALSE;
518 quicksearch->clear_search = clear_search;
520 update_extended_buttons(quicksearch);
525 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
527 return quicksearch->hbox_search;
530 void quicksearch_show(QuickSearch *quicksearch)
532 prepare_matcher(quicksearch);
533 gtk_widget_show(quicksearch->hbox_search);
534 update_extended_buttons(quicksearch);
535 gtk_widget_grab_focus(
536 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
539 void quicksearch_hide(QuickSearch *quicksearch)
541 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
542 quicksearch_set_active(quicksearch, FALSE);
543 gtk_widget_hide(quicksearch->hbox_search);
546 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
547 const gchar *matchstring)
549 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
551 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
553 prefs_common.summary_quicksearch_type = type;
555 prepare_matcher(quicksearch);
557 quicksearch_set_running(quicksearch, TRUE);
558 if (quicksearch->callback != NULL)
559 quicksearch->callback(quicksearch, quicksearch->callback_data);
560 quicksearch_set_running(quicksearch, FALSE);
563 gboolean quicksearch_is_active(QuickSearch *quicksearch)
565 return quicksearch->active &&
566 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
567 || quicksearch->matcher_list != NULL);
570 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
572 static GdkColor yellow;
574 static GdkColor black;
575 static gboolean colors_initialised = FALSE;
576 gboolean error = FALSE;
578 if (!colors_initialised) {
579 gdk_color_parse("#f5f6be", &yellow);
580 gdk_color_parse("#000000", &black);
581 gdk_color_parse("#ff7070", &red);
582 colors_initialised = gdk_colormap_alloc_color(
583 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
584 colors_initialised &= gdk_colormap_alloc_color(
585 gdk_colormap_get_system(), &black, FALSE, TRUE);
586 colors_initialised &= gdk_colormap_alloc_color(
587 gdk_colormap_get_system(), &red, FALSE, TRUE);
590 quicksearch->active = active;
593 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
594 && quicksearch->matcher_list == NULL))
598 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
599 if (colors_initialised) {
600 gtk_widget_modify_base(
601 GTK_COMBO(quicksearch->search_string_entry)->entry,
602 GTK_STATE_NORMAL, error ? &red : &yellow);
603 gtk_widget_modify_text(
604 GTK_COMBO(quicksearch->search_string_entry)->entry,
605 GTK_STATE_NORMAL, &black);
608 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
609 if (colors_initialised) {
610 gtk_widget_modify_base(
611 GTK_COMBO(quicksearch->search_string_entry)->entry,
612 GTK_STATE_NORMAL, NULL);
613 gtk_widget_modify_text(
614 GTK_COMBO(quicksearch->search_string_entry)->entry,
615 GTK_STATE_NORMAL, NULL);
620 quicksearch_reset_cur_folder_item(quicksearch);
624 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
625 QuickSearchExecuteCallback callback,
628 quicksearch->callback = callback;
629 quicksearch->callback_data = data;
632 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
634 gchar *searched_header = NULL;
635 gboolean result = FALSE;
637 if (!quicksearch->active)
640 switch (prefs_common.summary_quicksearch_type) {
641 case QUICK_SEARCH_SUBJECT:
642 searched_header = msginfo->subject;
644 case QUICK_SEARCH_FROM:
645 searched_header = msginfo->from;
647 case QUICK_SEARCH_TO:
648 searched_header = msginfo->to;
650 case QUICK_SEARCH_EXTENDED:
653 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
656 quicksearch->matching = TRUE;
657 if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
658 quicksearch->search_string &&
659 searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
661 else if ((quicksearch->matcher_list != NULL) &&
662 matcherlist_match(quicksearch->matcher_list, msginfo))
665 quicksearch->matching = FALSE;
666 if (quicksearch->deferred_free) {
667 prepare_matcher(quicksearch);
673 /* allow Mutt-like patterns in quick search */
674 gchar *expand_search_string(const gchar *search_string)
677 gchar term_char, save_char;
678 gchar *cmd_start, *cmd_end;
680 gchar *returnstr = NULL;
682 gboolean casesens, dontmatch;
683 /* list of allowed pattern abbreviations */
685 gchar *abbreviated; /* abbreviation */
686 gchar *command; /* actual matcher command */
687 gint numparams; /* number of params for cmd */
688 gboolean qualifier; /* do we append regexpcase */
689 gboolean quotes; /* do we need quotes */
692 { "a", "all", 0, FALSE, FALSE },
693 { "ag", "age_greater", 1, FALSE, FALSE },
694 { "al", "age_lower", 1, FALSE, FALSE },
695 { "b", "body_part", 1, TRUE, TRUE },
696 { "B", "message", 1, TRUE, TRUE },
697 { "c", "cc", 1, TRUE, TRUE },
698 { "C", "to_or_cc", 1, TRUE, TRUE },
699 { "D", "deleted", 0, FALSE, FALSE },
700 { "e", "header \"Sender\"", 1, TRUE, TRUE },
701 { "E", "execute", 1, FALSE, TRUE },
702 { "f", "from", 1, TRUE, TRUE },
703 { "F", "forwarded", 0, FALSE, FALSE },
704 { "h", "headers_part", 1, TRUE, TRUE },
705 { "i", "header \"Message-ID\"", 1, TRUE, TRUE },
706 { "I", "inreplyto", 1, TRUE, TRUE },
707 { "L", "locked", 0, FALSE, FALSE },
708 { "n", "newsgroups", 1, TRUE, TRUE },
709 { "N", "new", 0, FALSE, FALSE },
710 { "O", "~new", 0, FALSE, FALSE },
711 { "r", "replied", 0, FALSE, FALSE },
712 { "R", "~unread", 0, FALSE, FALSE },
713 { "s", "subject", 1, TRUE, TRUE },
714 { "se", "score_equal", 1, FALSE, FALSE },
715 { "sg", "score_greater", 1, FALSE, FALSE },
716 { "sl", "score_lower", 1, FALSE, FALSE },
717 { "Se", "size_equal", 1, FALSE, FALSE },
718 { "Sg", "size_greater", 1, FALSE, FALSE },
719 { "Ss", "size_smaller", 1, FALSE, FALSE },
720 { "t", "to", 1, TRUE, TRUE },
721 { "T", "marked", 0, FALSE, FALSE },
722 { "U", "unread", 0, FALSE, FALSE },
723 { "x", "header \"References\"", 1, TRUE, TRUE },
724 { "X", "test", 1, FALSE, FALSE },
725 { "y", "header \"X-Label\"", 1, TRUE, TRUE },
726 { "&", "&", 0, FALSE, FALSE },
727 { "|", "|", 0, FALSE, FALSE },
728 { "p", "partial", 0, FALSE, FALSE },
729 { NULL, NULL, 0, FALSE, FALSE }
732 if (search_string == NULL)
735 copy_str = g_strdup(search_string);
737 matcherstr = g_string_sized_new(16);
738 cmd_start = copy_str;
739 while (cmd_start && *cmd_start) {
740 /* skip all white spaces */
741 while (*cmd_start && isspace((guchar)*cmd_start))
745 /* extract a command */
746 while (*cmd_end && !isspace((guchar)*cmd_end))
750 save_char = *cmd_end;
756 /* ~ and ! mean logical NOT */
757 if (*cmd_start == '~' || *cmd_start == '!')
762 /* % means case sensitive match */
763 if (*cmd_start == '%')
769 /* find matching abbreviation */
770 for (i = 0; cmds[i].command; i++) {
771 if (!strcmp(cmd_start, cmds[i].abbreviated)) {
772 /* restore character */
773 *cmd_end = save_char;
776 if (matcherstr->len > 0) {
777 g_string_append(matcherstr, " ");
780 g_string_append(matcherstr, "~");
781 g_string_append(matcherstr, cmds[i].command);
782 g_string_append(matcherstr, " ");
784 /* stop if no params required */
785 if (cmds[i].numparams == 0)
788 /* extract a parameter, allow quotes */
789 while (*cmd_end && isspace((guchar)*cmd_end))
793 if (*cmd_start == '"') {
800 /* extract actual parameter */
801 while ((*cmd_end) && (*cmd_end != term_char))
807 save_char = *cmd_end;
810 if (cmds[i].qualifier) {
812 g_string_append(matcherstr, "regexp ");
814 g_string_append(matcherstr, "regexpcase ");
817 /* do we need to add quotes ? */
818 if (cmds[i].quotes && term_char != '"')
819 g_string_append(matcherstr, "\"");
821 /* copy actual parameter */
822 g_string_append(matcherstr, cmd_start);
824 /* do we need to add quotes ? */
825 if (cmds[i].quotes && term_char != '"')
826 g_string_append(matcherstr, "\"");
828 /* restore original character */
829 *cmd_end = save_char;
842 /* return search string if no match is found to allow
843 all available filtering expressions in quicksearch */
844 if (matcherstr->len > 0) returnstr = matcherstr->str;
845 else returnstr = g_strdup(search_string);
847 g_string_free(matcherstr, FALSE);
851 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
853 quicksearch->running = run;
856 gboolean quicksearch_is_running(QuickSearch *quicksearch)
858 return quicksearch->running;
861 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
863 GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
864 glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
866 char *str = g_strdup(gtk_entry_get_text(entry));
873 if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
874 /* remove selection */
875 gtk_editable_delete_selection(GTK_EDITABLE(entry));
876 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
879 str = g_strdup(gtk_entry_get_text(entry));
883 if (!(c = gdk_keyval_to_unicode(val))) {
887 char_len = g_unichar_to_utf8(c, key);
890 key[char_len] = '\0';
891 if (curpos < g_utf8_strlen(str, -1)) {
892 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
893 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
895 printf("new %s %s %s\n", begin, key, end);
896 new = g_strdup_printf("%s%s%s", begin, key, end);
897 gtk_entry_set_text(entry, new);
900 new = g_strdup_printf("%s%s", begin, key);
901 gtk_entry_set_text(entry, new);
905 gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
909 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
912 GSList *msglist = folder_item_get_msg_list(src);
914 gboolean result = FALSE;
915 gint num = 0, total = src->total_msgs;
916 statusbar_print_all(_("Searching in %s... \n"),
917 src->path ? src->path : "(null)");
918 for (cur = msglist; cur != NULL; cur = cur->next) {
919 MsgInfo *msg = (MsgInfo *)cur->data;
920 statusbar_progress_all(num++,total, 50);
921 if (quicksearch_match(quicksearch, msg)) {
922 procmsg_msginfo_free(msg);
926 procmsg_msginfo_free(msg);
928 if (!quicksearch_is_active(quicksearch))
931 statusbar_progress_all(0,0,0);
934 g_slist_free(msglist);
938 void quicksearch_search_subfolders(QuickSearch *quicksearch,
939 FolderView *folderview,
940 FolderItem *folder_item)
942 FolderItem *cur = NULL;
943 GNode *node = folder_item->node->children;
945 if (!prefs_common.summary_quicksearch_recurse)
948 for (; node != NULL; node = node->next) {
949 cur = FOLDER_ITEM(node->data);
950 if (quicksearch_match_subfolder(quicksearch, cur)) {
951 folderview_update_search_icon(cur, TRUE);
953 folderview_update_search_icon(cur, FALSE);
955 if (cur->node->children)
956 quicksearch_search_subfolders(quicksearch,
960 quicksearch->root_folder_item = folder_item;
961 if (!quicksearch_is_active(quicksearch))
962 quicksearch_reset_cur_folder_item(quicksearch);
965 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
966 FolderItem *folder_item)
968 FolderItem *cur = NULL;
969 GNode *node = (folder_item && folder_item->node) ?
970 folder_item->node->children : NULL;
972 for (; node != NULL; node = node->next) {
973 cur = FOLDER_ITEM(node->data);
974 folderview_update_search_icon(cur, FALSE);
975 if (cur->node->children)
976 quicksearch_reset_folder_items(quicksearch,
981 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
983 if (quicksearch->root_folder_item)
984 quicksearch_reset_folder_items(quicksearch,
985 quicksearch->root_folder_item);
987 quicksearch->root_folder_item = NULL;