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