2011-04-10 [colin] 3.7.9cvs9
[claws.git] / src / gtk / quicksearch.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2011 Colin Leroy <colin@colino.net> 
4  * and the Claws Mail team
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  * 
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #  include "config.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <ctype.h>
28
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31
32 #include "gtkcmoptionmenu.h"
33 #include "utils.h"
34 #include "combobox.h"
35 #include "menu.h"
36 #include "prefs_common.h"
37 #include "description_window.h"
38 #include "matcher.h"
39 #include "matcher_parser.h"
40 #include "quicksearch.h"
41 #include "folderview.h"
42 #include "folder.h"
43 #include "prefs_matcher.h"
44 #include "claws.h"
45 #include "statusbar.h"
46
47 struct _QuickSearchRequest
48 {
49         QuickSearchType                  type;
50         gchar                           *matchstring;
51         FolderItem                      *folderItem;
52         gboolean                         recursive;
53 };
54 typedef struct _QuickSearchRequest QuickSearchRequest;
55
56 struct _QuickSearch
57 {
58         GtkWidget                       *hbox_search;
59         GtkWidget                       *search_type;
60         GtkWidget                       *search_type_opt;
61         GtkWidget                       *search_string_entry;
62         GtkWidget                       *search_condition_expression;
63         GtkWidget                       *search_description;
64         GtkWidget                       *clear_search;
65
66         gboolean                         active;
67         gchar                           *search_string;
68         MatcherList                     *matcher_list;
69
70         QuickSearchRequest              *request;
71         QuickSearchExecuteCallback       callback;
72         gpointer                         callback_data;
73         gboolean                         running;
74         gboolean                         has_focus;
75         gboolean                         matching;
76         gboolean                         deferred_free;
77         FolderItem                      *root_folder_item;
78         gboolean                         is_fast;
79         gboolean                         in_typing;
80         guint                            press_timeout_id;
81
82         GList                           *normal_search_strings;
83         GList                           *extended_search_strings;
84         
85         /* dynamic and autorun qs settings are exclusive*/
86         GtkWidget                        *dynamic_menuitem;
87         GtkWidget                        *autorun_menuitem;
88
89         gboolean                        gui;
90 };
91
92 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
93 static void quicksearch_set_matchstring(QuickSearch *quicksearch, const gchar *matchstring);
94 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
95 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
96 static gchar *expand_search_string(const gchar *str);
97 static gchar *expand_tag_search_string(const gchar *str);
98
99 static gboolean quicksearch_from_gui(QuickSearch *quicksearch)
100 {
101         return quicksearch->gui;
102 }
103
104 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
105 {
106         return quicksearch->is_fast;
107 }
108
109 void quicksearch_set_recursive(QuickSearch *quicksearch, gboolean recursive)
110 {
111         quicksearch->request->recursive = recursive;
112 }
113
114 static void quicksearch_set_type(QuickSearch *quicksearch, gint type)
115 {
116         gint index;
117         quicksearch->request->type = type;
118         if (quicksearch->gui == FALSE)
119                 return;
120         index = menu_find_option_menu_index(GTK_CMOPTION_MENU(quicksearch->search_type_opt), 
121                                         GINT_TO_POINTER(type),
122                                         NULL);
123         gtk_cmoption_menu_set_history(GTK_CMOPTION_MENU(quicksearch->search_type_opt), index);  
124 }
125
126 static gchar *quicksearch_get_text(QuickSearch * quicksearch)
127 {
128         gchar *search_string = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))), 0, -1);
129
130         g_strstrip(search_string);
131         return search_string;
132 }
133
134 static void quicksearch_set_popdown_strings(QuickSearch *quicksearch)
135 {
136         GtkWidget *search_string_entry = quicksearch->search_string_entry;
137
138         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_string_entry));
139
140         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED)
141                 combobox_set_popdown_strings(GTK_COMBO_BOX(search_string_entry),
142                         quicksearch->extended_search_strings);  
143         else
144                 combobox_set_popdown_strings(GTK_COMBO_BOX(search_string_entry),
145                         quicksearch->normal_search_strings);
146 }
147
148 static void prepare_matcher(QuickSearch *quicksearch)
149 {
150         /* param search_string is "matchstring" */
151         const gchar *search_string;
152         QuickSearchType quicksearch_type;
153
154         if (quicksearch == NULL)
155                 return;
156
157         /* When called from the GUI, reset type and matchstring */
158         if (quicksearch_from_gui(quicksearch)) {
159                 gchar *s = quicksearch_get_text(quicksearch);
160                 quicksearch_set_matchstring(quicksearch, s);
161                 g_free(s);
162                 quicksearch->request->type = prefs_common.summary_quicksearch_type;
163         }
164         quicksearch_type = quicksearch->request->type;
165         search_string = quicksearch->request->matchstring;
166
167         if (search_string == NULL || search_string[0] == '\0') {
168                 quicksearch_set_active(quicksearch, FALSE);
169         }
170
171         if (quicksearch->matcher_list != NULL) {
172                 if (quicksearch->matching) {
173                         quicksearch->deferred_free = TRUE;
174                         return;
175                 }
176                 quicksearch->deferred_free = FALSE;
177                 matcherlist_free(quicksearch->matcher_list);
178                 quicksearch->matcher_list = NULL;
179         }
180
181         if (search_string == NULL || search_string[0] == '\0') {
182                 return;
183         }
184         if (quicksearch_type == QUICK_SEARCH_EXTENDED) {
185                 char *newstr = NULL;
186
187                 newstr = expand_search_string(search_string);
188                 if (newstr && newstr[0] != '\0') {
189                         quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
190                         g_free(newstr);
191                 } else {
192                         quicksearch->matcher_list = NULL;
193                         quicksearch_set_active(quicksearch, FALSE);
194                         return;
195                 }
196         } else if (quicksearch_type == QUICK_SEARCH_TAG) {
197                 char *newstr = expand_tag_search_string(search_string);
198                 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
199                 g_free(newstr);
200         } else if (quicksearch_type == QUICK_SEARCH_MIXED) {
201                 char *newstr = expand_tag_search_string(search_string);
202                 quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
203                 g_free(newstr);
204                 g_free(quicksearch->search_string);
205                 quicksearch->search_string = g_utf8_casefold(search_string, -1);
206         } else {
207                 quicksearch->is_fast = TRUE;
208                 g_free(quicksearch->search_string);
209                 quicksearch->search_string = g_utf8_casefold(search_string, -1);
210         }
211         quicksearch_set_active(quicksearch, TRUE);
212 }
213
214 static void update_extended_buttons (QuickSearch *quicksearch)
215 {
216         GtkWidget *expr_btn = quicksearch->search_condition_expression;
217         GtkWidget *ext_btn = quicksearch->search_description;
218
219         cm_return_if_fail(expr_btn != NULL);
220         cm_return_if_fail(ext_btn != NULL);
221
222         if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED) {
223                 gtk_widget_show(expr_btn);
224                 gtk_widget_show(ext_btn);
225         } else {
226                 gtk_widget_hide(expr_btn);
227                 gtk_widget_hide(ext_btn);
228         }
229 }
230
231 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
232                                   QuickSearch *qs)
233 {
234         qs->has_focus = TRUE;
235         return FALSE;
236 }
237
238 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
239                                   QuickSearch *qs)
240 {
241         qs->has_focus = FALSE;
242         qs->in_typing = FALSE;
243         return FALSE;
244 }
245
246 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
247 {
248         return quicksearch->has_focus;
249 }
250
251 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
252 {
253         gchar *search_string = quicksearch_get_text(quicksearch);
254         quicksearch_set_matchstring(quicksearch, search_string);
255         prepare_matcher(quicksearch);
256
257         /* add to history, for extended search add only correct matching rules */
258         if (!quicksearch->in_typing && search_string && strlen(search_string) != 0) {
259                 switch (prefs_common.summary_quicksearch_type) {
260                         case QUICK_SEARCH_EXTENDED:
261                                 if (quicksearch->matcher_list) {
262                                         quicksearch->extended_search_strings =
263                                                 add_history(quicksearch->extended_search_strings,
264                                                                 g_strdup(search_string));
265                                         prefs_common.summary_quicksearch_history =
266                                                 add_history(prefs_common.summary_quicksearch_history,
267                                                                 g_strdup(search_string));
268                                 }
269                                 break;
270                         default:
271                                 quicksearch->normal_search_strings =
272                                         add_history(quicksearch->normal_search_strings,
273                                                         g_strdup(search_string));               
274                                 prefs_common.summary_quicksearch_history =
275                                         add_history(prefs_common.summary_quicksearch_history,
276                                                         g_strdup(search_string));
277                                 break;
278                 }
279
280                 quicksearch_set_popdown_strings(quicksearch);
281
282         }
283
284         if (run_only_if_fast && !quicksearch->is_fast) {
285                 g_free(search_string);
286                 return;
287         }
288         if (quicksearch->matcher_list == NULL && 
289             prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED &&
290             search_string && strlen(search_string) != 0) {
291                 g_free(search_string);
292                 return;
293         }
294         quicksearch_set_running(quicksearch, TRUE);
295         if (quicksearch->callback != NULL)
296                 quicksearch->callback(quicksearch, quicksearch->callback_data);
297         quicksearch_set_running(quicksearch, FALSE);
298         g_free(search_string);
299 }
300
301 static int searchbar_changed_timeout(void *data)
302 {
303         QuickSearch *qs = (QuickSearch *)data;
304         if (qs && prefs_common.summary_quicksearch_dynamic) {
305                 qs->in_typing = TRUE;
306                 searchbar_run(qs, TRUE);
307         }
308         return FALSE;
309 }
310
311 static void searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
312 {
313         if (!qs->has_focus && prefs_common.summary_quicksearch_autorun) {
314                 gtk_widget_grab_focus(qs->search_string_entry);
315                 searchbar_run(qs, TRUE);
316                 return;
317         }
318
319         if (prefs_common.summary_quicksearch_dynamic) {
320                 if (qs->press_timeout_id != -1) {
321                         g_source_remove(qs->press_timeout_id);
322                 }
323                 qs->press_timeout_id = g_timeout_add(500,
324                                 searchbar_changed_timeout, qs);
325         }
326
327         if (!qs->has_focus)
328                 gtk_widget_grab_focus(qs->search_string_entry);
329 }
330
331 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
332                                   QuickSearch *quicksearch)
333 {
334         if (event && (event->keyval == GDK_Escape)) {
335                 gchar *str;
336
337                 quicksearch->in_typing = FALSE;
338
339                 str = quicksearch_get_text(quicksearch);
340                 cm_return_val_if_fail(str != NULL, TRUE);
341
342                 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
343                 if (!*str) {
344                         summaryview_activate_quicksearch(
345                                 mainwindow_get_mainwindow()->summaryview, 
346                                 FALSE);
347                 } else {
348                         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
349                         gtk_widget_grab_focus(
350                                         mainwindow_get_mainwindow()->summaryview->ctree);
351                 }
352                 g_free(str);
353                 return TRUE;
354         }
355
356         if (event != NULL && (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter)) {
357                 if (quicksearch->press_timeout_id != -1) {
358                         g_source_remove(quicksearch->press_timeout_id);
359                         quicksearch->press_timeout_id = -1;
360                 }
361                 quicksearch->in_typing = FALSE;
362                 /* add expression to history list and exec quicksearch */
363                 searchbar_run(quicksearch, FALSE);
364
365                 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
366                 return TRUE;
367         }
368
369         if (event && (event->keyval == GDK_Down || event->keyval == GDK_Up)) {
370                 combobox_set_value_from_arrow_key(
371                                 GTK_COMBO_BOX(quicksearch->search_string_entry),
372                                 event->keyval);
373                 return TRUE;
374         }
375
376         return FALSE;
377 }
378
379 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
380 {
381         QuickSearch *quicksearch = (QuickSearch *)data;
382         gchar *search_string = quicksearch_get_text(quicksearch);
383         quicksearch_set_matchstring(quicksearch, search_string);
384
385         prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
386                                    G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
387                                    GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
388
389         /* Show extended search description button, only when Extended is selected */
390         update_extended_buttons(quicksearch);
391         quicksearch_set_popdown_strings(quicksearch);
392
393         if (!search_string || *(search_string) == 0) {
394                 g_free(search_string);
395                 return TRUE;
396         }
397
398         prepare_matcher(quicksearch);
399
400         quicksearch_set_running(quicksearch, TRUE);
401         if (quicksearch->callback != NULL)
402                 quicksearch->callback(quicksearch, quicksearch->callback_data);
403         quicksearch_set_running(quicksearch, FALSE);
404         g_free(search_string);
405         return TRUE;
406 }
407
408 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
409 {
410         QuickSearch *quicksearch = (QuickSearch *)data;
411         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
412         gchar *search_string = quicksearch_get_text(quicksearch);
413         /* not needed to quicksearch_set_matchstring(search_string);
414            wait for prepare_matcher() */
415
416         prefs_common.summary_quicksearch_recurse = checked;
417         quicksearch_set_recursive(quicksearch, checked);
418
419         /* reselect the search type */
420         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
421
422         if (!search_string || *(search_string) == 0) {
423                 g_free(search_string);
424                 return TRUE;
425         }
426
427         prepare_matcher(quicksearch);
428
429         quicksearch_set_running(quicksearch, TRUE);
430         if (quicksearch->callback != NULL)
431                 quicksearch->callback(quicksearch, quicksearch->callback_data);
432         quicksearch_set_running(quicksearch, FALSE);
433         g_free(search_string);
434         return TRUE;
435 }
436
437 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
438 {
439         QuickSearch *quicksearch = (QuickSearch *)data;
440         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
441
442         prefs_common.summary_quicksearch_sticky = checked;
443
444         /* reselect the search type */
445         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
446
447         return TRUE;
448 }
449
450 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
451 {
452         QuickSearch *quicksearch = (QuickSearch *)data;
453         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
454
455         prefs_common.summary_quicksearch_dynamic = checked;
456         if (checked)
457                 gtk_check_menu_item_set_active(
458                                 GTK_CHECK_MENU_ITEM(quicksearch->autorun_menuitem),
459                                 FALSE);
460
461         /* reselect the search type */
462         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
463
464         return TRUE;
465 }
466
467 static gboolean searchtype_autorun_changed(GtkMenuItem *widget, gpointer data)
468 {
469         QuickSearch *quicksearch = (QuickSearch *)data;
470         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
471
472         prefs_common.summary_quicksearch_autorun = checked;
473         if (checked)
474                 gtk_check_menu_item_set_active(
475                                 GTK_CHECK_MENU_ITEM(quicksearch->dynamic_menuitem),
476                                 FALSE);
477
478         /* reselect the search type */
479         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
480
481         return TRUE;
482 }
483
484 /*
485  * Strings describing how to use Extended Search
486  *
487  * When adding new lines, remember to put 2 strings for each line
488  */
489 static gchar *search_descr_strings[] = {
490         "a",     N_("all messages"),
491         "ag #",  N_("messages whose age is greater than #"),
492         "al #",  N_("messages whose age is less than #"),
493         "b S",   N_("messages which contain S in the message body"),
494         "B S",   N_("messages which contain S in the whole message"),
495         "c S",   N_("messages carbon-copied to S"),
496         "C S",   N_("message is either to: or cc: to S"),
497         "D",     N_("deleted messages"), /** how I can filter deleted messages **/
498         "e S",   N_("messages which contain S in the Sender field"),
499         "E S",   N_("true if execute \"S\" succeeds"),
500         "f S",   N_("messages originating from user S"),
501         "F",     N_("forwarded messages"),
502         "h S",   N_("messages which contain header S"),
503         "i S",   N_("messages which contain S in Message-ID header"),
504         "I S",   N_("messages which contain S in In-Reply-To header"),
505         "k #",   N_("messages which are marked with color #"),
506         "L",     N_("locked messages"),
507         "n S",   N_("messages which are in newsgroup S"),
508         "N",     N_("new messages"),
509         "O",     N_("old messages"),
510         "p",     N_("incomplete messages (not entirely downloaded)"),
511         "r",     N_("messages which have been replied to"),
512         "R",     N_("read messages"),
513         "s S",   N_("messages which contain S in subject"),
514         "se #",  N_("messages whose score is equal to #"),
515         "sg #",  N_("messages whose score is greater than #"),
516         "sl #",  N_("messages whose score is lower than #"),
517         "Se #",  N_("messages whose size is equal to #"),
518         "Sg #",  N_("messages whose size is greater than #"),
519         "Ss #",  N_("messages whose size is smaller than #"),
520         "t S",   N_("messages which have been sent to S"),
521         "tg S",  N_("messages which tags contain S"),
522         "tagged",N_("messages which have tag(s)"),
523         "T",     N_("marked messages"),
524         "U",     N_("unread messages"),
525         "x S",   N_("messages which contain S in References header"),
526         "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
527         "y S",   N_("messages which contain S in X-Label header"),
528         "",      "" ,
529         "&amp;",         N_("logical AND operator"),
530         "|",     N_("logical OR operator"),
531         "! or ~",       N_("logical NOT operator"),
532         "%",     N_("case sensitive search"),
533         "",      "" ,
534         " ",     N_("all filtering expressions are allowed"),
535         NULL,    NULL
536 };
537
538 static DescriptionWindow search_descr = {
539         NULL,
540         NULL,
541         2,
542         N_("Extended Search"),
543         N_("Extended Search allows the user to define criteria that messages must "
544            "have in order to match and be displayed in the message list.\n"
545            "The following symbols can be used:"),
546         search_descr_strings
547 };
548
549 static void search_description_cb(GtkWidget *widget)
550 {
551         description_window_create(&search_descr);
552 };
553
554 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
555 {
556         QuickSearch *quicksearch = (QuickSearch *)data;
557
558         if (!quicksearch->active)
559                 return TRUE;
560
561         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
562
563         return TRUE;
564 };
565
566 static void search_condition_expr_done(MatcherList * matchers)
567 {
568         gchar *str;
569
570         cm_return_if_fail(
571                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
572
573         if (matchers == NULL)
574                 return;
575
576         str = matcherlist_to_string(matchers);
577
578         if (str != NULL) {
579                 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
580                                 prefs_common.summary_quicksearch_type, str);
581                 g_free(str);
582
583                 /* add expression to history list and exec quicksearch */
584                 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
585         }
586 }
587
588 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
589 {
590         gchar * cond_str;
591         MatcherList * matchers = NULL;
592         
593         cm_return_val_if_fail(
594                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
595                         FALSE);
596
597         /* re-use the current quicksearch value if it's a condition expression,
598            otherwise ignore it silently */
599         cond_str = quicksearch_get_text(mainwindow_get_mainwindow()->summaryview->quicksearch);
600
601         if (*cond_str != '\0') {
602                 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
603         }
604
605         prefs_matcher_open(matchers, search_condition_expr_done);
606
607         if (matchers != NULL)
608                 matcherlist_free(matchers);
609
610         g_free(cond_str);
611
612         return TRUE;
613 };
614
615 static void quicksearch_set_button(GtkButton *button, const gchar *icon, const gchar *text)
616 {
617         GList *children = gtk_container_get_children(GTK_CONTAINER(button));
618         GList *cur;
619         GtkWidget *box;
620         gboolean icon_visible;
621
622         g_object_get(gtk_settings_get_default(), 
623                                          "gtk-button-images", &icon_visible, 
624                                          NULL);
625
626         for (cur = children; cur; cur = cur->next)
627                 gtk_container_remove(GTK_CONTAINER(button), GTK_WIDGET(cur->data));
628         
629         g_list_free(children);
630         box = gtk_hbox_new(FALSE, 0);
631         
632         gtk_container_add(GTK_CONTAINER(button), box);
633         if (icon_visible || !text || !*text)
634                 gtk_box_pack_start(GTK_BOX(box), gtk_image_new_from_stock(icon, 
635                         GTK_ICON_SIZE_BUTTON), FALSE, FALSE, 0);
636         gtk_box_pack_start(GTK_BOX(box), gtk_label_new_with_mnemonic(text), FALSE, FALSE, 0);
637         gtk_widget_show_all(box);
638 }
639
640 /*
641  * Builds a new QuickSearchRequest
642  */
643 static QuickSearchRequest *quicksearchrequest_new(void)
644 {
645         QuickSearchRequest *request;
646         request = g_new0(QuickSearchRequest, 1);
647         return request;
648 }
649
650 /*
651  * Builds a new QuickSearch object independent from the GUI
652  */
653 QuickSearch *quicksearch_new_nogui(void)
654 {
655         QuickSearch *quicksearch;
656         QuickSearchRequest *request;
657
658         request = quicksearchrequest_new();
659         quicksearch = g_new0(QuickSearch, 1);
660         quicksearch->request = request;
661         quicksearch->gui = FALSE;
662
663         /* init. values initally found in quicksearch_new().
664            There's no need to init. all pointers to NULL since we use g_new0
665          */
666         quicksearch->matcher_list = NULL;
667         quicksearch->active = FALSE;
668         quicksearch->running = FALSE;
669         quicksearch->in_typing = FALSE;
670         quicksearch->press_timeout_id = -1;
671         quicksearch->normal_search_strings = NULL;
672         quicksearch->extended_search_strings = NULL;
673
674         return quicksearch;
675 }
676
677 QuickSearch *quicksearch_new()
678 {
679         QuickSearch *quicksearch;
680
681         GtkWidget *hbox_search;
682         GtkWidget *search_type_opt;
683         GtkWidget *search_type;
684         GtkWidget *search_string_entry;
685         GtkWidget *search_hbox;
686         GtkWidget *search_description;
687         GtkWidget *clear_search;
688         GtkWidget *search_condition_expression;
689         GtkWidget *menuitem;
690         CLAWS_TIP_DECL();
691         GtkWidget *vbox;
692
693         quicksearch = quicksearch_new_nogui();
694         quicksearch->gui = TRUE;
695
696         /* quick search */
697         hbox_search = gtk_hbox_new(FALSE, 0);
698
699         search_type_opt = gtk_cmoption_menu_new();
700         gtk_widget_show(search_type_opt);
701         gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
702
703         search_type = gtk_menu_new();
704         MENUITEM_ADD (search_type, menuitem,
705                         prefs_common_translated_header_name("Subject"), QUICK_SEARCH_SUBJECT);
706         g_signal_connect(G_OBJECT(menuitem), "activate",
707                          G_CALLBACK(searchtype_changed),
708                          quicksearch);
709         MENUITEM_ADD (search_type, menuitem,
710                         prefs_common_translated_header_name("From"), QUICK_SEARCH_FROM);
711         g_signal_connect(G_OBJECT(menuitem), "activate",
712                          G_CALLBACK(searchtype_changed),
713                          quicksearch);
714         MENUITEM_ADD (search_type, menuitem,
715                         prefs_common_translated_header_name("To"), QUICK_SEARCH_TO);
716         g_signal_connect(G_OBJECT(menuitem), "activate",
717                          G_CALLBACK(searchtype_changed),
718                          quicksearch);
719         MENUITEM_ADD (search_type, menuitem,
720                         prefs_common_translated_header_name("Tag"), QUICK_SEARCH_TAG);
721         g_signal_connect(G_OBJECT(menuitem), "activate",
722                          G_CALLBACK(searchtype_changed),
723                          quicksearch);
724         MENUITEM_ADD (search_type, menuitem,
725                         _("From/To/Subject/Tag"), QUICK_SEARCH_MIXED);
726         g_signal_connect(G_OBJECT(menuitem), "activate",
727                          G_CALLBACK(searchtype_changed),
728                          quicksearch);
729         MENUITEM_ADD (search_type, menuitem, _("Extended"), QUICK_SEARCH_EXTENDED);
730         g_signal_connect(G_OBJECT(menuitem), "activate",
731                          G_CALLBACK(searchtype_changed),
732                          quicksearch);
733
734         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
735
736         menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
737         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
738
739         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
740                                         prefs_common.summary_quicksearch_recurse);
741         quicksearch_set_recursive(quicksearch, prefs_common.summary_quicksearch_recurse);
742         g_signal_connect(G_OBJECT(menuitem), "activate",
743                          G_CALLBACK(searchtype_recursive_changed),
744                          quicksearch);
745
746         menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
747         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
748
749         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
750                                         prefs_common.summary_quicksearch_sticky);
751
752         g_signal_connect(G_OBJECT(menuitem), "activate",
753                          G_CALLBACK(searchtype_sticky_changed),
754                          quicksearch);
755
756         menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
757         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
758
759         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
760                                         prefs_common.summary_quicksearch_dynamic);
761
762         quicksearch->dynamic_menuitem = menuitem;
763
764         g_signal_connect(G_OBJECT(menuitem), "activate",
765                          G_CALLBACK(searchtype_dynamic_changed),
766                          quicksearch);
767
768         menuitem = gtk_check_menu_item_new_with_label(_("Run on select"));
769         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
770
771         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
772                                         prefs_common.summary_quicksearch_autorun);
773
774         quicksearch->autorun_menuitem = menuitem;
775
776         g_signal_connect(G_OBJECT(menuitem), "activate",
777                          G_CALLBACK(searchtype_autorun_changed),
778                          quicksearch);
779
780         gtk_cmoption_menu_set_menu(GTK_CMOPTION_MENU(search_type_opt), search_type);
781
782         quicksearch->search_type_opt = search_type_opt;
783         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
784
785         gtk_widget_show(search_type);
786
787         search_string_entry = gtk_combo_box_new_with_entry ();
788         gtk_combo_box_set_active(GTK_COMBO_BOX(search_string_entry), -1);
789
790         vbox = gtk_vbox_new(TRUE, 0);
791         gtk_box_pack_start(GTK_BOX(vbox), search_string_entry, FALSE, FALSE, 0);
792         gtk_box_pack_start(GTK_BOX(hbox_search), vbox, TRUE, TRUE, 4);
793
794         gtk_widget_show(vbox);
795         gtk_widget_show(search_string_entry);
796
797         search_hbox = gtk_hbox_new(FALSE, 5);
798         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
799         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
800                            FALSE, FALSE, 0);
801         g_signal_connect(G_OBJECT(clear_search), "clicked",
802                          G_CALLBACK(clear_search_cb), quicksearch);
803         CLAWS_SET_TIP(clear_search,
804                              _("Clear the current search"));
805         gtk_widget_show(clear_search);
806
807         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
808         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
809                            FALSE, FALSE, 0);
810         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
811                          G_CALLBACK(search_condition_expr),
812                          quicksearch);
813         CLAWS_SET_TIP(search_condition_expression,
814                              _("Edit search criteria"));
815         gtk_widget_show(search_condition_expression);
816
817         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
818         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
819                            FALSE, FALSE, 0);
820         g_signal_connect(G_OBJECT(search_description), "clicked",
821                          G_CALLBACK(search_description_cb), NULL);
822         CLAWS_SET_TIP(search_description,
823                              _("Information about extended symbols"));
824         gtk_widget_show(search_description);
825
826         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
827         gtk_widget_show(search_hbox);
828
829         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
830                            "key_press_event",
831                            G_CALLBACK(searchbar_pressed),
832                            quicksearch);
833
834         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
835                          "changed",
836                          G_CALLBACK(searchbar_changed_cb),
837                          quicksearch);
838
839         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
840                          "focus_in_event",
841                          G_CALLBACK(searchbar_focus_evt_in),
842                          quicksearch);
843         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
844                          "focus_out_event",
845                          G_CALLBACK(searchbar_focus_evt_out),
846                          quicksearch);
847
848         quicksearch->hbox_search = hbox_search;
849         quicksearch->search_type = search_type;
850         quicksearch->search_string_entry = search_string_entry;
851         quicksearch->search_condition_expression = search_condition_expression;
852         quicksearch->search_description = search_description;
853         quicksearch->matcher_list = NULL;
854         quicksearch->active = FALSE;
855         quicksearch->running = FALSE;
856         quicksearch->clear_search = clear_search;
857         quicksearch->in_typing = FALSE;
858         quicksearch->press_timeout_id = -1;
859         quicksearch->normal_search_strings = NULL;
860         quicksearch->extended_search_strings = NULL;
861
862         quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, _("_Information"));
863         quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, _("_Edit"));
864         quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, _("_Clear"));
865         
866         update_extended_buttons(quicksearch);
867
868         return quicksearch;
869 }
870
871 void quicksearch_relayout(QuickSearch *quicksearch)
872 {
873         switch (prefs_common.layout_mode) {
874         case NORMAL_LAYOUT:
875         case WIDE_LAYOUT:
876         case WIDE_MSGLIST_LAYOUT:
877                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, _("_Information"));
878                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, _("_Edit"));
879                 quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, _("_Clear"));
880                 break;
881         case SMALL_LAYOUT:
882         case VERTICAL_LAYOUT:
883                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, "");
884                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, "");
885                 quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, "");
886                 break;
887         }
888 }
889
890 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
891 {
892         return quicksearch->hbox_search;
893 }
894
895 void quicksearch_show(QuickSearch *quicksearch)
896 {
897         MainWindow *mainwin = mainwindow_get_mainwindow();
898         GtkWidget *ctree = NULL;
899         prepare_matcher(quicksearch);
900         gtk_widget_show(quicksearch->hbox_search);
901         update_extended_buttons(quicksearch);
902         gtk_widget_grab_focus(
903                 GTK_WIDGET(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))));
904
905         GTK_EVENTS_FLUSH();
906
907         if (!mainwin || !mainwin->summaryview) {
908                 return;
909         }
910         
911         ctree = summary_get_main_widget(mainwin->summaryview);
912         
913         if (ctree && mainwin->summaryview->selected)
914                 gtk_cmctree_node_moveto(GTK_CMCTREE(ctree), 
915                                 mainwin->summaryview->selected, 
916                                 0, 0.5, 0);
917 }
918
919 void quicksearch_hide(QuickSearch *quicksearch)
920 {
921         if (quicksearch_is_active(quicksearch)) {
922                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
923                 quicksearch_set_active(quicksearch, FALSE);
924         }
925         gtk_widget_hide(quicksearch->hbox_search);
926 }
927
928 /*
929  *\brief        Sets the matchstring.
930  *
931  *\param        quicksearch quicksearch to set
932  *\param        matchstring the match string; it is duplicated, not stored
933  */
934 static void quicksearch_set_matchstring(QuickSearch *quicksearch,
935                                         const gchar *matchstring)
936 {
937         g_free(quicksearch->request->matchstring);
938         quicksearch->request->matchstring = g_strdup(matchstring);
939 }
940
941 void quicksearch_set(QuickSearch *quicksearch, QuickSearchType type,
942                      const gchar *matchstring)
943 {
944         quicksearch_set_type(quicksearch, type);
945
946         if (!matchstring || !(*matchstring))
947                 quicksearch->in_typing = FALSE;
948
949         quicksearch_set_matchstring(quicksearch, matchstring);
950
951         if (!quicksearch_from_gui(quicksearch)) {
952                 prepare_matcher(quicksearch);
953                 /* no callback */
954                 return;
955         }
956                 
957         g_signal_handlers_block_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
958                                         G_CALLBACK(searchbar_changed_cb), quicksearch);
959         gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
960                            matchstring);
961         g_signal_handlers_unblock_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
962                                           G_CALLBACK(searchbar_changed_cb), quicksearch);
963
964         prefs_common.summary_quicksearch_type = type;
965
966         prepare_matcher(quicksearch);
967
968         quicksearch_set_running(quicksearch, TRUE);
969         if (quicksearch->callback != NULL)
970                 quicksearch->callback(quicksearch, quicksearch->callback_data);
971         quicksearch_set_running(quicksearch, FALSE);
972 }
973
974 gboolean quicksearch_is_active(QuickSearch *quicksearch)
975 {
976         return quicksearch->active && 
977                 (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED
978                  || quicksearch->matcher_list != NULL);
979 }
980
981 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
982 {
983         static GdkColor yellow;
984         static GdkColor red;
985         static GdkColor black;
986         static gboolean colors_initialised = FALSE;
987         gboolean error = FALSE;
988
989         
990         quicksearch->active = active;
991         if (quicksearch->gui == FALSE)
992                 return;
993
994         if (!colors_initialised) {
995                 gdk_color_parse("#f5f6be", &yellow);
996                 gdk_color_parse("#000000", &black);
997                 gdk_color_parse("#ff7070", &red);
998                 colors_initialised = gdk_colormap_alloc_color(
999                         gdk_colormap_get_system(), &yellow, FALSE, TRUE);
1000                 colors_initialised &= gdk_colormap_alloc_color(
1001                         gdk_colormap_get_system(), &black, FALSE, TRUE);
1002                 colors_initialised &= gdk_colormap_alloc_color(
1003                         gdk_colormap_get_system(), &red, FALSE, TRUE);
1004         }
1005
1006         if (active && 
1007                 (prefs_common.summary_quicksearch_type == QUICK_SEARCH_EXTENDED
1008                  && quicksearch->matcher_list == NULL))
1009                 error = TRUE;
1010
1011         if (active) {
1012                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
1013                 if (colors_initialised) {
1014                         gtk_widget_modify_base(
1015                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
1016                                 GTK_STATE_NORMAL, error ? &red : &yellow);
1017                         gtk_widget_modify_text(
1018                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
1019                                 GTK_STATE_NORMAL, &black);
1020                 }
1021         } else {
1022                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
1023                 if (colors_initialised) {
1024                         gtk_widget_modify_base(
1025                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
1026                                 GTK_STATE_NORMAL, NULL);
1027                         gtk_widget_modify_text(
1028                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
1029                                 GTK_STATE_NORMAL, NULL);
1030                 }
1031         }
1032
1033         if (!active) {
1034                 quicksearch_reset_cur_folder_item(quicksearch);
1035         }
1036 }
1037
1038 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
1039                                       QuickSearchExecuteCallback callback,
1040                                       gpointer data)
1041 {
1042         quicksearch->callback = callback;
1043         quicksearch->callback_data = data;
1044 }
1045
1046 gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
1047 {
1048         gchar *searched_header = NULL;
1049         gboolean result = FALSE;
1050         gchar *to = NULL, *from = NULL, *subject = NULL;
1051         QuickSearchType quicksearch_type;
1052
1053         if (!quicksearch->active)
1054                 return TRUE;
1055
1056         quicksearch_type = quicksearch->request->type;
1057
1058         switch (quicksearch_type) {
1059         case QUICK_SEARCH_SUBJECT:
1060                 if (msginfo->subject)
1061                         searched_header = g_utf8_casefold(msginfo->subject, -1);
1062                 else
1063                         return FALSE;
1064                 break;
1065         case QUICK_SEARCH_FROM:
1066                 if (msginfo->from)
1067                         searched_header = g_utf8_casefold(msginfo->from, -1);
1068                 else
1069                         return FALSE;
1070                 break;
1071         case QUICK_SEARCH_TO:
1072                 if (msginfo->to)
1073                         searched_header = g_utf8_casefold(msginfo->to, -1);
1074                 else
1075                         return FALSE;
1076                 break;
1077         case QUICK_SEARCH_MIXED:
1078                 if (msginfo->to)
1079                         to = g_utf8_casefold(msginfo->to, -1);
1080                 if (msginfo->from)
1081                         from = g_utf8_casefold(msginfo->from, -1);
1082                 if (msginfo->subject)
1083                         subject = g_utf8_casefold(msginfo->subject, -1);
1084                 break;
1085         case QUICK_SEARCH_EXTENDED:
1086                 break;
1087         default:
1088                 debug_print("unknown search type (%d)\n", quicksearch_type);
1089                 break;
1090         }
1091
1092         quicksearch->matching = TRUE;
1093         if (quicksearch_type != QUICK_SEARCH_EXTENDED &&
1094             quicksearch_type != QUICK_SEARCH_MIXED &&
1095             quicksearch_type != QUICK_SEARCH_TAG &&
1096             quicksearch->search_string &&
1097             searched_header && strstr(searched_header, quicksearch->search_string) != NULL)
1098                 result = TRUE;
1099         else if (quicksearch_type == QUICK_SEARCH_MIXED &&
1100                 quicksearch->search_string && (
1101                 (to && strstr(to, quicksearch->search_string) != NULL) ||
1102                 (from && strstr(from, quicksearch->search_string) != NULL) ||
1103                 (subject && strstr(subject, quicksearch->search_string) != NULL) ||
1104                 ((quicksearch->matcher_list != NULL) &&
1105                  matcherlist_match(quicksearch->matcher_list, msginfo))  ))
1106                 result = TRUE;
1107         else if ((quicksearch->matcher_list != NULL) &&
1108                  matcherlist_match(quicksearch->matcher_list, msginfo))
1109                 result = TRUE;
1110
1111         quicksearch->matching = FALSE;
1112         if (quicksearch_from_gui(quicksearch)==TRUE && quicksearch->deferred_free) {
1113                 /* Ref. http://lists.claws-mail.org/pipermail/users/2010-August/003063.html
1114                    See also 2.0.0cvs140 ChangeLog entry
1115                    and comment in search_msgs_in_folder() */
1116                 prepare_matcher(quicksearch);
1117         }
1118
1119         g_free(to);
1120         g_free(from);
1121         g_free(subject);
1122         g_free(searched_header);
1123
1124         return result;
1125 }
1126
1127 /* allow Mutt-like patterns in quick search */
1128 static gchar *expand_search_string(const gchar *search_string)
1129 {
1130         int i = 0;
1131         gchar term_char, save_char;
1132         gchar *cmd_start, *cmd_end;
1133         GString *matcherstr;
1134         gchar *returnstr = NULL;
1135         gchar *copy_str;
1136         gboolean casesens, dontmatch;
1137         /* list of allowed pattern abbreviations */
1138         struct {
1139                 gchar           *abbreviated;   /* abbreviation */
1140                 gchar           *command;       /* actual matcher command */
1141                 gint            numparams;      /* number of params for cmd */
1142                 gboolean        qualifier;      /* do we append regexpcase */
1143                 gboolean        quotes;         /* do we need quotes */
1144         }
1145         cmds[] = {
1146                 { "a",  "all",                          0,      FALSE,  FALSE },
1147                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
1148                 { "al", "age_lower",                    1,      FALSE,  FALSE },
1149                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
1150                 { "B",  "message",                      1,      TRUE,   TRUE  },
1151                 { "c",  "cc",                           1,      TRUE,   TRUE  },
1152                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
1153                 { "D",  "deleted",                      0,      FALSE,  FALSE },
1154                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
1155                 { "E",  "execute",                      1,      FALSE,  TRUE  },
1156                 { "f",  "from",                         1,      TRUE,   TRUE  },
1157                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
1158                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
1159                 { "i",  "header \"Message-ID\"",        1,      TRUE,   TRUE  },
1160                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
1161                 { "k",  "colorlabel",                   1,      FALSE,  FALSE },
1162                 { "L",  "locked",                       0,      FALSE,  FALSE },
1163                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
1164                 { "N",  "new",                          0,      FALSE,  FALSE },
1165                 { "O",  "~new",                         0,      FALSE,  FALSE },
1166                 { "r",  "replied",                      0,      FALSE,  FALSE },
1167                 { "R",  "~unread",                      0,      FALSE,  FALSE },
1168                 { "s",  "subject",                      1,      TRUE,   TRUE  },
1169                 { "se", "score_equal",                  1,      FALSE,  FALSE },
1170                 { "sg", "score_greater",                1,      FALSE,  FALSE },
1171                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
1172                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
1173                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
1174                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
1175                 { "t",  "to",                           1,      TRUE,   TRUE  },
1176                 { "tg", "tag",                          1,      TRUE,   TRUE  },
1177                 { "T",  "marked",                       0,      FALSE,  FALSE },
1178                 { "U",  "unread",                       0,      FALSE,  FALSE },
1179                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
1180                 { "X",  "test",                         1,      FALSE,  FALSE },
1181                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
1182                 { "&",  "&",                            0,      FALSE,  FALSE },
1183                 { "|",  "|",                            0,      FALSE,  FALSE },
1184                 { "p",  "partial",                      0,      FALSE,  FALSE },
1185                 { NULL, NULL,                           0,      FALSE,  FALSE }
1186         };
1187
1188         if (search_string == NULL)
1189                 return NULL;
1190
1191         copy_str = g_strdup(search_string);
1192
1193         matcherstr = g_string_sized_new(16);
1194         cmd_start = copy_str;
1195         while (cmd_start && *cmd_start) {
1196                 /* skip all white spaces */
1197                 while (*cmd_start && isspace((guchar)*cmd_start))
1198                         cmd_start++;
1199                 cmd_end = cmd_start;
1200
1201                 /* extract a command */
1202                 while (*cmd_end && !isspace((guchar)*cmd_end))
1203                         cmd_end++;
1204
1205                 /* save character */
1206                 save_char = *cmd_end;
1207                 *cmd_end = '\0';
1208
1209                 dontmatch = FALSE;
1210                 casesens = FALSE;
1211
1212                 /* ~ and ! mean logical NOT */
1213                 if (*cmd_start == '~' || *cmd_start == '!')
1214                 {
1215                         dontmatch = TRUE;
1216                         cmd_start++;
1217                 }
1218                 /* % means case sensitive match */
1219                 if (*cmd_start == '%')
1220                 {
1221                         casesens = TRUE;
1222                         cmd_start++;
1223                 }
1224
1225                 /* find matching abbreviation */
1226                 for (i = 0; cmds[i].command; i++) {
1227                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
1228                                 /* restore character */
1229                                 *cmd_end = save_char;
1230
1231                                 /* copy command */
1232                                 if (matcherstr->len > 0) {
1233                                         g_string_append(matcherstr, " ");
1234                                 }
1235                                 if (dontmatch)
1236                                         g_string_append(matcherstr, "~");
1237                                 g_string_append(matcherstr, cmds[i].command);
1238                                 g_string_append(matcherstr, " ");
1239
1240                                 /* stop if no params required */
1241                                 if (cmds[i].numparams == 0)
1242                                         break;
1243
1244                                 /* extract a parameter, allow quotes */
1245                                 while (*cmd_end && isspace((guchar)*cmd_end))
1246                                         cmd_end++;
1247
1248                                 cmd_start = cmd_end;
1249                                 if (*cmd_start == '"') {
1250                                         term_char = '"';
1251                                         cmd_end++;
1252                                 }
1253                                 else
1254                                         term_char = ' ';
1255
1256                                 /* extract actual parameter */
1257                                 while ((*cmd_end) && (*cmd_end != term_char))
1258                                         cmd_end++;
1259
1260                                 if (*cmd_end == '"')
1261                                         cmd_end++;
1262
1263                                 save_char = *cmd_end;
1264                                 *cmd_end = '\0';
1265
1266                                 if (cmds[i].qualifier) {
1267                                         if (casesens)
1268                                                 g_string_append(matcherstr, "regexp ");
1269                                         else
1270                                                 g_string_append(matcherstr, "regexpcase ");
1271                                 }
1272
1273                                 /* do we need to add quotes ? */
1274                                 if (cmds[i].quotes && term_char != '"')
1275                                         g_string_append(matcherstr, "\"");
1276
1277                                 /* copy actual parameter */
1278                                 g_string_append(matcherstr, cmd_start);
1279
1280                                 /* do we need to add quotes ? */
1281                                 if (cmds[i].quotes && term_char != '"')
1282                                         g_string_append(matcherstr, "\"");
1283
1284                                 /* restore original character */
1285                                 *cmd_end = save_char;
1286
1287                                 break;
1288                         }
1289                 }
1290
1291                 if (*cmd_end)
1292                         cmd_end++;
1293                 cmd_start = cmd_end;
1294         }
1295
1296         g_free(copy_str);
1297
1298         /* return search string if no match is found to allow
1299            all available filtering expressions in quicksearch */
1300         if (matcherstr->len > 0) returnstr = matcherstr->str;
1301         else returnstr = g_strdup(search_string);
1302         g_string_free(matcherstr, FALSE);
1303         return returnstr;
1304 }
1305
1306 static gchar *expand_tag_search_string(const gchar *search_string)
1307 {
1308         gchar *newstr = NULL;
1309         gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL;
1310         gint i = 0;
1311         while (words && words[i] && *words[i]) {
1312                 g_strstrip(words[i]);
1313                 if (!newstr) {
1314                         newstr = g_strdup_printf("tag regexpcase \"%s\"", words[i]);
1315                 } else {
1316                         gint o_len = strlen(newstr);
1317                         gint s_len = 18; /* strlen("|tag regexpcase \"\"") */
1318                         gint n_len = s_len + strlen(words[i]);
1319                         newstr = g_realloc(newstr,o_len+n_len+1);
1320                         strcpy(newstr+o_len, "|tag regexpcase \"");
1321                         strcpy(newstr+o_len+(s_len-1), words[i]);
1322                         strcpy(newstr+o_len+(n_len-1), "\"");
1323                 }
1324                 i++;
1325         }
1326         g_strfreev(words);
1327         return newstr;
1328 }
1329
1330 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
1331 {
1332         quicksearch->running = run;
1333 }
1334
1335 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1336 {
1337         return quicksearch->running;
1338 }
1339
1340 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1341 {
1342         GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))));
1343         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1344         guint32 c;
1345         char *str = g_strdup(gtk_entry_get_text(entry));
1346         char *begin = str;
1347         char *end = NULL;
1348         char *new = NULL;
1349         char key[7] = "";
1350         guint char_len = 0;
1351
1352         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1353                 /* remove selection */
1354                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1355                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1356                 /* refresh string */
1357                 g_free(str);
1358                 str = g_strdup(gtk_entry_get_text(entry));
1359                 begin = str;
1360         }
1361
1362         if (!(c = gdk_keyval_to_unicode(val))) {
1363                 g_free(str);
1364                 return;
1365         }
1366         char_len = g_unichar_to_utf8(c, key);
1367         if (char_len < 0)
1368                 return;
1369         key[char_len] = '\0';
1370         if (curpos < g_utf8_strlen(str, -1)) {
1371                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1372                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1373                 *stop = '\0';
1374                 new = g_strdup_printf("%s%s%s", begin, key, end);
1375                 gtk_entry_set_text(entry, new);
1376                 g_free(end);
1377         } else {
1378                 new = g_strdup_printf("%s%s", begin, key);
1379                 gtk_entry_set_text(entry, new);
1380         }
1381         g_free(str);
1382         g_free(new);
1383         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1384
1385 }
1386
1387 static gboolean quicksearch_match_subfolder(QuickSearch *quicksearch,
1388                                  FolderItem *src)
1389 {
1390         GSList *msglist = NULL;
1391         GSList *cur;
1392         gboolean result = FALSE;
1393         gint num = 0, total = 0;
1394         gint interval = quicksearch_is_fast(quicksearch) ? 5000:100;
1395
1396         statusbar_print_all(_("Searching in %s... \n"),
1397                 src->path ? src->path : "(null)");
1398                 
1399         msglist = folder_item_get_msg_list(src);
1400         total = src->total_msgs;
1401         folder_item_update_freeze();
1402         for (cur = msglist; cur != NULL; cur = cur->next) {
1403                 MsgInfo *msg = (MsgInfo *)cur->data;
1404                 statusbar_progress_all(num++,total, interval);
1405                 if (quicksearch_match(quicksearch, msg)) {
1406                         result = TRUE;
1407                         break;
1408                 }
1409                 if (num % interval == 0)
1410                         GTK_EVENTS_FLUSH();
1411                 if (!quicksearch_is_active(quicksearch))
1412                         break;
1413         }
1414         folder_item_update_thaw();
1415         statusbar_progress_all(0,0,0);
1416         statusbar_pop_all();
1417
1418         procmsg_msg_list_free(msglist);
1419         return result;
1420 }
1421
1422 gboolean quicksearch_is_in_subfolder(QuickSearch *quicksearch, FolderItem *cur)
1423 {
1424         if (quicksearch->root_folder_item == NULL)
1425                 return FALSE;
1426         
1427         while (cur) {
1428                 if (cur == quicksearch->root_folder_item) {
1429                         return TRUE;
1430                 }
1431                 cur = folder_item_parent(cur);
1432         }
1433         return FALSE;
1434 }
1435
1436 void quicksearch_search_subfolders(QuickSearch *quicksearch,
1437                                    FolderView *folderview,
1438                                    FolderItem *folder_item)
1439 {
1440         FolderItem *cur = NULL;
1441         GNode *node = folder_item->node->children;
1442
1443         if (!prefs_common.summary_quicksearch_recurse
1444         ||  quicksearch->in_typing == TRUE)
1445                 return;
1446
1447         for (; node != NULL; node = node->next) {
1448                 cur = FOLDER_ITEM(node->data);
1449                 if (quicksearch_match_subfolder(quicksearch, cur)) {
1450                         folderview_update_search_icon(cur, TRUE);
1451                 } else {
1452                         folderview_update_search_icon(cur, FALSE);
1453                 }
1454                 if (cur->node->children)
1455                         quicksearch_search_subfolders(quicksearch,
1456                                                       folderview,
1457                                                       cur);
1458         }
1459         quicksearch->root_folder_item = folder_item;
1460         if (!quicksearch_is_active(quicksearch))
1461                 quicksearch_reset_cur_folder_item(quicksearch);
1462 }
1463
1464 static void quicksearch_reset_folder_items(QuickSearch *quicksearch,
1465                                     FolderItem *folder_item)
1466 {
1467         FolderItem *cur = NULL;
1468         GNode *node = (folder_item && folder_item->node) ?
1469                         folder_item->node->children : NULL;
1470
1471         for (; node != NULL; node = node->next) {
1472                 cur = FOLDER_ITEM(node->data);
1473                 folderview_update_search_icon(cur, FALSE);
1474                 if (cur->node->children)
1475                         quicksearch_reset_folder_items(quicksearch,
1476                                                        cur);
1477         }
1478 }
1479
1480 void quicksearch_reset_cur_folder_item(QuickSearch *quicksearch)
1481 {
1482         if (quicksearch->root_folder_item)
1483                 quicksearch_reset_folder_items(quicksearch,
1484                                                quicksearch->root_folder_item);
1485
1486         quicksearch->root_folder_item = NULL;
1487 }
1488
1489 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1490 {
1491         return quicksearch->in_typing;
1492 }
1493
1494 void quicksearch_set_search_strings(QuickSearch *quicksearch)
1495 {
1496         GList *strings = prefs_common.summary_quicksearch_history;
1497         gchar *newstr = NULL;
1498         MatcherList *matcher_list = NULL;
1499
1500         if (!strings)
1501                 return;
1502
1503         matcher_parser_disable_warnings(TRUE);
1504         
1505         do {
1506                 newstr = expand_search_string((gchar *) strings->data);
1507                 if (newstr && newstr[0] != '\0') {
1508                         if (!strchr(newstr, ' ')) {
1509                                 quicksearch->normal_search_strings =
1510                                         g_list_append(
1511                                                 quicksearch->normal_search_strings,
1512                                                 g_strdup(strings->data));
1513                                 g_free(newstr);
1514                                 continue;
1515                         }
1516                         
1517                         matcher_list = matcher_parser_get_cond(newstr, FALSE);
1518                         g_free(newstr);
1519                         
1520                         if (matcher_list) {
1521                                 quicksearch->extended_search_strings =
1522                                         g_list_prepend(
1523                                                 quicksearch->extended_search_strings,
1524                                                 g_strdup(strings->data));
1525                                 matcherlist_free(matcher_list);
1526                         } else
1527                                 quicksearch->normal_search_strings =
1528                                         g_list_prepend(
1529                                                 quicksearch->normal_search_strings,
1530                                                 g_strdup(strings->data));
1531                 }
1532         
1533         } while ((strings = g_list_next(strings)) != NULL);
1534
1535         matcher_parser_disable_warnings(FALSE); 
1536
1537         quicksearch->normal_search_strings = g_list_reverse(quicksearch->normal_search_strings);
1538         quicksearch->extended_search_strings = g_list_reverse(quicksearch->extended_search_strings);
1539
1540         quicksearch_set_popdown_strings(quicksearch);
1541 }
1542
1543 /*
1544  * Searches in the supplied folderItem the messages (MessageInfo) matching a
1545  * QuickSearchType + search string (ex.: QUICK_SEARCH_FROM and "foo@bar.com").
1546  *
1547  * Found messages are appended to the array 'messages' and their ref.counts
1548  * are incremented by 1 --so they need to be released (procmsg_msginfo_free())
1549  * before the array 'messages' is freed.
1550  */
1551 void search_msgs_in_folder(GSList **messages, QuickSearch* quicksearch,
1552                            FolderItem* folderItem)
1553 {
1554         /* from quicksearch_match_subfolder */
1555         GSList *msglist = NULL;
1556         GSList *cur;
1557
1558         /* The list is built w/ MsgInfo items whose ref.counts are incremented,
1559            but they are decremented when the list is freed by
1560            procmsg_msg_list_free(): we'll  ask for a new ref., below
1561         */
1562         msglist = folder_item_get_msg_list(folderItem);
1563
1564         for (cur = msglist; cur != NULL; cur = cur->next) {
1565                 MsgInfo *msg = (MsgInfo *)cur->data;
1566                 if (quicksearch_match(quicksearch, msg)) {
1567                         /*debug_print("found: %s from:%s\n",procmsg_get_message_file_path(msg),msg->from);*/
1568                         *messages = g_slist_prepend(*messages, procmsg_msginfo_new_ref(msg));
1569                 }
1570                 /* See 2.0.0cvs140 ChangeLog entry for details
1571                    see also comments in quicksearch_match() */
1572                 if (quicksearch_from_gui(quicksearch)==TRUE
1573                     && !quicksearch_is_active(quicksearch))
1574                         break;
1575         }
1576         procmsg_msg_list_free(msglist);
1577 }
1578
1579 /*
1580  * Searches within the folderItem and its sub-folders (if recursive is TRUE)
1581  * the messages matching the search request.
1582  *
1583  * NB: search within a Folder can be done this way:
1584  *         search_msg_in_folders(messages, quicksearch, searchType,
1585  *                               FOLDER_ITEM(folder->node->data), TRUE);
1586  */
1587 void search_msgs_in_folders(GSList **messages, QuickSearch* quicksearch,
1588                             FolderItem* folderItem)
1589 {
1590         FolderItem *cur = NULL;
1591
1592         search_msgs_in_folder(messages, quicksearch, folderItem);
1593         if (quicksearch->request->recursive == FALSE)
1594                 return;
1595
1596         GNode *node = folderItem->node->children;
1597         for (; node != NULL; node = node->next) {
1598                 cur = FOLDER_ITEM(node->data);
1599                 debug_print("in: %s\n",cur->path);
1600                 if (cur->node->children)
1601                         search_msgs_in_folders(messages, quicksearch, cur);
1602                 else
1603                         search_msgs_in_folder(messages, quicksearch, cur);
1604         }
1605         *messages = g_slist_reverse(*messages);
1606 }
1607
1608  /*
1609   * Returns the QuickSearchType associated to the supplied string.
1610   */
1611 QuickSearchType quicksearch_type(const gchar* type)
1612 {
1613         QuickSearchType searchType = QUICK_SEARCH_EXTENDED;
1614         if (!type)
1615                 return searchType;
1616         switch(toupper(*type)) {
1617         case 'S':
1618                 searchType = QUICK_SEARCH_SUBJECT;
1619         break;
1620         case 'F':
1621                 searchType = QUICK_SEARCH_FROM;
1622         break;
1623         case 'T':
1624                 searchType = QUICK_SEARCH_TO;
1625         break;
1626         case 'E':
1627                 searchType = QUICK_SEARCH_EXTENDED;
1628         break;
1629         case 'M':
1630                 searchType = QUICK_SEARCH_MIXED;
1631         break;
1632         case 'G':
1633                 searchType = QUICK_SEARCH_TAG;
1634         break;
1635         }
1636         return searchType;
1637 }