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