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