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