2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Colin Leroy <colin@colino.net>
4 * and the Claws Mail team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include <glib/gi18n.h>
30 #include <gdk/gdkkeysyms.h>
34 #include "prefs_common.h"
35 #include "description_window.h"
37 #include "matcher_parser.h"
38 #include "quicksearch.h"
39 #include "folderview.h"
41 #include "prefs_matcher.h"
43 #include "statusbar.h"
47 GtkWidget *hbox_search;
48 GtkWidget *search_type;
49 GtkWidget *search_type_opt;
50 GtkWidget *search_string_entry;
51 GtkWidget *search_condition_expression;
52 GtkWidget *search_description;
53 GtkWidget *clear_search;
57 MatcherList *matcher_list;
59 QuickSearchExecuteCallback callback;
60 gpointer callback_data;
64 gboolean deferred_free;
65 FolderItem *root_folder_item;
68 guint press_timeout_id;
71 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
72 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
73 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
74 static gchar *expand_search_string(const gchar *str);
76 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
78 return quicksearch->is_fast;
81 static void quicksearch_set_type(QuickSearch *quicksearch, gint type)
84 index = menu_find_option_menu_index(GTK_OPTION_MENU(quicksearch->search_type_opt),
85 GINT_TO_POINTER(type),
87 gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt), index);
90 static void prepare_matcher(QuickSearch *quicksearch)
92 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
94 if (search_string == NULL || search_string[0] == '\0') {
95 quicksearch_set_active(quicksearch, FALSE);
98 if (quicksearch->matcher_list != NULL) {
99 if (quicksearch->matching) {
100 quicksearch->deferred_free = TRUE;
103 quicksearch->deferred_free = FALSE;
104 matcherlist_free(quicksearch->matcher_list);
105 quicksearch->matcher_list = NULL;
108 if (search_string == NULL || search_string[0] == '\0') {
112 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
115 newstr = expand_search_string(search_string);
116 if (newstr && newstr[0] != '\0') {
117 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
120 quicksearch->matcher_list = NULL;
121 quicksearch_set_active(quicksearch, FALSE);
126 quicksearch->is_fast = TRUE;
127 g_free(quicksearch->search_string);
128 quicksearch->search_string = g_strdup(search_string);
131 quicksearch_set_active(quicksearch, TRUE);
134 static void update_extended_buttons (QuickSearch *quicksearch)
136 GtkWidget *expr_btn = quicksearch->search_condition_expression;
137 GtkWidget *ext_btn = quicksearch->search_description;
139 g_return_if_fail(expr_btn != NULL);
140 g_return_if_fail(ext_btn != NULL);
142 if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
143 gtk_widget_show(expr_btn);
144 gtk_widget_show(ext_btn);
146 gtk_widget_hide(expr_btn);
147 gtk_widget_hide(ext_btn);
151 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
154 qs->has_focus = TRUE;
158 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
161 qs->has_focus = FALSE;
162 qs->in_typing = FALSE;
166 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
168 return quicksearch->has_focus;
171 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
173 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
176 if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) {
177 prefs_common.summary_quicksearch_history =
178 add_history(prefs_common.summary_quicksearch_history,
180 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry),
181 prefs_common.summary_quicksearch_history);
184 prepare_matcher(quicksearch);
185 if (run_only_if_fast && !quicksearch->is_fast)
187 if (quicksearch->matcher_list == NULL &&
188 prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED &&
189 search_string && strlen(search_string) != 0)
191 quicksearch_set_running(quicksearch, TRUE);
192 if (quicksearch->callback != NULL)
193 quicksearch->callback(quicksearch, quicksearch->callback_data);
194 quicksearch_set_running(quicksearch, FALSE);
197 static int searchbar_changed_timeout(void *data)
199 QuickSearch *qs = (QuickSearch *)data;
200 if (qs && prefs_common.summary_quicksearch_dynamic) {
201 qs->in_typing = TRUE;
202 searchbar_run(qs, TRUE);
207 static gboolean searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
209 if (prefs_common.summary_quicksearch_dynamic) {
210 if (qs->press_timeout_id != -1) {
211 g_source_remove(qs->press_timeout_id);
213 qs->press_timeout_id = g_timeout_add(500,
214 searchbar_changed_timeout, qs);
220 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
221 QuickSearch *quicksearch)
223 if (event != NULL && event->keyval == GDK_Escape) {
227 quicksearch->in_typing = FALSE;
229 str = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
230 g_return_val_if_fail(str != NULL, TRUE);
232 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
234 summaryview_activate_quicksearch(
235 mainwindow_get_mainwindow()->summaryview,
238 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
239 gtk_widget_grab_focus(
240 mainwindow_get_mainwindow()->summaryview->ctree);
246 if (event != NULL && event->keyval == GDK_Return) {
247 if (quicksearch->press_timeout_id != -1) {
248 g_source_remove(quicksearch->press_timeout_id);
249 quicksearch->press_timeout_id = -1;
251 quicksearch->in_typing = FALSE;
252 /* add expression to history list and exec quicksearch */
253 searchbar_run(quicksearch, FALSE);
255 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
262 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
264 QuickSearch *quicksearch = (QuickSearch *)data;
265 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
267 prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
268 G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
269 GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
271 /* Show extended search description button, only when Extended is selected */
272 update_extended_buttons(quicksearch);
274 if (!search_string || strlen(search_string) == 0) {
278 prepare_matcher(quicksearch);
280 quicksearch_set_running(quicksearch, TRUE);
281 if (quicksearch->callback != NULL)
282 quicksearch->callback(quicksearch, quicksearch->callback_data);
283 quicksearch_set_running(quicksearch, FALSE);
287 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
289 QuickSearch *quicksearch = (QuickSearch *)data;
290 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
291 const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
293 prefs_common.summary_quicksearch_recurse = checked;
295 /* reselect the search type */
296 quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
298 if (!search_string || strlen(search_string) == 0) {
302 prepare_matcher(quicksearch);
304 quicksearch_set_running(quicksearch, TRUE);
305 if (quicksearch->callback != NULL)
306 quicksearch->callback(quicksearch, quicksearch->callback_data);
307 quicksearch_set_running(quicksearch, FALSE);
311 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
313 QuickSearch *quicksearch = (QuickSearch *)data;
314 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
316 prefs_common.summary_quicksearch_sticky = checked;
318 /* reselect the search type */
319 quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
324 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
326 QuickSearch *quicksearch = (QuickSearch *)data;
327 gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
329 prefs_common.summary_quicksearch_dynamic = checked;
331 /* reselect the search type */
332 quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
338 * Strings describing how to use Extended Search
340 * When adding new lines, remember to put 2 strings for each line
342 static gchar *search_descr_strings[] = {
343 "a", N_("all messages"),
344 "ag #", N_("messages whose age is greater than #"),
345 "al #", N_("messages whose age is less than #"),
346 "b S", N_("messages which contain S in the message body"),
347 "B S", N_("messages which contain S in the whole message"),
348 "c S", N_("messages carbon-copied to S"),
349 "C S", N_("message is either to: or cc: to S"),
350 "D", N_("deleted messages"), /** how I can filter deleted messages **/
351 "e S", N_("messages which contain S in the Sender field"),
352 "E S", N_("true if execute \"S\" succeeds"),
353 "f S", N_("messages originating from user S"),
354 "F", N_("forwarded messages"),
355 "h S", N_("messages which contain header S"),
356 "i S", N_("messages which contain S in Message-ID header"),
357 "I S", N_("messages which contain S in inreplyto header"),
358 "k #", N_("messages which are marked with color #"),
359 "L", N_("locked messages"),
360 "n S", N_("messages which are in newsgroup S"),
361 "N", N_("new messages"),
362 "O", N_("old messages"),
363 "p", N_("incomplete messages (not entirely downloaded)"),
364 "r", N_("messages which have been replied to"),
365 "R", N_("read messages"),
366 "s S", N_("messages which contain S in subject"),
367 "se #", N_("messages whose score is equal to #"),
368 "sg #", N_("messages whose score is greater than #"),
369 "sl #", N_("messages whose score is lower than #"),
370 "Se #", N_("messages whose size is equal to #"),
371 "Sg #", N_("messages whose size is greater than #"),
372 "Ss #", N_("messages whose size is smaller than #"),
373 "t S", N_("messages which have been sent to S"),
374 "T", N_("marked messages"),
375 "U", N_("unread messages"),
376 "x S", N_("messages which contain S in References header"),
377 "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
378 "y S", N_("messages which contain S in X-Label header"),
380 "&", N_("logical AND operator"),
381 "|", N_("logical OR operator"),
382 "! or ~", N_("logical NOT operator"),
383 "%", N_("case sensitive search"),
385 " ", N_("all filtering expressions are allowed"),
389 static DescriptionWindow search_descr = {
393 N_("Extended Search"),
394 N_("Extended Search allows the user to define criteria that messages must "
395 "have in order to match and be displayed in the message list.\n\n"
396 "The following symbols can be used:"),
400 static void search_description_cb(GtkWidget *widget)
402 description_window_create(&search_descr);
405 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
407 QuickSearch *quicksearch = (QuickSearch *)data;
409 if (!quicksearch->active)
412 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
417 static void search_condition_expr_done(MatcherList * matchers)
422 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
424 if (matchers == NULL)
427 str = matcherlist_to_string(matchers);
430 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
431 prefs_common.summary_quicksearch_type, str);
434 /* add expression to history list and exec quicksearch */
435 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
439 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
441 const gchar * cond_str;
442 MatcherList * matchers = NULL;
444 g_return_val_if_fail(
445 mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
448 /* re-use it the current quicksearch value if it's a condition expression,
449 otherwise ignore it silently */
450 cond_str = gtk_entry_get_text(
451 GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
452 search_string_entry)->entry));
453 if (*cond_str != '\0') {
454 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
457 prefs_matcher_open(matchers, search_condition_expr_done);
459 if (matchers != NULL)
460 matcherlist_free(matchers);
465 QuickSearch *quicksearch_new()
467 QuickSearch *quicksearch;
469 GtkWidget *hbox_search;
470 GtkWidget *search_type_opt;
471 GtkWidget *search_type;
472 GtkWidget *search_string_entry;
473 GtkWidget *search_hbox;
474 GtkWidget *search_description;
475 GtkWidget *clear_search;
476 GtkWidget *search_condition_expression;
478 GtkTooltips *tips = gtk_tooltips_new();
480 quicksearch = g_new0(QuickSearch, 1);
483 hbox_search = gtk_hbox_new(FALSE, 0);
485 search_type_opt = gtk_option_menu_new();
486 gtk_widget_show(search_type_opt);
487 gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
489 search_type = gtk_menu_new();
490 MENUITEM_ADD (search_type, menuitem,
491 prefs_common_translated_header_name("Subject"), QUICK_SEARCH_SUBJECT);
492 g_signal_connect(G_OBJECT(menuitem), "activate",
493 G_CALLBACK(searchtype_changed),
495 MENUITEM_ADD (search_type, menuitem,
496 prefs_common_translated_header_name("From"), QUICK_SEARCH_FROM);
497 g_signal_connect(G_OBJECT(menuitem), "activate",
498 G_CALLBACK(searchtype_changed),
500 MENUITEM_ADD (search_type, menuitem,
501 prefs_common_translated_header_name("To"), QUICK_SEARCH_TO);
502 g_signal_connect(G_OBJECT(menuitem), "activate",
503 G_CALLBACK(searchtype_changed),
505 MENUITEM_ADD (search_type, menuitem,
506 prefs_common_translated_header_name("From, To or Subject"), QUICK_SEARCH_MIXED);
507 g_signal_connect(G_OBJECT(menuitem), "activate",
508 G_CALLBACK(searchtype_changed),
510 MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
511 g_signal_connect(G_OBJECT(menuitem), "activate",
512 G_CALLBACK(searchtype_changed),
515 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
517 menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
518 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
520 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
521 prefs_common.summary_quicksearch_recurse);
523 g_signal_connect(G_OBJECT(menuitem), "activate",
524 G_CALLBACK(searchtype_recursive_changed),
527 menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
528 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
530 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
531 prefs_common.summary_quicksearch_sticky);
533 g_signal_connect(G_OBJECT(menuitem), "activate",
534 G_CALLBACK(searchtype_sticky_changed),
537 menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
538 gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
540 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
541 prefs_common.summary_quicksearch_dynamic);
543 g_signal_connect(G_OBJECT(menuitem), "activate",
544 G_CALLBACK(searchtype_dynamic_changed),
547 gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
549 quicksearch->search_type_opt = search_type_opt;
550 quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
552 gtk_widget_show(search_type);
554 search_string_entry = gtk_combo_new();
555 gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
556 gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
557 gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
558 if (prefs_common.summary_quicksearch_history)
559 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
560 prefs_common.summary_quicksearch_history);
561 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
562 gtk_widget_show(search_string_entry);
564 search_hbox = gtk_hbox_new(FALSE, 5);
566 #if GTK_CHECK_VERSION(2, 8, 0)
567 clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
569 clear_search = gtk_button_new_with_label(_(" Clear "));
571 gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
573 g_signal_connect(G_OBJECT(clear_search), "clicked",
574 G_CALLBACK(clear_search_cb), quicksearch);
575 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
577 _("Clear the current search"), NULL);
578 gtk_widget_show(clear_search);
580 #if GTK_CHECK_VERSION(2, 8, 0)
581 search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
583 search_condition_expression = gtk_button_new_with_label(" ... ");
585 gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
587 g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
588 G_CALLBACK(search_condition_expr),
590 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
591 search_condition_expression,
592 _("Edit search criteria"), NULL);
593 gtk_widget_show(search_condition_expression);
595 #if GTK_CHECK_VERSION(2, 8, 0)
596 search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
598 search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
600 gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
602 g_signal_connect(G_OBJECT(search_description), "clicked",
603 G_CALLBACK(search_description_cb), NULL);
604 gtk_tooltips_set_tip(GTK_TOOLTIPS(tips),
606 _("Information about extended symbols"), NULL);
607 gtk_widget_show(search_description);
609 gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
610 gtk_widget_show(search_hbox);
612 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
614 G_CALLBACK(searchbar_pressed),
617 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
619 G_CALLBACK(searchbar_changed_cb),
622 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
624 G_CALLBACK(searchbar_focus_evt_in),
626 g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
628 G_CALLBACK(searchbar_focus_evt_out),
631 quicksearch->hbox_search = hbox_search;
632 quicksearch->search_type = search_type;
633 quicksearch->search_string_entry = search_string_entry;
634 quicksearch->search_condition_expression = search_condition_expression;
635 quicksearch->search_description = search_description;
636 quicksearch->matcher_list = NULL;
637 quicksearch->active = FALSE;
638 quicksearch->running = FALSE;
639 quicksearch->clear_search = clear_search;
640 quicksearch->in_typing = FALSE;
641 quicksearch->press_timeout_id = -1;
643 update_extended_buttons(quicksearch);
648 void quicksearch_relayout(QuickSearch *quicksearch)
650 switch (prefs_common.layout_mode) {
653 case WIDE_MSGLIST_LAYOUT:
654 #if GTK_CHECK_VERSION(2, 8, 0)
655 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO);
656 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT);
657 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR);
659 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _(" Extended Symbols... "));
660 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), " ... ");
661 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _(" Clear "));
664 case VERTICAL_LAYOUT:
665 #if GTK_CHECK_VERSION(2, 8, 0)
666 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), "");
667 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "");
668 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), "");
670 gtk_button_set_image(GTK_BUTTON(quicksearch->search_description),
671 gtk_image_new_from_stock(GTK_STOCK_INFO, GTK_ICON_SIZE_BUTTON));
672 gtk_button_set_image(GTK_BUTTON(quicksearch->search_condition_expression),
673 gtk_image_new_from_stock(GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON));
674 gtk_button_set_image(GTK_BUTTON(quicksearch->clear_search),
675 gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
677 gtk_button_set_label(GTK_BUTTON(quicksearch->search_description), _("Info"));
678 gtk_button_set_label(GTK_BUTTON(quicksearch->search_condition_expression), "...");
679 gtk_button_set_label(GTK_BUTTON(quicksearch->clear_search), _("Clear"));
685 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
687 return quicksearch->hbox_search;
690 void quicksearch_show(QuickSearch *quicksearch)
692 MainWindow *mainwin = mainwindow_get_mainwindow();
693 GtkWidget *ctree = NULL;
694 prepare_matcher(quicksearch);
695 gtk_widget_show(quicksearch->hbox_search);
696 update_extended_buttons(quicksearch);
697 gtk_widget_grab_focus(
698 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
702 if (!mainwin || !mainwin->summaryview) {
706 ctree = summary_get_main_widget(mainwin->summaryview);
708 if (ctree && mainwin->summaryview->selected)
709 gtk_ctree_node_moveto(GTK_CTREE(ctree),
710 mainwin->summaryview->selected,
714 void quicksearch_hide(QuickSearch *quicksearch)
716 if (quicksearch_is_active(quicksearch)) {
717 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
718 quicksearch_set_active(quicksearch, FALSE);
720 gtk_widget_hide(quicksearch->hbox_search);
723 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
724 const gchar *matchstring)
726 quicksearch_set_type(quicksearch, type);
728 if (!matchstring || !(*matchstring))
729 quicksearch->in_typing = FALSE;
731 g_signal_handlers_block_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
732 G_CALLBACK(searchbar_changed_cb), quicksearch);
733 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
735 g_signal_handlers_unblock_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
736 G_CALLBACK(searchbar_changed_cb), quicksearch);
738 prefs_common.summary_quicksearch_type = type;
740 prepare_matcher(quicksearch);
742 quicksearch_set_running(quicksearch, TRUE);
743 if (quicksearch->callback != NULL)
744 quicksearch->callback(quicksearch, quicksearch->callback_data);
745 quicksearch_set_running(quicksearch, FALSE);
748 gboolean quicksearch_is_active(QuickSearch *quicksearch)
750 return quicksearch->active &&
751 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
752 || quicksearch->matcher_list != NULL);
755 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
757 static GdkColor yellow;
759 static GdkColor black;
760 static gboolean colors_initialised = FALSE;
761 gboolean error = FALSE;
763 if (!colors_initialised) {
764 gdk_color_parse("#f5f6be", &yellow);
765 gdk_color_parse("#000000", &black);
766 gdk_color_parse("#ff7070", &red);
767 colors_initialised = gdk_colormap_alloc_color(
768 gdk_colormap_get_system(), &yellow, FALSE, TRUE);
769 colors_initialised &= gdk_colormap_alloc_color(
770 gdk_colormap_get_system(), &black, FALSE, TRUE);
771 colors_initialised &= gdk_colormap_alloc_color(
772 gdk_colormap_get_system(), &red, FALSE, TRUE);
775 quicksearch->active = active;
778 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
779 && quicksearch->matcher_list == NULL))
783 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
784 if (colors_initialised) {
785 gtk_widget_modify_base(
786 GTK_COMBO(quicksearch->search_string_entry)->entry,
787 GTK_STATE_NORMAL, error ? &red : &yellow);
788 gtk_widget_modify_text(
789 GTK_COMBO(quicksearch->search_string_entry)->entry,
790 GTK_STATE_NORMAL, &black);
793 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
794 if (colors_initialised) {
795 gtk_widget_modify_base(
796 GTK_COMBO(quicksearch->search_string_entry)->entry,
797 GTK_STATE_NORMAL, NULL);
798 gtk_widget_modify_text(
799 GTK_COMBO(quicksearch->search_string_entry)->entry,
800 GTK_STATE_NORMAL, NULL);
805 quicksearch_reset_cur_folder_item(quicksearch);
809 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
810 QuickSearchExecuteCallback callback,
813 quicksearch->callback = callback;
814 quicksearch->callback_data = data;
817 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
819 gchar *searched_header = NULL;
820 gboolean result = FALSE;
822 if (!quicksearch->active)
825 switch (prefs_common.summary_quicksearch_type) {
826 case QUICK_SEARCH_SUBJECT:
827 searched_header = msginfo->subject;
829 case QUICK_SEARCH_FROM:
830 searched_header = msginfo->from;
832 case QUICK_SEARCH_TO:
833 searched_header = msginfo->to;
835 case QUICK_SEARCH_MIXED:
837 case QUICK_SEARCH_EXTENDED:
840 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
843 quicksearch->matching = TRUE;
844 if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
845 prefs_common.summary_quicksearch_type != QUICK_SEARCH_MIXED &&
846 quicksearch->search_string &&
847 searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
849 else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED &&
850 quicksearch->search_string && (
851 (msginfo->to && strcasestr(msginfo->to, quicksearch->search_string) != NULL) ||
852 (msginfo->from && strcasestr(msginfo->from, quicksearch->search_string) != NULL) ||
853 (msginfo->subject && strcasestr(msginfo->subject, quicksearch->search_string) != NULL) ))
855 else if ((quicksearch->matcher_list != NULL) &&
856 matcherlist_match(quicksearch->matcher_list, msginfo))
859 quicksearch->matching = FALSE;
860 if (quicksearch->deferred_free) {
861 prepare_matcher(quicksearch);
867 /* allow Mutt-like patterns in quick search */
868 static gchar *expand_search_string(const gchar *search_string)
871 gchar term_char, save_char;
872 gchar *cmd_start, *cmd_end;
874 gchar *returnstr = NULL;
876 gboolean casesens, dontmatch;
877 /* list of allowed pattern abbreviations */
879 gchar *abbreviated; /* abbreviation */
880 gchar *command; /* actual matcher command */
881 gint numparams; /* number of params for cmd */
882 gboolean qualifier; /* do we append regexpcase */
883 gboolean quotes; /* do we need quotes */
886 { "a", "all", 0, FALSE, FALSE },
887 { "ag", "age_greater", 1, FALSE, FALSE },
888 { "al", "age_lower", 1, FALSE, FALSE },
889 { "b", "body_part", 1, TRUE, TRUE },
890 { "B", "message", 1, TRUE, TRUE },
891 { "c", "cc", 1, TRUE, TRUE },
892 { "C", "to_or_cc", 1, TRUE, TRUE },
893 { "D", "deleted", 0, FALSE, FALSE },
894 { "e", "header \"Sender\"", 1, TRUE, TRUE },
895 { "E", "execute", 1, FALSE, TRUE },
896 { "f", "from", 1, TRUE, TRUE },
897 { "F", "forwarded", 0, FALSE, FALSE },
898 { "h", "headers_part", 1, TRUE, TRUE },
899 { "i", "header \"Message-ID\"", 1, TRUE, TRUE },
900 { "I", "inreplyto", 1, TRUE, TRUE },
901 { "k", "colorlabel", 1, FALSE, FALSE },
902 { "L", "locked", 0, FALSE, FALSE },
903 { "n", "newsgroups", 1, TRUE, TRUE },
904 { "N", "new", 0, FALSE, FALSE },
905 { "O", "~new", 0, FALSE, FALSE },
906 { "r", "replied", 0, FALSE, FALSE },
907 { "R", "~unread", 0, FALSE, FALSE },
908 { "s", "subject", 1, TRUE, TRUE },
909 { "se", "score_equal", 1, FALSE, FALSE },
910 { "sg", "score_greater", 1, FALSE, FALSE },
911 { "sl", "score_lower", 1, FALSE, FALSE },
912 { "Se", "size_equal", 1, FALSE, FALSE },
913 { "Sg", "size_greater", 1, FALSE, FALSE },
914 { "Ss", "size_smaller", 1, FALSE, FALSE },
915 { "t", "to", 1, TRUE, TRUE },
916 { "T", "marked", 0, FALSE, FALSE },
917 { "U", "unread", 0, FALSE, FALSE },
918 { "x", "header \"References\"", 1, TRUE, TRUE },
919 { "X", "test", 1, FALSE, FALSE },
920 { "y", "header \"X-Label\"", 1, TRUE, TRUE },
921 { "&", "&", 0, FALSE, FALSE },
922 { "|", "|", 0, FALSE, FALSE },
923 { "p", "partial", 0, FALSE, FALSE },
924 { NULL, NULL, 0, FALSE, FALSE }
927 if (search_string == NULL)
930 copy_str = g_strdup(search_string);
932 matcherstr = g_string_sized_new(16);
933 cmd_start = copy_str;
934 while (cmd_start && *cmd_start) {
935 /* skip all white spaces */
936 while (*cmd_start && isspace((guchar)*cmd_start))
940 /* extract a command */
941 while (*cmd_end && !isspace((guchar)*cmd_end))
945 save_char = *cmd_end;
951 /* ~ and ! mean logical NOT */
952 if (*cmd_start == '~' || *cmd_start == '!')
957 /* % means case sensitive match */
958 if (*cmd_start == '%')
964 /* find matching abbreviation */
965 for (i = 0; cmds[i].command; i++) {
966 if (!strcmp(cmd_start, cmds[i].abbreviated)) {
967 /* restore character */
968 *cmd_end = save_char;
971 if (matcherstr->len > 0) {
972 g_string_append(matcherstr, " ");
975 g_string_append(matcherstr, "~");
976 g_string_append(matcherstr, cmds[i].command);
977 g_string_append(matcherstr, " ");
979 /* stop if no params required */
980 if (cmds[i].numparams == 0)
983 /* extract a parameter, allow quotes */
984 while (*cmd_end && isspace((guchar)*cmd_end))
988 if (*cmd_start == '"') {
995 /* extract actual parameter */
996 while ((*cmd_end) && (*cmd_end != term_char))
1002 save_char = *cmd_end;
1005 if (cmds[i].qualifier) {
1007 g_string_append(matcherstr, "regexp ");
1009 g_string_append(matcherstr, "regexpcase ");
1012 /* do we need to add quotes ? */
1013 if (cmds[i].quotes && term_char != '"')
1014 g_string_append(matcherstr, "\"");
1016 /* copy actual parameter */
1017 g_string_append(matcherstr, cmd_start);
1019 /* do we need to add quotes ? */
1020 if (cmds[i].quotes && term_char != '"')
1021 g_string_append(matcherstr, "\"");
1023 /* restore original character */
1024 *cmd_end = save_char;
1032 cmd_start = cmd_end;
1037 /* return search string if no match is found to allow
1038 all available filtering expressions in quicksearch */
1039 if (matcherstr->len > 0) returnstr = matcherstr->str;
1040 else returnstr = g_strdup(search_string);
1042 g_string_free(matcherstr, FALSE);
1046 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1048 quicksearch->running = run;
1051 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1053 return quicksearch->running;
1056 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1058 GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
1059 glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1061 char *str = g_strdup(gtk_entry_get_text(entry));
1068 if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1069 /* remove selection */
1070 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1071 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1072 /* refresh string */
1074 str = g_strdup(gtk_entry_get_text(entry));
1078 if (!(c = gdk_keyval_to_unicode(val))) {
1082 char_len = g_unichar_to_utf8(c, key);
1085 key[char_len] = '\0';
1086 if (curpos < g_utf8_strlen(str, -1)) {
1087 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1088 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1090 new = g_strdup_printf("%s%s%s", begin, key, end);
1091 gtk_entry_set_text(entry, new);
1094 new = g_strdup_printf("%s%s", begin, key);
1095 gtk_entry_set_text(entry, new);
1099 gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1103 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1106 GSList *msglist = NULL;
1108 gboolean result = FALSE;
1109 gint num = 0, total = 0;
1110 gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1112 statusbar_print_all(_("Searching in %s... \n"),
1113 src->path ? src->path : "(null)");
1115 msglist = folder_item_get_msg_list(src);
1116 total = src->total_msgs;
1117 folder_item_update_freeze();
1118 for (cur = msglist; cur != NULL; cur = cur->next) {
1119 MsgInfo *msg = (MsgInfo *)cur->data;
1120 statusbar_progress_all(num++,total, interval);
1121 if (quicksearch_match(quicksearch, msg)) {
1125 if (num % interval == 0)
1127 if (!quicksearch_is_active(quicksearch))
1130 folder_item_update_thaw();
1131 statusbar_progress_all(0,0,0);
1132 statusbar_pop_all();
1134 procmsg_msg_list_free(msglist);
1138 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1139 FolderView *folderview,
1140 FolderItem *folder_item)
1142 FolderItem *cur = NULL;
1143 GNode *node = folder_item->node->children;
1145 if (!prefs_common.summary_quicksearch_recurse
1146 || quicksearch->in_typing == TRUE)
1149 for (; node != NULL; node = node->next) {
1150 cur = FOLDER_ITEM(node->data);
1151 if (quicksearch_match_subfolder(quicksearch, cur)) {
1152 folderview_update_search_icon(cur, TRUE);
1154 folderview_update_search_icon(cur, FALSE);
1156 if (cur->node->children)
1157 quicksearch_search_subfolders(quicksearch,
1161 quicksearch->root_folder_item = folder_item;
1162 if (!quicksearch_is_active(quicksearch))
1163 quicksearch_reset_cur_folder_item(quicksearch);
1166 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1167 FolderItem *folder_item)
1169 FolderItem *cur = NULL;
1170 GNode *node = (folder_item && folder_item->node) ?
1171 folder_item->node->children : NULL;
1173 for (; node != NULL; node = node->next) {
1174 cur = FOLDER_ITEM(node->data);
1175 folderview_update_search_icon(cur, FALSE);
1176 if (cur->node->children)
1177 quicksearch_reset_folder_items(quicksearch,
1182 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1184 if (quicksearch->root_folder_item)
1185 quicksearch_reset_folder_items(quicksearch,
1186 quicksearch->root_folder_item);
1188 quicksearch->root_folder_item = NULL;
1191 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1193 return quicksearch->in_typing;