2006-10-18 [colin] 2.5.5cvs17
[claws.git] / src / gtk / quicksearch.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2004 Hiroyuki Yamamoto & the Sylpheed-Claws team
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <ctype.h>
27
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30
31 #include "utils.h"
32 #include "menu.h"
33 #include "prefs_common.h"
34 #include "description_window.h"
35 #include "matcher.h"
36 #include "matcher_parser.h"
37 #include "quicksearch.h"
38 #include "folderview.h"
39 #include "folder.h"
40 #include "prefs_matcher.h"
41 #include "sylpheed.h"
42 #include "statusbar.h"
43
44 struct _QuickSearch
45 {
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;
53
54         gboolean                         active;
55         gchar                           *search_string;
56         MatcherList                     *matcher_list;
57
58         QuickSearchExecuteCallback       callback;
59         gpointer                         callback_data;
60         gboolean                         running;
61         gboolean                         has_focus;
62         gboolean                         matching;
63         gboolean                         deferred_free;
64         FolderItem                      *root_folder_item;
65         gboolean                         is_fast;
66         gboolean                         in_typing;
67         guint                            press_timeout_id;
68 };
69
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
74 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
75 {
76         return quicksearch->is_fast;
77 }
78
79 static void prepare_matcher(QuickSearch *quicksearch)
80 {
81         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
82
83         if (search_string == NULL || search_string[0] == '\0') {
84                 quicksearch_set_active(quicksearch, FALSE);
85         }
86
87         if (quicksearch->matcher_list != NULL) {
88                 if (quicksearch->matching) {
89                         quicksearch->deferred_free = TRUE;
90                         return;
91                 }
92                 quicksearch->deferred_free = FALSE;
93                 matcherlist_free(quicksearch->matcher_list);
94                 quicksearch->matcher_list = NULL;
95         }
96
97         if (search_string == NULL || search_string[0] == '\0') {
98                 return;
99         }
100
101         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
102                 char *newstr = NULL;
103
104                 newstr = expand_search_string(search_string);
105                 if (newstr && newstr[0] != '\0') {
106                         quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
107                         g_free(newstr);
108                 } else {
109                         quicksearch->matcher_list = NULL;
110                         quicksearch_set_active(quicksearch, FALSE);
111
112                         return;
113                 }
114         } else {
115                 quicksearch->is_fast = TRUE;
116                 g_free(quicksearch->search_string);
117                 quicksearch->search_string = g_strdup(search_string);
118         }
119
120         quicksearch_set_active(quicksearch, TRUE);
121 }
122
123 static void update_extended_buttons (QuickSearch *quicksearch)
124 {
125         GtkWidget *expr_btn = quicksearch->search_condition_expression;
126         GtkWidget *ext_btn = quicksearch->search_description;
127
128         g_return_if_fail(expr_btn != NULL);
129         g_return_if_fail(ext_btn != NULL);
130
131         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
132                 gtk_widget_show(expr_btn);
133                 gtk_widget_show(ext_btn);
134         } else {
135                 gtk_widget_hide(expr_btn);
136                 gtk_widget_hide(ext_btn);
137         }
138 }
139
140 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
141                                   QuickSearch *qs)
142 {
143         qs->has_focus = TRUE;
144         return FALSE;
145 }
146
147 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
148                                   QuickSearch *qs)
149 {
150         qs->has_focus = FALSE;
151         qs->in_typing = FALSE;
152         return FALSE;
153 }
154
155 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
156 {
157         return quicksearch->has_focus;
158 }
159
160 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
161 {
162         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
163
164         /* add to history */
165         if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) {
166                 prefs_common.summary_quicksearch_history =
167                         add_history(prefs_common.summary_quicksearch_history,
168                                         search_string);
169                 gtk_combo_set_popdown_strings(GTK_COMBO(quicksearch->search_string_entry),
170                         prefs_common.summary_quicksearch_history);
171         }
172
173         prepare_matcher(quicksearch);
174         if (run_only_if_fast && !quicksearch->is_fast)
175                 return;
176         if (quicksearch->matcher_list == NULL && 
177             prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED)
178                 return;
179         quicksearch_set_running(quicksearch, TRUE);
180         if (quicksearch->callback != NULL)
181                 quicksearch->callback(quicksearch, quicksearch->callback_data);
182         quicksearch_set_running(quicksearch, FALSE);
183 }
184
185 static int searchbar_changed_timeout(void *data)
186 {
187         QuickSearch *qs = (QuickSearch *)data;
188         if (qs && prefs_common.summary_quicksearch_dynamic) {
189                 qs->in_typing = TRUE;
190                 searchbar_run(qs, TRUE);
191         }
192         return FALSE;
193 }
194
195 static gboolean searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
196 {
197         if (prefs_common.summary_quicksearch_dynamic) {
198                 if (qs->press_timeout_id != -1) {
199                         gtk_timeout_remove(qs->press_timeout_id);
200                 }
201                 qs->press_timeout_id = gtk_timeout_add(500,
202                                 searchbar_changed_timeout, qs);
203         }
204
205         return FALSE;
206 }
207
208 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
209                                   QuickSearch *quicksearch)
210 {
211         if (event != NULL && event->keyval == GDK_Escape) {
212
213                 const gchar *str;
214
215                 quicksearch->in_typing = FALSE;
216
217                 str = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
218                 g_return_val_if_fail(str != NULL, TRUE);
219
220                 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
221                 if (!*str) {
222                         quicksearch_hide(quicksearch);
223                 } else {
224                         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
225                         gtk_widget_grab_focus(
226                                         GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
227                 }
228
229                 return TRUE;
230         }
231
232         if (event != NULL && event->keyval == GDK_Return) {
233                 quicksearch->in_typing = FALSE;
234                 /* add expression to history list and exec quicksearch */
235                 searchbar_run(quicksearch, FALSE);
236
237                 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
238                 return TRUE;
239         }
240
241         return FALSE;
242 }
243
244 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
245 {
246         QuickSearch *quicksearch = (QuickSearch *)data;
247         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
248
249         prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
250                                    G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
251                                    GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
252
253         /* Show extended search description button, only when Extended is selected */
254         update_extended_buttons(quicksearch);
255
256         if (!search_string || strlen(search_string) == 0) {
257                 return TRUE;
258         }
259
260         prepare_matcher(quicksearch);
261
262         quicksearch_set_running(quicksearch, TRUE);
263         if (quicksearch->callback != NULL)
264                 quicksearch->callback(quicksearch, quicksearch->callback_data);
265         quicksearch_set_running(quicksearch, FALSE);
266         return TRUE;
267 }
268
269 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
270 {
271         QuickSearch *quicksearch = (QuickSearch *)data;
272         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
273         const gchar *search_string = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry));
274
275         prefs_common.summary_quicksearch_recurse = checked;
276
277         /* reselect the search type */
278         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
279                                     prefs_common.summary_quicksearch_type);
280
281         if (!search_string || strlen(search_string) == 0) {
282                 return TRUE;
283         }
284
285         prepare_matcher(quicksearch);
286
287         quicksearch_set_running(quicksearch, TRUE);
288         if (quicksearch->callback != NULL)
289                 quicksearch->callback(quicksearch, quicksearch->callback_data);
290         quicksearch_set_running(quicksearch, FALSE);
291         return TRUE;
292 }
293
294 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
295 {
296         QuickSearch *quicksearch = (QuickSearch *)data;
297         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
298
299         prefs_common.summary_quicksearch_sticky = checked;
300
301         /* reselect the search type */
302         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
303                                     prefs_common.summary_quicksearch_type);
304
305         return TRUE;
306 }
307
308 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
309 {
310         QuickSearch *quicksearch = (QuickSearch *)data;
311         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
312
313         prefs_common.summary_quicksearch_dynamic = checked;
314
315         /* reselect the search type */
316         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
317                                     prefs_common.summary_quicksearch_type);
318
319         return TRUE;
320 }
321
322 /*
323  * Strings describing how to use Extended Search
324  *
325  * When adding new lines, remember to put 2 strings for each line
326  */
327 static gchar *search_descr_strings[] = {
328         "a",     N_("all messages"),
329         "ag #",  N_("messages whose age is greater than #"),
330         "al #",  N_("messages whose age is less than #"),
331         "b S",   N_("messages which contain S in the message body"),
332         "B S",   N_("messages which contain S in the whole message"),
333         "c S",   N_("messages carbon-copied to S"),
334         "C S",   N_("message is either to: or cc: to S"),
335         "D",     N_("deleted messages"), /** how I can filter deleted messages **/
336         "e S",   N_("messages which contain S in the Sender field"),
337         "E S",   N_("true if execute \"S\" succeeds"),
338         "f S",   N_("messages originating from user S"),
339         "F",     N_("forwarded messages"),
340         "h S",   N_("messages which contain header S"),
341         "i S",   N_("messages which contain S in Message-ID header"),
342         "I S",   N_("messages which contain S in inreplyto header"),
343         "k #",   N_("messages which are marked with color #"),
344         "L",     N_("locked messages"),
345         "n S",   N_("messages which are in newsgroup S"),
346         "N",     N_("new messages"),
347         "O",     N_("old messages"),
348         "p",     N_("incomplete messages (not entirely downloaded)"),
349         "r",     N_("messages which have been replied to"),
350         "R",     N_("read messages"),
351         "s S",   N_("messages which contain S in subject"),
352         "se #",  N_("messages whose score is equal to #"),
353         "sg #",  N_("messages whose score is greater than #"),
354         "sl #",  N_("messages whose score is lower than #"),
355         "Se #",  N_("messages whose size is equal to #"),
356         "Sg #",  N_("messages whose size is greater than #"),
357         "Ss #",  N_("messages whose size is smaller than #"),
358         "t S",   N_("messages which have been sent to S"),
359         "T",     N_("marked messages"),
360         "U",     N_("unread messages"),
361         "x S",   N_("messages which contain S in References header"),
362         "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
363         "y S",   N_("messages which contain S in X-Label header"),
364         "",      "" ,
365         "&amp;",         N_("logical AND operator"),
366         "|",     N_("logical OR operator"),
367         "! or ~",       N_("logical NOT operator"),
368         "%",     N_("case sensitive search"),
369         "",      "" ,
370         " ",     N_("all filtering expressions are allowed"),
371         NULL,    NULL
372 };
373
374 static DescriptionWindow search_descr = {
375         NULL,
376         NULL,
377         2,
378         N_("Extended Search"),
379         N_("Extended Search allows the user to define criteria that messages must "
380            "have in order to match and be displayed in the message list.\n\n"
381            "The following symbols can be used:"),
382         search_descr_strings
383 };
384
385 static void search_description_cb(GtkWidget *widget)
386 {
387         description_window_create(&search_descr);
388 };
389
390 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
391 {
392         QuickSearch *quicksearch = (QuickSearch *)data;
393
394         if (!quicksearch->active)
395                 return TRUE;
396
397         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
398
399         return TRUE;
400 };
401
402 static void search_condition_expr_done(MatcherList * matchers)
403 {
404         gchar *str;
405
406         g_return_if_fail(
407                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
408
409         if (matchers == NULL)
410                 return;
411
412         str = matcherlist_to_string(matchers);
413
414         if (str != NULL) {
415                 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
416                                 prefs_common.summary_quicksearch_type, str);
417                 g_free(str);
418
419                 /* add expression to history list and exec quicksearch */
420                 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
421         }
422 }
423
424 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
425 {
426         const gchar * cond_str;
427         MatcherList * matchers = NULL;
428         
429         g_return_val_if_fail(
430                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
431                         FALSE);
432
433         /* re-use it the current quicksearch value if it's a condition expression,
434            otherwise ignore it silently */
435         cond_str = gtk_entry_get_text(
436                         GTK_ENTRY(GTK_COMBO(mainwindow_get_mainwindow()->summaryview->quicksearch->
437                         search_string_entry)->entry));
438         if (*cond_str != '\0') {
439                 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
440         }
441
442         prefs_matcher_open(matchers, search_condition_expr_done);
443
444         if (matchers != NULL)
445                 matcherlist_free(matchers);
446
447         return TRUE;
448 };
449
450 QuickSearch *quicksearch_new()
451 {
452         QuickSearch *quicksearch;
453
454         GtkWidget *hbox_search;
455         GtkWidget *search_type_opt;
456         GtkWidget *search_type;
457         GtkWidget *search_string_entry;
458         GtkWidget *search_hbox;
459         GtkWidget *search_description;
460         GtkWidget *clear_search;
461         GtkWidget *search_condition_expression;
462         GtkWidget *menuitem;
463         GtkTooltips *search_cond_expr_tip;
464
465         quicksearch = g_new0(QuickSearch, 1);
466
467         /* quick search */
468         hbox_search = gtk_hbox_new(FALSE, 0);
469
470         search_type_opt = gtk_option_menu_new();
471         gtk_widget_show(search_type_opt);
472         gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
473
474         search_type = gtk_menu_new();
475         MENUITEM_ADD (search_type, menuitem, _("Subject"), QUICK_SEARCH_SUBJECT);
476         g_signal_connect(G_OBJECT(menuitem), "activate",
477                          G_CALLBACK(searchtype_changed),
478                          quicksearch);
479         MENUITEM_ADD (search_type, menuitem, _("From"), QUICK_SEARCH_FROM);
480         g_signal_connect(G_OBJECT(menuitem), "activate",
481                          G_CALLBACK(searchtype_changed),
482                          quicksearch);
483         MENUITEM_ADD (search_type, menuitem, _("To"), QUICK_SEARCH_TO);
484         g_signal_connect(G_OBJECT(menuitem), "activate",
485                          G_CALLBACK(searchtype_changed),
486                          quicksearch);
487         MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
488         g_signal_connect(G_OBJECT(menuitem), "activate",
489                          G_CALLBACK(searchtype_changed),
490                          quicksearch);
491
492         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
493
494         menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
495         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
496
497         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
498                                         prefs_common.summary_quicksearch_recurse);
499
500         g_signal_connect(G_OBJECT(menuitem), "activate",
501                          G_CALLBACK(searchtype_recursive_changed),
502                          quicksearch);
503
504         menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
505         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
506
507         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
508                                         prefs_common.summary_quicksearch_sticky);
509
510         g_signal_connect(G_OBJECT(menuitem), "activate",
511                          G_CALLBACK(searchtype_sticky_changed),
512                          quicksearch);
513
514         menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
515         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
516
517         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
518                                         prefs_common.summary_quicksearch_dynamic);
519
520         g_signal_connect(G_OBJECT(menuitem), "activate",
521                          G_CALLBACK(searchtype_dynamic_changed),
522                          quicksearch);
523
524         gtk_option_menu_set_menu(GTK_OPTION_MENU(search_type_opt), search_type);
525
526         gtk_option_menu_set_history(GTK_OPTION_MENU(search_type_opt), prefs_common.summary_quicksearch_type);
527
528         gtk_widget_show(search_type);
529
530         search_string_entry = gtk_combo_new();
531         gtk_box_pack_start(GTK_BOX(hbox_search), search_string_entry, FALSE, FALSE, 2);
532         gtk_combo_set_value_in_list(GTK_COMBO(search_string_entry), FALSE, TRUE);
533         gtk_combo_set_case_sensitive(GTK_COMBO(search_string_entry), TRUE);
534         if (prefs_common.summary_quicksearch_history)
535                 gtk_combo_set_popdown_strings(GTK_COMBO(search_string_entry),
536                         prefs_common.summary_quicksearch_history);
537         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(search_string_entry)->entry), "");
538         gtk_widget_show(search_string_entry);
539
540         search_hbox = gtk_hbox_new(FALSE, 5);
541
542 #if GTK_CHECK_VERSION(2, 8, 0)
543         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
544 #else
545         clear_search = gtk_button_new_with_label(_(" Clear "));
546 #endif
547         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
548                            FALSE, FALSE, 0);
549         g_signal_connect(G_OBJECT(clear_search), "clicked",
550                          G_CALLBACK(clear_search_cb), quicksearch);
551         gtk_widget_show(clear_search);
552
553 #if GTK_CHECK_VERSION(2, 8, 0)
554         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
555 #else
556         search_condition_expression = gtk_button_new_with_label(" ... ");
557 #endif
558         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
559                            FALSE, FALSE, 0);
560         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
561                          G_CALLBACK(search_condition_expr),
562                          quicksearch);
563         search_cond_expr_tip = gtk_tooltips_new();
564         gtk_tooltips_set_tip(GTK_TOOLTIPS(search_cond_expr_tip),
565                              search_condition_expression,
566                              _("Edit search criteria"), NULL);
567         gtk_widget_show(search_condition_expression);
568
569 #if GTK_CHECK_VERSION(2, 8, 0)
570         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
571 #else
572         search_description = gtk_button_new_with_label(_(" Extended Symbols... "));
573 #endif
574         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
575                            FALSE, FALSE, 0);
576         g_signal_connect(G_OBJECT(search_description), "clicked",
577                          G_CALLBACK(search_description_cb), NULL);
578         gtk_widget_show(search_description);
579
580         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
581         gtk_widget_show(search_hbox);
582
583         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
584                            "key_press_event",
585                            G_CALLBACK(searchbar_pressed),
586                            quicksearch);
587
588         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
589                          "changed",
590                          G_CALLBACK(searchbar_changed_cb),
591                          quicksearch);
592
593         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
594                          "focus_in_event",
595                          G_CALLBACK(searchbar_focus_evt_in),
596                          quicksearch);
597         g_signal_connect(G_OBJECT(GTK_COMBO(search_string_entry)->entry),
598                          "focus_out_event",
599                          G_CALLBACK(searchbar_focus_evt_out),
600                          quicksearch);
601
602         quicksearch->hbox_search = hbox_search;
603         quicksearch->search_type = search_type;
604         quicksearch->search_type_opt = search_type_opt;
605         quicksearch->search_string_entry = search_string_entry;
606         quicksearch->search_condition_expression = search_condition_expression;
607         quicksearch->search_description = search_description;
608         quicksearch->matcher_list = NULL;
609         quicksearch->active = FALSE;
610         quicksearch->running = FALSE;
611         quicksearch->clear_search = clear_search;
612         quicksearch->in_typing = FALSE;
613         quicksearch->press_timeout_id = -1;
614
615         update_extended_buttons(quicksearch);
616
617         return quicksearch;
618 }
619
620 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
621 {
622         return quicksearch->hbox_search;
623 }
624
625 void quicksearch_show(QuickSearch *quicksearch)
626 {
627         prepare_matcher(quicksearch);
628         gtk_widget_show(quicksearch->hbox_search);
629         update_extended_buttons(quicksearch);
630         gtk_widget_grab_focus(
631                 GTK_WIDGET(GTK_COMBO(quicksearch->search_string_entry)->entry));
632 }
633
634 void quicksearch_hide(QuickSearch *quicksearch)
635 {
636         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
637         quicksearch_set_active(quicksearch, FALSE);
638         gtk_widget_hide(quicksearch->hbox_search);
639 }
640
641 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
642                      const gchar *matchstring)
643 {
644         gtk_option_menu_set_history(GTK_OPTION_MENU(quicksearch->search_type_opt),
645                                     type);
646
647         g_signal_handlers_block_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
648                         G_CALLBACK(searchbar_changed_cb), quicksearch);
649         gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry),
650                            matchstring);
651         g_signal_handlers_unblock_by_func(G_OBJECT(GTK_COMBO(quicksearch->search_string_entry)->entry),
652                         G_CALLBACK(searchbar_changed_cb), quicksearch);
653
654         prefs_common.summary_quicksearch_type = type;
655
656         prepare_matcher(quicksearch);
657
658         quicksearch_set_running(quicksearch, TRUE);
659         if (quicksearch->callback != NULL)
660                 quicksearch->callback(quicksearch, quicksearch->callback_data);
661         quicksearch_set_running(quicksearch, FALSE);
662 }
663
664 gboolean quicksearch_is_active(QuickSearch *quicksearch)
665 {
666         return quicksearch->active && 
667                 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
668                  || quicksearch->matcher_list != NULL);
669 }
670
671 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
672 {
673         static GdkColor yellow;
674         static GdkColor red;
675         static GdkColor black;
676         static gboolean colors_initialised = FALSE;
677         gboolean error = FALSE;
678
679         if (!colors_initialised) {
680                 gdk_color_parse("#f5f6be", &yellow);
681                 gdk_color_parse("#000000", &black);
682                 gdk_color_parse("#ff7070", &red);
683                 colors_initialised = gdk_colormap_alloc_color(
684                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
685                 colors_initialised &= gdk_colormap_alloc_color(
686                         gdk_colormap_get_system(), &black, FALSE, TRUE);
687                 colors_initialised &= gdk_colormap_alloc_color(
688                         gdk_colormap_get_system(), &red, FALSE, TRUE);
689         }
690
691         quicksearch->active = active;
692
693         if (active && 
694                 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
695                  && quicksearch->matcher_list == NULL))
696                 error = TRUE;
697
698         if (active) {
699                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
700                 if (colors_initialised) {
701                         gtk_widget_modify_base(
702                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
703                                 GTK_STATE_NORMAL, error ? &red : &yellow);
704                         gtk_widget_modify_text(
705                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
706                                 GTK_STATE_NORMAL, &black);
707                 }
708         } else {
709                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
710                 if (colors_initialised) {
711                         gtk_widget_modify_base(
712                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
713                                 GTK_STATE_NORMAL, NULL);
714                         gtk_widget_modify_text(
715                                 GTK_COMBO(quicksearch->search_string_entry)->entry,
716                                 GTK_STATE_NORMAL, NULL);
717                 }
718         }
719
720         if (!active) {
721                 quicksearch_reset_cur_folder_item(quicksearch);
722         }
723 }
724
725 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
726                                       QuickSearchExecuteCallback callback,
727                                       gpointer data)
728 {
729         quicksearch->callback = callback;
730         quicksearch->callback_data = data;
731 }
732
733 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
734 {
735         gchar *searched_header = NULL;
736         gboolean result = FALSE;
737
738         if (!quicksearch->active)
739                 return TRUE;
740
741         switch (prefs_common.summary_quicksearch_type) {
742         case QUICK_SEARCH_SUBJECT:
743                 searched_header = msginfo->subject;
744                 break;
745         case QUICK_SEARCH_FROM:
746                 searched_header = msginfo->from;
747                 break;
748         case QUICK_SEARCH_TO:
749                 searched_header = msginfo->to;
750                 break;
751         case QUICK_SEARCH_EXTENDED:
752                 break;
753         default:
754                 debug_print("unknown search type (%d)\n", prefs_common.summary_quicksearch_type);
755                 break;
756         }
757         quicksearch->matching = TRUE;
758         if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
759             quicksearch->search_string &&
760             searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
761                 result = TRUE;
762         else if ((quicksearch->matcher_list != NULL) &&
763                  matcherlist_match(quicksearch->matcher_list, msginfo))
764                 result = TRUE;
765
766         quicksearch->matching = FALSE;
767         if (quicksearch->deferred_free) {
768                 prepare_matcher(quicksearch);
769         }
770
771         return result;
772 }
773
774 /* allow Mutt-like patterns in quick search */
775 gchar *expand_search_string(const gchar *search_string)
776 {
777         int i = 0;
778         gchar term_char, save_char;
779         gchar *cmd_start, *cmd_end;
780         GString *matcherstr;
781         gchar *returnstr = NULL;
782         gchar *copy_str;
783         gboolean casesens, dontmatch;
784         /* list of allowed pattern abbreviations */
785         struct {
786                 gchar           *abbreviated;   /* abbreviation */
787                 gchar           *command;       /* actual matcher command */
788                 gint            numparams;      /* number of params for cmd */
789                 gboolean        qualifier;      /* do we append regexpcase */
790                 gboolean        quotes;         /* do we need quotes */
791         }
792         cmds[] = {
793                 { "a",  "all",                          0,      FALSE,  FALSE },
794                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
795                 { "al", "age_lower",                    1,      FALSE,  FALSE },
796                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
797                 { "B",  "message",                      1,      TRUE,   TRUE  },
798                 { "c",  "cc",                           1,      TRUE,   TRUE  },
799                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
800                 { "D",  "deleted",                      0,      FALSE,  FALSE },
801                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
802                 { "E",  "execute",                      1,      FALSE,  TRUE  },
803                 { "f",  "from",                         1,      TRUE,   TRUE  },
804                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
805                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
806                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
807                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
808                 { "k",  "colorlabel",                   1,      FALSE,  FALSE },
809                 { "L",  "locked",                       0,      FALSE,  FALSE },
810                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
811                 { "N",  "new",                          0,      FALSE,  FALSE },
812                 { "O",  "~new",                         0,      FALSE,  FALSE },
813                 { "r",  "replied",                      0,      FALSE,  FALSE },
814                 { "R",  "~unread",                      0,      FALSE,  FALSE },
815                 { "s",  "subject",                      1,      TRUE,   TRUE  },
816                 { "se", "score_equal",                  1,      FALSE,  FALSE },
817                 { "sg", "score_greater",                1,      FALSE,  FALSE },
818                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
819                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
820                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
821                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
822                 { "t",  "to",                           1,      TRUE,   TRUE  },
823                 { "T",  "marked",                       0,      FALSE,  FALSE },
824                 { "U",  "unread",                       0,      FALSE,  FALSE },
825                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
826                 { "X",  "test",                         1,      FALSE,  FALSE },
827                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
828                 { "&",  "&",                            0,      FALSE,  FALSE },
829                 { "|",  "|",                            0,      FALSE,  FALSE },
830                 { "p",  "partial",                      0,      FALSE,  FALSE },
831                 { NULL, NULL,                           0,      FALSE,  FALSE }
832         };
833
834         if (search_string == NULL)
835                 return NULL;
836
837         copy_str = g_strdup(search_string);
838
839         matcherstr = g_string_sized_new(16);
840         cmd_start = copy_str;
841         while (cmd_start && *cmd_start) {
842                 /* skip all white spaces */
843                 while (*cmd_start && isspace((guchar)*cmd_start))
844                         cmd_start++;
845                 cmd_end = cmd_start;
846
847                 /* extract a command */
848                 while (*cmd_end && !isspace((guchar)*cmd_end))
849                         cmd_end++;
850
851                 /* save character */
852                 save_char = *cmd_end;
853                 *cmd_end = '\0';
854
855                 dontmatch = FALSE;
856                 casesens = FALSE;
857
858                 /* ~ and ! mean logical NOT */
859                 if (*cmd_start == '~' || *cmd_start == '!')
860                 {
861                         dontmatch = TRUE;
862                         cmd_start++;
863                 }
864                 /* % means case sensitive match */
865                 if (*cmd_start == '%')
866                 {
867                         casesens = TRUE;
868                         cmd_start++;
869                 }
870
871                 /* find matching abbreviation */
872                 for (i = 0; cmds[i].command; i++) {
873                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
874                                 /* restore character */
875                                 *cmd_end = save_char;
876
877                                 /* copy command */
878                                 if (matcherstr->len > 0) {
879                                         g_string_append(matcherstr, " ");
880                                 }
881                                 if (dontmatch)
882                                         g_string_append(matcherstr, "~");
883                                 g_string_append(matcherstr, cmds[i].command);
884                                 g_string_append(matcherstr, " ");
885
886                                 /* stop if no params required */
887                                 if (cmds[i].numparams == 0)
888                                         break;
889
890                                 /* extract a parameter, allow quotes */
891                                 while (*cmd_end && isspace((guchar)*cmd_end))
892                                         cmd_end++;
893
894                                 cmd_start = cmd_end;
895                                 if (*cmd_start == '"') {
896                                         term_char = '"';
897                                         cmd_end++;
898                                 }
899                                 else
900                                         term_char = ' ';
901
902                                 /* extract actual parameter */
903                                 while ((*cmd_end) && (*cmd_end != term_char))
904                                         cmd_end++;
905
906                                 if (*cmd_end == '"')
907                                         cmd_end++;
908
909                                 save_char = *cmd_end;
910                                 *cmd_end = '\0';
911
912                                 if (cmds[i].qualifier) {
913                                         if (casesens)
914                                                 g_string_append(matcherstr, "regexp ");
915                                         else
916                                                 g_string_append(matcherstr, "regexpcase ");
917                                 }
918
919                                 /* do we need to add quotes ? */
920                                 if (cmds[i].quotes && term_char != '"')
921                                         g_string_append(matcherstr, "\"");
922
923                                 /* copy actual parameter */
924                                 g_string_append(matcherstr, cmd_start);
925
926                                 /* do we need to add quotes ? */
927                                 if (cmds[i].quotes && term_char != '"')
928                                         g_string_append(matcherstr, "\"");
929
930                                 /* restore original character */
931                                 *cmd_end = save_char;
932
933                                 break;
934                         }
935                 }
936
937                 if (*cmd_end)
938                         cmd_end++;
939                 cmd_start = cmd_end;
940         }
941
942         g_free(copy_str);
943
944         /* return search string if no match is found to allow
945            all available filtering expressions in quicksearch */
946         if (matcherstr->len > 0) returnstr = matcherstr->str;
947         else returnstr = g_strdup(search_string);
948
949         g_string_free(matcherstr, FALSE);
950         return returnstr;
951 }
952
953 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
954 {
955         quicksearch->running = run;
956 }
957
958 gboolean quicksearch_is_running(QuickSearch *quicksearch)
959 {
960         return quicksearch->running;
961 }
962
963 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
964 {
965         GtkEntry *entry = GTK_ENTRY(GTK_COMBO(quicksearch->search_string_entry)->entry);
966         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
967         guint32 c;
968         char *str = g_strdup(gtk_entry_get_text(entry));
969         char *begin = str;
970         char *end = NULL;
971         char *new = NULL;
972         char key[7] = "";
973         guint char_len = 0;
974
975         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
976                 /* remove selection */
977                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
978                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
979                 /* refresh string */
980                 g_free(str);
981                 str = g_strdup(gtk_entry_get_text(entry));
982                 begin = str;
983         }
984
985         if (!(c = gdk_keyval_to_unicode(val))) {
986                 g_free(str);
987                 return;
988         }
989         char_len = g_unichar_to_utf8(c, key);
990         if (char_len < 0)
991                 return;
992         key[char_len] = '\0';
993         if (curpos < g_utf8_strlen(str, -1)) {
994                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
995                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
996                 *stop = '\0';
997                 new = g_strdup_printf("%s%s%s", begin, key, end);
998                 gtk_entry_set_text(entry, new);
999                 g_free(end);
1000         } else {
1001                 new = g_strdup_printf("%s%s", begin, key);
1002                 gtk_entry_set_text(entry, new);
1003         }
1004         g_free(str);
1005         g_free(new);
1006         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1007
1008 }
1009
1010 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1011                                  FolderItem *src)
1012 {
1013         GSList *msglist = folder_item_get_msg_list(src);
1014         GSList *cur;
1015         gboolean result = FALSE;
1016         gint num = 0, total = src->total_msgs;
1017         gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1018
1019         statusbar_print_all(_("Searching in %s... \n"),
1020                 src->path ? src->path : "(null)");
1021         folder_item_update_freeze();
1022         for (cur = msglist; cur != NULL; cur = cur->next) {
1023                 MsgInfo *msg = (MsgInfo *)cur->data;
1024                 statusbar_progress_all(num++,total, interval);
1025                 if (quicksearch_match(quicksearch, msg)) {
1026                         procmsg_msginfo_free(msg);
1027                         result = TRUE;
1028                         break;
1029                 }
1030                 procmsg_msginfo_free(msg);
1031                 if (num % interval == 0)
1032                         GTK_EVENTS_FLUSH();
1033                 if (!quicksearch_is_active(quicksearch))
1034                         break;
1035         }
1036         folder_item_update_thaw();
1037         statusbar_progress_all(0,0,0);
1038         statusbar_pop_all();
1039
1040         g_slist_free(msglist);
1041         return result;
1042 }
1043
1044 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1045                                    FolderView *folderview,
1046                                    FolderItem *folder_item)
1047 {
1048         FolderItem *cur = NULL;
1049         GNode *node = folder_item->node->children;
1050
1051         if (!prefs_common.summary_quicksearch_recurse
1052         ||  quicksearch->in_typing == TRUE)
1053                 return;
1054
1055         for (; node != NULL; node = node->next) {
1056                 cur = FOLDER_ITEM(node->data);
1057                 if (quicksearch_match_subfolder(quicksearch, cur)) {
1058                         folderview_update_search_icon(cur, TRUE);
1059                 } else {
1060                         folderview_update_search_icon(cur, FALSE);
1061                 }
1062                 if (cur->node->children)
1063                         quicksearch_search_subfolders(quicksearch,
1064                                                       folderview,
1065                                                       cur);
1066         }
1067         quicksearch->root_folder_item = folder_item;
1068         if (!quicksearch_is_active(quicksearch))
1069                 quicksearch_reset_cur_folder_item(quicksearch);
1070 }
1071
1072 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1073                                     FolderItem *folder_item)
1074 {
1075         FolderItem *cur = NULL;
1076         GNode *node = (folder_item && folder_item->node) ?
1077                         folder_item->node->children : NULL;
1078
1079         for (; node != NULL; node = node->next) {
1080                 cur = FOLDER_ITEM(node->data);
1081                 folderview_update_search_icon(cur, FALSE);
1082                 if (cur->node->children)
1083                         quicksearch_reset_folder_items(quicksearch,
1084                                                        cur);
1085         }
1086 }
1087
1088 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1089 {
1090         if (quicksearch->root_folder_item)
1091                 quicksearch_reset_folder_items(quicksearch,
1092                                                quicksearch->root_folder_item);
1093
1094         quicksearch->root_folder_item = NULL;
1095 }
1096
1097 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1098 {
1099         return quicksearch->in_typing;
1100 }