2011-10-22 [colin] 3.7.10cvs41
[claws.git] / src / summary_search.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2011 Hiroyuki Yamamoto and the Claws Mail 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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtk.h>
30 #if !GTK_CHECK_VERSION(3, 0, 0)
31 #include "gtk/gtksctree.h"
32 #endif
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include "main.h"
38 #include "summary_search.h"
39 #include "summaryview.h"
40 #include "messageview.h"
41 #include "mainwindow.h"
42 #include "menu.h"
43 #include "utils.h"
44 #include "gtkutils.h"
45 #include "combobox.h"
46 #include "prefs_gtk.h"
47 #include "manage_window.h"
48 #include "alertpanel.h"
49 #include "matcher.h"
50 #include "matcher_parser.h"
51 #include "prefs_matcher.h"
52 #include "manual.h"
53 #include "prefs_common.h"
54
55 static struct SummarySearchWindow {
56         GtkWidget *window;
57
58         GtkWidget *bool_optmenu;
59
60         GtkWidget *from_entry;
61         GtkWidget *to_entry;
62         GtkWidget *subject_entry;
63         GtkWidget *body_entry;
64
65         GtkWidget *adv_condition_entry;
66         GtkWidget *adv_condition_btn;
67         GtkWidget *adv_search_checkbtn;
68
69         GtkWidget *case_checkbtn;
70
71         GtkWidget *clear_btn;
72         GtkWidget *help_btn;
73         GtkWidget *all_btn;
74         GtkWidget *prev_btn;
75         GtkWidget *next_btn;
76         GtkWidget *close_btn;
77         GtkWidget *stop_btn;
78
79         SummaryView *summaryview;
80
81         MatcherList                     *matcher_list;
82
83         gboolean is_searching;
84         gboolean from_entry_has_focus;
85         gboolean to_entry_has_focus;
86         gboolean subject_entry_has_focus;
87         gboolean body_entry_has_focus;
88         gboolean adv_condition_entry_has_focus;
89 } search_window;
90
91 static void summary_search_create       (void);
92
93 static void summary_search_execute      (gboolean        backward,
94                                          gboolean        search_all);
95
96 static void summary_search_clear        (GtkButton      *button,
97                                          gpointer        data);
98 static void summary_search_prev_clicked (GtkButton      *button,
99                                          gpointer        data);
100 static void summary_search_next_clicked (GtkButton      *button,
101                                          gpointer        data);
102 static void summary_search_all_clicked  (GtkButton      *button,
103                                          gpointer        data);
104 static void summary_search_stop_clicked (GtkButton      *button,
105                                          gpointer        data);
106 static void adv_condition_btn_clicked   (GtkButton      *button,
107                                          gpointer        data);
108
109 static void from_changed                        (void);
110 static void to_changed                          (void);
111 static void subject_changed                     (void);
112 static void body_changed                        (void);
113 static void adv_condition_changed       (void);
114
115 static gboolean from_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
116                                   gpointer data);
117 static gboolean from_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
118                                   gpointer data);
119 static gboolean to_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
120                                   gpointer data);
121 static gboolean to_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
122                                   gpointer data);
123 static gboolean subject_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
124                                   gpointer data);
125 static gboolean subject_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
126                                   gpointer data);
127 static gboolean body_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
128                                   gpointer data);
129 static gboolean body_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
130                                   gpointer data);
131 static gboolean adv_condition_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
132                                   gpointer data);
133 static gboolean adv_condition_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
134                                   gpointer data);
135 #ifndef MAEMO
136 static gboolean key_pressed             (GtkWidget      *widget,
137                                          GdkEventKey    *event,
138                                          gpointer        data);
139 #endif
140
141 #if !GTK_CHECK_VERSION(2,14,0)
142 /* Work around http://bugzilla.gnome.org/show_bug.cgi?id=56070 */
143 #define GTK_BUTTON_SET_SENSITIVE(widget,sensitive) {                                    \
144         gboolean in_btn = FALSE;                                                        \
145         if (GTK_IS_BUTTON(widget))                                                      \
146                 in_btn = GTK_BUTTON(widget)->in_button;                                 \
147         gtk_widget_set_sensitive(widget, sensitive);                                    \
148         if (GTK_IS_BUTTON(widget))                                                      \
149                 GTK_BUTTON(widget)->in_button = in_btn;                                 \
150 }
151 #else
152 #define GTK_BUTTON_SET_SENSITIVE(widget,sensitive) {                                    \
153         gtk_widget_set_sensitive(widget, sensitive);                                    \
154 }
155 #endif
156
157 void summary_search(SummaryView *summaryview)
158 {
159         if (!search_window.window) {
160                 summary_search_create();
161         } else {
162                 gtk_widget_hide(search_window.window);
163         }
164
165         search_window.summaryview = summaryview;
166
167         gtk_widget_grab_focus(search_window.next_btn);
168         gtk_widget_grab_focus(search_window.subject_entry);
169         gtk_widget_show(search_window.window);
170 }
171
172 static void summary_show_stop_button(void)
173 {
174         gtk_widget_hide(search_window.close_btn);
175         gtk_widget_show(search_window.stop_btn);
176         GTK_BUTTON_SET_SENSITIVE(search_window.all_btn, FALSE)
177         GTK_BUTTON_SET_SENSITIVE(search_window.prev_btn, FALSE)
178         GTK_BUTTON_SET_SENSITIVE(search_window.next_btn, FALSE)
179 }
180
181 static void summary_hide_stop_button(void)
182 {
183         gtk_widget_hide(search_window.stop_btn);
184         gtk_widget_show(search_window.close_btn);
185         gtk_widget_set_sensitive(search_window.all_btn, TRUE);
186         gtk_widget_set_sensitive(search_window.prev_btn, TRUE);
187         gtk_widget_set_sensitive(search_window.next_btn, TRUE);
188 }
189
190 static void summary_search_create(void)
191 {
192         GtkWidget *window;
193         GtkWidget *vbox1;
194         GtkWidget *bool_hbox;
195         GtkWidget *bool_optmenu;
196         GtkListStore *menu;
197         GtkTreeIter iter;
198         GtkWidget *clear_btn;
199
200         GtkWidget *table1;
201         GtkWidget *from_label;
202         GtkWidget *from_entry;
203         GtkWidget *to_label;
204         GtkWidget *to_entry;
205         GtkWidget *subject_label;
206         GtkWidget *subject_entry;
207         GtkWidget *body_label;
208         GtkWidget *body_entry;
209         GtkWidget *adv_condition_label;
210         GtkWidget *adv_condition_entry;
211         GtkWidget *adv_condition_btn;
212
213         GtkWidget *checkbtn_hbox;
214         GtkWidget *adv_search_checkbtn;
215         GtkWidget *case_checkbtn;
216
217         GtkWidget *confirm_area;
218         GtkWidget *help_btn;
219         GtkWidget *all_btn;
220         GtkWidget *prev_btn;
221         GtkWidget *next_btn;
222         GtkWidget *close_btn;
223         GtkWidget *stop_btn;
224         gboolean is_searching = FALSE;
225
226         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "summary_search");
227         gtk_window_set_title(GTK_WINDOW (window), _("Search messages"));
228         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
229         gtk_container_set_border_width(GTK_CONTAINER (window), 8);
230         g_signal_connect(G_OBJECT(window), "delete_event",
231                          G_CALLBACK(gtk_widget_hide_on_delete), NULL);
232 #ifdef MAEMO
233         maemo_connect_key_press_to_mainwindow(GTK_WINDOW(window));
234 #else
235         g_signal_connect(G_OBJECT(window), "key_press_event",
236                          G_CALLBACK(key_pressed), NULL);
237 #endif
238         MANAGE_WINDOW_SIGNALS_CONNECT(window);
239
240         vbox1 = gtk_vbox_new (FALSE, 0);
241         gtk_widget_show (vbox1);
242         gtk_container_add (GTK_CONTAINER (window), vbox1);
243
244         bool_hbox = gtk_hbox_new(FALSE, 4);
245         gtk_widget_show(bool_hbox);
246         gtk_box_pack_start(GTK_BOX(vbox1), bool_hbox, FALSE, FALSE, 0);
247
248         bool_optmenu = gtkut_sc_combobox_create(NULL, FALSE);
249         menu = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(bool_optmenu)));
250         gtk_widget_show(bool_optmenu);
251         gtk_box_pack_start(GTK_BOX(bool_hbox), bool_optmenu, FALSE, FALSE, 0);
252
253         COMBOBOX_ADD(menu, _("Match any of the following"), 0);
254         gtk_combo_box_set_active_iter(GTK_COMBO_BOX(bool_optmenu), &iter);
255         COMBOBOX_ADD(menu, _("Match all of the following"), 1);
256
257         clear_btn = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
258         gtk_widget_show(clear_btn);
259         gtk_box_pack_end(GTK_BOX(bool_hbox), clear_btn, FALSE, FALSE, 0);
260
261         table1 = gtk_table_new (5, 3, FALSE);
262         gtk_widget_show (table1);
263         gtk_box_pack_start (GTK_BOX (vbox1), table1, TRUE, TRUE, 0);
264         gtk_container_set_border_width (GTK_CONTAINER (table1), 4);
265         gtk_table_set_row_spacings (GTK_TABLE (table1), 8);
266         gtk_table_set_col_spacings (GTK_TABLE (table1), 8);
267
268         from_entry = gtk_combo_box_entry_new_text ();
269         gtk_combo_box_set_active(GTK_COMBO_BOX(from_entry), -1);
270         if (prefs_common.summary_search_from_history)
271                 combobox_set_popdown_strings(GTK_COMBO_BOX(from_entry),
272                                 prefs_common.summary_search_from_history);
273         gtk_widget_show (from_entry);
274         gtk_table_attach (GTK_TABLE (table1), from_entry, 1, 3, 0, 1,
275                           GTK_EXPAND|GTK_FILL, 0, 0, 0);
276         g_signal_connect(G_OBJECT(from_entry), "changed",
277                          G_CALLBACK(from_changed), NULL);
278         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((from_entry)))),
279                          "focus_in_event", G_CALLBACK(from_entry_focus_evt_in), NULL);
280         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((from_entry)))),
281                          "focus_out_event", G_CALLBACK(from_entry_focus_evt_out), NULL);
282
283         to_entry = gtk_combo_box_entry_new_text ();
284         gtk_combo_box_set_active(GTK_COMBO_BOX(to_entry), -1);
285         if (prefs_common.summary_search_to_history)
286                 combobox_set_popdown_strings(GTK_COMBO_BOX(to_entry),
287                                 prefs_common.summary_search_to_history);
288         gtk_widget_show (to_entry);
289         gtk_table_attach (GTK_TABLE (table1), to_entry, 1, 3, 1, 2,
290                           GTK_EXPAND|GTK_FILL, 0, 0, 0);
291         g_signal_connect(G_OBJECT(to_entry), "changed",
292                          G_CALLBACK(to_changed), NULL);
293         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((to_entry)))),
294                          "focus_in_event", G_CALLBACK(to_entry_focus_evt_in), NULL);
295         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((to_entry)))),
296                          "focus_out_event", G_CALLBACK(to_entry_focus_evt_out), NULL);
297
298         subject_entry = gtk_combo_box_entry_new_text ();
299         gtk_combo_box_set_active(GTK_COMBO_BOX(subject_entry), -1);
300         if (prefs_common.summary_search_subject_history)
301                 combobox_set_popdown_strings(GTK_COMBO_BOX(subject_entry),
302                                 prefs_common.summary_search_subject_history);
303         gtk_widget_show (subject_entry);
304         gtk_table_attach (GTK_TABLE (table1), subject_entry, 1, 3, 2, 3,
305                           GTK_EXPAND|GTK_FILL, 0, 0, 0);
306         g_signal_connect(G_OBJECT(subject_entry), "changed",
307                          G_CALLBACK(subject_changed), NULL);
308         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((subject_entry)))),
309                          "focus_in_event", G_CALLBACK(subject_entry_focus_evt_in), NULL);
310         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((subject_entry)))),
311                          "focus_out_event", G_CALLBACK(subject_entry_focus_evt_out), NULL);
312
313         body_entry = gtk_combo_box_entry_new_text ();
314         gtk_combo_box_set_active(GTK_COMBO_BOX(body_entry), -1);
315         if (prefs_common.summary_search_body_history)
316                 combobox_set_popdown_strings(GTK_COMBO_BOX(body_entry),
317                                 prefs_common.summary_search_body_history);
318         gtk_widget_show (body_entry);
319         gtk_table_attach (GTK_TABLE (table1), body_entry, 1, 3, 3, 4,
320                           GTK_EXPAND|GTK_FILL, 0, 0, 0);
321         g_signal_connect(G_OBJECT(body_entry), "changed",
322                          G_CALLBACK(body_changed), NULL);
323         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((body_entry)))),
324                          "focus_in_event", G_CALLBACK(body_entry_focus_evt_in), NULL);
325         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((body_entry)))),
326                          "focus_out_event", G_CALLBACK(body_entry_focus_evt_out), NULL);
327
328         adv_condition_entry = gtk_combo_box_entry_new_text ();
329         gtk_combo_box_set_active(GTK_COMBO_BOX(adv_condition_entry), -1);
330         if (prefs_common.summary_search_adv_condition_history)
331                 combobox_set_popdown_strings(GTK_COMBO_BOX(adv_condition_entry),
332                                 prefs_common.summary_search_adv_condition_history);
333         gtk_widget_show (adv_condition_entry);
334         gtk_table_attach (GTK_TABLE (table1), adv_condition_entry, 1, 2, 4, 5,
335                           GTK_EXPAND|GTK_FILL, 0, 0, 0);
336         g_signal_connect(G_OBJECT(adv_condition_entry), "changed",
337                          G_CALLBACK(adv_condition_changed), NULL);
338         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((adv_condition_entry)))),
339                          "focus_in_event", G_CALLBACK(adv_condition_entry_focus_evt_in), NULL);
340         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((adv_condition_entry)))),
341                          "focus_out_event", G_CALLBACK(adv_condition_entry_focus_evt_out), NULL);
342
343         adv_condition_btn = gtk_button_new_with_label(" ... ");
344         gtk_widget_show (adv_condition_btn);
345         gtk_table_attach (GTK_TABLE (table1), adv_condition_btn, 2, 3, 4, 5,
346                           GTK_FILL, 0, 0, 0);
347         g_signal_connect(G_OBJECT (adv_condition_btn), "clicked",
348                          G_CALLBACK(adv_condition_btn_clicked), search_window.window);
349
350         CLAWS_SET_TIP(adv_condition_btn,
351                              _("Edit search criteria"));
352
353         from_label = gtk_label_new (_("From:"));
354         gtk_widget_show (from_label);
355         gtk_table_attach (GTK_TABLE (table1), from_label, 0, 1, 0, 1,
356                           GTK_FILL, 0, 0, 0);
357         gtk_label_set_justify (GTK_LABEL (from_label), GTK_JUSTIFY_RIGHT);
358         gtk_misc_set_alignment (GTK_MISC (from_label), 1, 0.5);
359
360         to_label = gtk_label_new (_("To:"));
361         gtk_widget_show (to_label);
362         gtk_table_attach (GTK_TABLE (table1), to_label, 0, 1, 1, 2,
363                           GTK_FILL, 0, 0, 0);
364         gtk_label_set_justify (GTK_LABEL (to_label), GTK_JUSTIFY_RIGHT);
365         gtk_misc_set_alignment (GTK_MISC (to_label), 1, 0.5);
366
367         subject_label = gtk_label_new (_("Subject:"));
368         gtk_widget_show (subject_label);
369         gtk_table_attach (GTK_TABLE (table1), subject_label, 0, 1, 2, 3,
370                           GTK_FILL, 0, 0, 0);
371         gtk_label_set_justify (GTK_LABEL (subject_label), GTK_JUSTIFY_RIGHT);
372         gtk_misc_set_alignment (GTK_MISC (subject_label), 1, 0.5);
373
374         body_label = gtk_label_new (_("Body:"));
375         gtk_widget_show (body_label);
376         gtk_table_attach (GTK_TABLE (table1), body_label, 0, 1, 3, 4,
377                           GTK_FILL, 0, 0, 0);
378         gtk_label_set_justify (GTK_LABEL (body_label), GTK_JUSTIFY_RIGHT);
379         gtk_misc_set_alignment (GTK_MISC (body_label), 1, 0.5);
380
381         adv_condition_label = gtk_label_new (_("Condition:"));
382         gtk_widget_show (adv_condition_label);
383         gtk_table_attach (GTK_TABLE (table1), adv_condition_label, 0, 1, 4, 5,
384                           GTK_FILL, 0, 0, 0);
385         gtk_label_set_justify (GTK_LABEL (adv_condition_label), GTK_JUSTIFY_RIGHT);
386         gtk_misc_set_alignment (GTK_MISC (adv_condition_label), 1, 0.5);
387
388         checkbtn_hbox = gtk_hbox_new (FALSE, 8);
389         gtk_widget_show (checkbtn_hbox);
390         gtk_box_pack_start (GTK_BOX (vbox1), checkbtn_hbox, TRUE, TRUE, 0);
391         gtk_container_set_border_width (GTK_CONTAINER (checkbtn_hbox), 8);
392
393         case_checkbtn = gtk_check_button_new_with_label (_("Case sensitive"));
394         gtk_widget_show (case_checkbtn);
395         gtk_box_pack_start (GTK_BOX (checkbtn_hbox), case_checkbtn,
396                             FALSE, FALSE, 0);
397
398         adv_search_checkbtn = gtk_check_button_new_with_label (_("Extended Search"));
399         gtk_widget_show (adv_search_checkbtn);
400         gtk_box_pack_start (GTK_BOX (checkbtn_hbox), adv_search_checkbtn,
401                             FALSE, FALSE, 0);
402
403         confirm_area = gtk_hbutton_box_new();
404         gtk_widget_show (confirm_area);
405         gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area),
406                                   GTK_BUTTONBOX_END);
407         gtk_box_set_spacing(GTK_BOX(confirm_area), 5);
408
409         gtkut_stock_button_add_help(confirm_area, &help_btn);
410
411         all_btn = gtk_button_new_with_mnemonic(_("Find _all"));
412         gtkut_widget_set_can_default(all_btn, TRUE);
413         gtk_box_pack_start(GTK_BOX(confirm_area), all_btn, TRUE, TRUE, 0);
414         gtk_widget_show(all_btn);
415
416         prev_btn = gtk_button_new_from_stock(GTK_STOCK_GO_BACK);
417         gtkut_widget_set_can_default(prev_btn, TRUE);
418         gtk_box_pack_start(GTK_BOX(confirm_area), prev_btn, TRUE, TRUE, 0);
419         gtk_widget_show(prev_btn);
420
421         next_btn = gtk_button_new_from_stock(GTK_STOCK_GO_FORWARD);
422         gtkut_widget_set_can_default(next_btn, TRUE);
423         gtk_box_pack_start(GTK_BOX(confirm_area), next_btn, TRUE, TRUE, 0);
424         gtk_widget_show(next_btn);
425
426         close_btn = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
427         gtkut_widget_set_can_default(close_btn, TRUE);
428         gtk_box_pack_start(GTK_BOX(confirm_area), close_btn, TRUE, TRUE, 0);
429         gtk_widget_show(close_btn);
430
431         /* stop button hidden */
432         stop_btn = gtk_button_new_from_stock(GTK_STOCK_STOP);
433         gtkut_widget_set_can_default(stop_btn, TRUE);
434         gtk_box_pack_start(GTK_BOX(confirm_area), stop_btn, TRUE, TRUE, 0);
435
436         gtk_box_pack_start (GTK_BOX (vbox1), confirm_area, FALSE, FALSE, 0);
437         gtk_widget_grab_default(next_btn);
438
439         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, bool_optmenu)
440         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, from_entry)
441         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, to_entry)
442         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, subject_entry)
443         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, body_entry)
444         SET_TOGGLE_SENSITIVITY(adv_search_checkbtn, adv_condition_label)
445         SET_TOGGLE_SENSITIVITY(adv_search_checkbtn, adv_condition_entry)
446         SET_TOGGLE_SENSITIVITY(adv_search_checkbtn, adv_condition_btn)
447         SET_TOGGLE_SENSITIVITY_REVERSE(adv_search_checkbtn, case_checkbtn)
448
449         g_signal_connect(G_OBJECT(help_btn), "clicked",
450                          G_CALLBACK(manual_open_with_anchor_cb),
451                          MANUAL_ANCHOR_SEARCHING);
452         g_signal_connect(G_OBJECT(clear_btn), "clicked",
453                          G_CALLBACK(summary_search_clear), NULL);
454         g_signal_connect(G_OBJECT(all_btn), "clicked",
455                          G_CALLBACK(summary_search_all_clicked), NULL);
456         g_signal_connect(G_OBJECT(prev_btn), "clicked",
457                          G_CALLBACK(summary_search_prev_clicked), NULL);
458         g_signal_connect(G_OBJECT(next_btn), "clicked",
459                          G_CALLBACK(summary_search_next_clicked), NULL);
460         g_signal_connect_closure
461                 (G_OBJECT(close_btn), "clicked",
462                  g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide),
463              window, NULL), FALSE);
464         g_signal_connect(G_OBJECT(stop_btn), "clicked",
465                          G_CALLBACK(summary_search_stop_clicked), NULL);
466
467         search_window.window = window;
468         search_window.bool_optmenu = bool_optmenu;
469         search_window.from_entry = from_entry;
470         search_window.to_entry = to_entry;
471         search_window.subject_entry = subject_entry;
472         search_window.body_entry = body_entry;
473         search_window.adv_condition_entry = adv_condition_entry;
474         search_window.adv_condition_btn = adv_condition_btn;
475         search_window.case_checkbtn = case_checkbtn;
476         search_window.adv_search_checkbtn = adv_search_checkbtn;
477         search_window.clear_btn = clear_btn;
478         search_window.help_btn = help_btn;
479         search_window.all_btn = all_btn;
480         search_window.prev_btn = prev_btn;
481         search_window.next_btn = next_btn;
482         search_window.close_btn = close_btn;
483         search_window.stop_btn = stop_btn;
484         search_window.matcher_list = NULL;
485         search_window.is_searching = is_searching;
486 #ifdef MAEMO
487         maemo_window_full_screen_if_needed(GTK_WINDOW(search_window.window));
488 #endif
489 }
490
491 static void summary_search_execute(gboolean backward, gboolean search_all)
492 {
493         SummaryView *summaryview = search_window.summaryview;
494         GtkCMCTree *ctree = GTK_CMCTREE(summaryview->ctree);
495         GtkCMCTreeNode *node;
496         MsgInfo *msginfo;
497         gboolean adv_search;
498         gboolean bool_and = FALSE;
499         gboolean case_sens = FALSE;
500         gboolean all_searched = FALSE;
501         gboolean matched = FALSE;
502         gboolean body_matched = FALSE;
503         gchar *from_str = NULL, *to_str = NULL, *subject_str = NULL;
504         gchar *body_str = NULL;
505         gchar *adv_condition = NULL;
506         gboolean is_fast = TRUE;
507         gint interval = 1000;
508         gint i = 0;
509         GSList *matchers = NULL;
510
511         if (summary_is_locked(summaryview)) {
512                 return;
513         }
514         summary_lock(summaryview);
515
516         adv_search = gtk_toggle_button_get_active
517                 (GTK_TOGGLE_BUTTON(search_window.adv_search_checkbtn));
518
519         if (search_window.matcher_list != NULL) {
520                 matcherlist_free(search_window.matcher_list);
521                 search_window.matcher_list = NULL;
522         }
523         if (adv_search) {
524                 adv_condition = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.adv_condition_entry));
525                 if (!adv_condition)
526                         adv_condition = gtk_editable_get_chars(
527                                         GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.adv_condition_entry))),0,-1);
528                 if (adv_condition && adv_condition[0] != '\0') {
529
530                         /* add to history */
531                         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.adv_condition_entry));
532                         prefs_common.summary_search_adv_condition_history = add_history(
533                                         prefs_common.summary_search_adv_condition_history, adv_condition);
534                         combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.adv_condition_entry),
535                                         prefs_common.summary_search_adv_condition_history);
536
537                         search_window.matcher_list = matcher_parser_get_cond((gchar*)adv_condition, &is_fast);
538                         if (!is_fast)
539                                 interval = 100;
540                         /* TODO: check for condition parsing error and show an error dialog */
541                         g_free(adv_condition);
542                 } else {
543                         /* TODO: warn if no search condition? (or make buttons enabled only when
544                                 at least one search condition has been set */
545                         summary_unlock(summaryview);
546                         return;
547                 }
548         } else {
549                 bool_and = combobox_get_active_data(
550                                 GTK_COMBO_BOX(search_window.bool_optmenu));
551                 case_sens = gtk_toggle_button_get_active
552                         (GTK_TOGGLE_BUTTON(search_window.case_checkbtn));
553
554                 from_str    = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.from_entry));
555                 to_str      = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.to_entry));
556                 subject_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.subject_entry));
557                 body_str    = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.body_entry));
558
559                 if (!from_str)
560                         from_str = gtk_editable_get_chars(
561                                         GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.from_entry))),0,-1);
562                 if (!to_str)
563                         to_str = gtk_editable_get_chars(
564                                         GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.to_entry))),0,-1);
565                 if (!subject_str)
566                         subject_str = gtk_editable_get_chars(
567                                         GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.subject_entry))),0,-1);
568                 if (!body_str)
569                         body_str = gtk_editable_get_chars(
570                                         GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.body_entry))),0,-1);
571
572                 if (!from_str || !to_str || !subject_str || !body_str) {
573                         /* TODO: warn if no search criteria? (or make buttons enabled only when
574                          * at least one search criteria has been set */
575                         summary_unlock(summaryview);
576                         return;
577                 }
578                 if (    (from_str[0] == '\0') &&
579                                 (to_str[0] == '\0') &&
580                                 (subject_str[0] == '\0') &&
581                                 (body_str[0] == '\0')) {
582                         /* TODO: warn if no search criteria? (or make buttons enabled only when
583                                 at least one search criteria has been set */
584                         summary_unlock(summaryview);
585                         return;
586                 }
587
588                 /* add to history */
589                 if (from_str[0] != '\0') {
590                         MatcherProp *prop = matcherprop_new(MATCHCRITERIA_FROM,
591                                                 NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE,
592                                                 from_str, 0);
593                         matchers = g_slist_append(matchers, prop);
594                         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.from_entry));
595                         prefs_common.summary_search_from_history = add_history(
596                                         prefs_common.summary_search_from_history, from_str);
597                         combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.from_entry),
598                                         prefs_common.summary_search_from_history);
599                 }
600                 if (to_str[0] != '\0') {
601                         MatcherProp *prop = matcherprop_new(MATCHCRITERIA_TO,
602                                                 NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE,
603                                                 to_str, 0);
604                         matchers = g_slist_append(matchers, prop);
605                         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.to_entry));
606                         prefs_common.summary_search_to_history = add_history(
607                                         prefs_common.summary_search_to_history, to_str);
608                         combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.to_entry),
609                                         prefs_common.summary_search_to_history);
610                 }
611                 if (subject_str[0] != '\0') {
612                         MatcherProp *prop = matcherprop_new(MATCHCRITERIA_SUBJECT,
613                                                 NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE,
614                                                 subject_str, 0);
615                         matchers = g_slist_append(matchers, prop);
616                         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.subject_entry));
617                         prefs_common.summary_search_subject_history = add_history(
618                                         prefs_common.summary_search_subject_history, subject_str);
619                         combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.subject_entry),
620                                         prefs_common.summary_search_subject_history);
621                 }
622                 if (body_str[0] != '\0') {
623                         MatcherProp *prop = matcherprop_new(MATCHCRITERIA_BODY_PART,
624                                                 NULL, case_sens ? MATCHTYPE_MATCH:MATCHTYPE_MATCHCASE,
625                                                 body_str, 0);
626                         matchers = g_slist_append(matchers, prop);
627                         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_window.body_entry));
628                         prefs_common.summary_search_body_history = add_history(
629                                         prefs_common.summary_search_body_history, body_str);
630                         combobox_set_popdown_strings(GTK_COMBO_BOX(search_window.body_entry),
631                                         prefs_common.summary_search_body_history);
632                 }
633                 search_window.matcher_list = matcherlist_new(matchers, bool_and);
634         }
635
636         search_window.is_searching = TRUE;
637         main_window_cursor_wait(summaryview->mainwin);
638         summary_show_stop_button();
639
640         if (search_all) {
641                 summary_freeze(summaryview);
642                 summary_unselect_all(summaryview);
643                 node = GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list);
644                 backward = FALSE;
645         } else if (!summaryview->selected) {
646                 if (backward) {
647                         node = GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list_end);
648                 } else {
649                         node = GTK_CMCTREE_NODE(GTK_CMCLIST(ctree)->row_list);
650                 }
651
652                 if (!node) {
653                         search_window.is_searching = FALSE;
654                         summary_hide_stop_button();
655                         main_window_cursor_normal(summaryview->mainwin);
656                         summary_unlock(summaryview);
657                         return;
658                 }
659         } else {
660                 if (backward) {
661                         node = gtkut_ctree_node_prev
662                                 (ctree, summaryview->selected);
663                 } else {
664                         node = gtkut_ctree_node_next
665                                 (ctree, summaryview->selected);
666                 }
667         }
668
669         for (; search_window.is_searching; i++) {
670                 if (!node) {
671                         gchar *str;
672                         AlertValue val;
673
674                         if (search_all) {
675                                 break;
676                         }
677
678                         if (all_searched) {
679                                 alertpanel_full(_("Search failed"),
680                                                 _("Search string not found."),
681                                                 GTK_STOCK_CLOSE, NULL, NULL, FALSE,
682                                                 NULL, ALERT_WARNING, G_ALERTDEFAULT);
683                                 break;
684                         }
685
686                         if (backward)
687                                 str = _("Beginning of list reached; continue from end?");
688                         else
689                                 str = _("End of list reached; continue from beginning?");
690
691                         val = alertpanel(_("Search finished"), str,
692                                          GTK_STOCK_NO, "+" GTK_STOCK_YES, NULL);
693                         if (G_ALERTALTERNATE == val) {
694                                 if (backward) {
695                                         node = GTK_CMCTREE_NODE
696                                                 (GTK_CMCLIST(ctree)->row_list_end);
697                                 } else {
698                                         node = GTK_CMCTREE_NODE
699                                                 (GTK_CMCLIST(ctree)->row_list);
700                                 }
701
702                                 all_searched = TRUE;
703
704                                 manage_window_focus_in(search_window.window, NULL, NULL);
705                         } else {
706                                 break;
707                         }
708                 }
709
710                 msginfo = gtk_cmctree_node_get_row_data(ctree, node);
711                 body_matched = FALSE;
712
713                 matched = matcherlist_match(search_window.matcher_list, msginfo);
714
715                 if (matched) {
716                         if (search_all) {
717                                 gtk_cmctree_select(ctree, node);
718                         } else {
719                                 if (messageview_is_visible
720                                         (summaryview->messageview)) {
721                                         summary_unlock(summaryview);
722                                         summary_select_node
723                                                 (summaryview, node, TRUE, TRUE);
724                                         summary_lock(summaryview);
725                                         if (body_matched) {
726                                                 messageview_search_string
727                                                         (summaryview->messageview,
728                                                          body_str, case_sens);
729                                         }
730                                 } else {
731                                         summary_select_node
732                                                 (summaryview, node, FALSE, TRUE);
733                                 }
734                                 break;
735                         }
736                 }
737
738                 node = backward ? gtkut_ctree_node_prev(ctree, node)
739                                 : gtkut_ctree_node_next(ctree, node);
740                 if (i % interval == 0)
741                         GTK_EVENTS_FLUSH();
742         }
743
744         g_free(from_str);
745         g_free(to_str);
746         g_free(subject_str);
747         g_free(body_str);
748
749         search_window.is_searching = FALSE;
750         summary_hide_stop_button();
751         main_window_cursor_normal(summaryview->mainwin);
752         if (search_all) {
753                 summary_thaw(summaryview);
754         }
755         summary_unlock(summaryview);
756 }
757
758 static void summary_search_clear(GtkButton *button, gpointer data)
759 {
760         if (gtk_toggle_button_get_active
761                 (GTK_TOGGLE_BUTTON(search_window.adv_search_checkbtn))) {
762                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.adv_condition_entry)))), "");
763         } else {
764                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.from_entry)))), "");
765                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.to_entry)))), "");
766                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.subject_entry)))), "");
767                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.body_entry)))), "");
768         }
769         /* stop searching */
770         if (search_window.is_searching) {
771                 search_window.is_searching = FALSE;
772         }
773 }
774
775 static void summary_search_prev_clicked(GtkButton *button, gpointer data)
776 {
777         summary_search_execute(TRUE, FALSE);
778 }
779
780 static void summary_search_next_clicked(GtkButton *button, gpointer data)
781 {
782         summary_search_execute(FALSE, FALSE);
783 }
784
785 static void summary_search_all_clicked(GtkButton *button, gpointer data)
786 {
787         summary_search_execute(FALSE, TRUE);
788 }
789
790 static void adv_condition_btn_done(MatcherList * matchers)
791 {
792         gchar *str;
793
794         cm_return_if_fail(
795                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
796
797         if (matchers == NULL) {
798                 return;
799         }
800
801         str = matcherlist_to_string(matchers);
802
803         if (str != NULL) {
804                 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((search_window.adv_condition_entry)))), str);
805                 g_free(str);
806         }
807 }
808
809 static void summary_search_stop_clicked(GtkButton *button, gpointer data)
810 {
811         search_window.is_searching = FALSE;
812 }
813
814 static void adv_condition_btn_clicked(GtkButton *button, gpointer data)
815 {
816         const gchar * cond_str;
817         MatcherList * matchers = NULL;
818
819         cm_return_if_fail( search_window.window != NULL );
820
821         /* re-use the current search value if it's a condition expression,
822            otherwise ignore it silently */
823         cond_str = gtk_combo_box_get_active_text(GTK_COMBO_BOX(search_window.adv_condition_entry));
824         if (cond_str && *cond_str != '\0') {
825                 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
826         }
827
828         prefs_matcher_open(matchers, adv_condition_btn_done);
829
830         if (matchers != NULL) {
831                 matcherlist_free(matchers);
832         }
833 };
834
835 static void from_changed(void)
836 {
837         if (!search_window.from_entry_has_focus)
838                 gtk_widget_grab_focus(search_window.from_entry);
839 }
840
841 static void to_changed(void)
842 {
843         if (!search_window.to_entry_has_focus)
844                 gtk_widget_grab_focus(search_window.to_entry);
845 }
846
847 static void subject_changed(void)
848 {
849         if (!search_window.subject_entry_has_focus)
850                 gtk_widget_grab_focus(search_window.subject_entry);
851 }
852
853 static void body_changed(void)
854 {
855         if (!search_window.body_entry_has_focus)
856                 gtk_widget_grab_focus(search_window.body_entry);
857 }
858
859 static void adv_condition_changed(void)
860 {
861         if (!search_window.adv_condition_entry_has_focus)
862                 gtk_widget_grab_focus(search_window.adv_condition_entry);
863 }
864
865 static gboolean from_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
866                                   gpointer data)
867 {
868         search_window.from_entry_has_focus = TRUE;
869         return FALSE;
870 }
871
872 static gboolean from_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
873                                   gpointer data)
874 {
875         search_window.from_entry_has_focus = FALSE;
876         return FALSE;
877 }
878
879 static gboolean to_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
880                                   gpointer data)
881 {
882         search_window.to_entry_has_focus = TRUE;
883         return FALSE;
884 }
885
886 static gboolean to_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
887                                   gpointer data)
888 {
889         search_window.to_entry_has_focus = FALSE;
890         return FALSE;
891 }
892
893 static gboolean subject_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
894                                   gpointer data)
895 {
896         search_window.subject_entry_has_focus = TRUE;
897         return FALSE;
898 }
899
900 static gboolean subject_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
901                                   gpointer data)
902 {
903         search_window.subject_entry_has_focus = FALSE;
904         return FALSE;
905 }
906
907 static gboolean body_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
908                                   gpointer data)
909 {
910         search_window.body_entry_has_focus = TRUE;
911         return FALSE;
912 }
913
914 static gboolean body_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
915                                   gpointer data)
916 {
917         search_window.body_entry_has_focus = FALSE;
918         return FALSE;
919 }
920
921 static gboolean adv_condition_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
922                                   gpointer data)
923 {
924         search_window.adv_condition_entry_has_focus = TRUE;
925         return FALSE;
926 }
927
928 static gboolean adv_condition_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
929                                   gpointer data)
930 {
931         search_window.adv_condition_entry_has_focus = FALSE;
932         return FALSE;
933 }
934 #ifndef MAEMO
935 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
936                             gpointer data)
937 {
938         if (event && (event->keyval == GDK_KEY_Escape)) {
939                 /* ESC key will:
940                         - stop a running search
941                         - close the search window if no search is running
942                 */
943                 if (!search_window.is_searching) {
944                         gtk_widget_hide(search_window.window);
945                 } else {
946                         search_window.is_searching = FALSE;
947                 }
948         }
949
950         if (event && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)) {
951                 if (!search_window.is_searching) {
952                         summary_search_execute(FALSE, FALSE);
953                 }
954         }
955
956         if (event && (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_Up)) {
957                 if (search_window.from_entry_has_focus) {
958                         combobox_set_value_from_arrow_key(
959                                         GTK_COMBO_BOX(search_window.from_entry),
960                                         event->keyval);
961                         return TRUE;
962                 }
963                 if (search_window.to_entry_has_focus) {
964                         combobox_set_value_from_arrow_key(
965                                         GTK_COMBO_BOX(search_window.to_entry),
966                                         event->keyval);
967                         return TRUE;
968                 }
969                 if (search_window.subject_entry_has_focus) {
970                         combobox_set_value_from_arrow_key(
971                                         GTK_COMBO_BOX(search_window.subject_entry),
972                                         event->keyval);
973                         return TRUE;
974                 }
975                 if (search_window.body_entry_has_focus) {
976                         combobox_set_value_from_arrow_key(
977                                         GTK_COMBO_BOX(search_window.body_entry),
978                                         event->keyval);
979                         return TRUE;
980                 }
981                 if (search_window.adv_condition_entry_has_focus) {
982                         combobox_set_value_from_arrow_key(
983                                         GTK_COMBO_BOX(search_window.adv_condition_entry),
984                                         event->keyval);
985                         return TRUE;
986                 }
987         }
988
989         return FALSE;
990 }
991 #endif