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