Fix some copyright headers
[claws.git] / src / gtk / quicksearch.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2018 Colin Leroy 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 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include <glib.h>
25 #include <glib/gi18n.h>
26 #include <ctype.h>
27
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30
31 #if !GTK_CHECK_VERSION(3, 0, 0)
32 #include "gtkcmoptionmenu.h"
33 #endif
34 #include "utils.h"
35 #include "combobox.h"
36 #include "menu.h"
37 #include "prefs_common.h"
38 #include "description_window.h"
39 #include "matcher.h"
40 #include "matcher_parser.h"
41 #include "quicksearch.h"
42 #include "folderview.h"
43 #include "folder.h"
44 #include "prefs_matcher.h"
45 #include "claws.h"
46 #include "statusbar.h"
47 #include "advsearch.h"
48 #include "alertpanel.h"
49
50 struct _QuickSearchRequest
51 {
52         AdvancedSearchType               type;
53         gchar                           *matchstring;
54 };
55 typedef struct _QuickSearchRequest QuickSearchRequest;
56
57 struct _QuickSearch
58 {
59         GtkWidget                       *hbox_search;
60         GtkWidget                       *search_type;
61 #if !GTK_CHECK_VERSION(3, 0, 0)
62         GtkWidget                       *search_type_opt;
63 #endif
64         GtkWidget                       *search_string_entry;
65         GtkWidget                       *search_condition_expression;
66         GtkWidget                       *search_description;
67         GtkWidget                       *clear_search;
68
69         gboolean                         active;
70         gchar                           *search_string;
71
72         QuickSearchRequest               request;
73         QuickSearchExecuteCallback       callback;
74         gpointer                         callback_data;
75         gboolean                         running;
76         gboolean                         has_focus;
77         gboolean                         in_typing;
78         guint                            press_timeout_id;
79
80         GList                           *normal_search_strings;
81         GList                           *extended_search_strings;
82         
83         /* dynamic and autorun qs settings are exclusive*/
84         GtkWidget                        *dynamic_menuitem;
85         GtkWidget                        *autorun_menuitem;
86
87         AdvancedSearch                  *asearch;
88         gboolean                         want_reexec;
89         gboolean                         want_history;
90 };
91
92 static GdkColor qs_active_bgcolor = {
93         (gulong)0,
94         (gushort)0,
95         (gushort)0,
96         (gushort)0
97 };
98
99 static GdkColor qs_active_color = {
100         (gulong)0,
101         (gushort)0,
102         (gushort)0,
103         (gushort)0
104 };
105
106 static GdkColor qs_error_bgcolor = {
107         (gulong)0,
108         (gushort)0,
109         (gushort)0,
110         (gushort)0
111 };
112
113 static GdkColor qs_error_color = {
114         (gulong)0,
115         (gushort)0,
116         (gushort)0,
117         (gushort)0
118 };
119
120 void quicksearch_set_on_progress_cb(QuickSearch* search,
121                 gboolean (*cb)(gpointer data, guint at, guint matched, guint total), gpointer data)
122 {
123         advsearch_set_on_progress_cb(search->asearch, cb, data);
124 }
125
126 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
127 static void quicksearch_set_matchstring(QuickSearch *quicksearch, const gchar *matchstring);
128 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
129 static void quicksearch_set_popdown_strings(QuickSearch *quicksearch);
130
131 static void quicksearch_add_to_history(QuickSearch* quicksearch)
132 {
133         gchar* search_string = quicksearch->request.matchstring;
134
135         /* add to history, for extended search add only correct matching rules */
136         if (quicksearch->want_history && !quicksearch->in_typing && search_string && strlen(search_string) != 0) {
137                 switch (prefs_common.summary_quicksearch_type) {
138                         case ADVANCED_SEARCH_EXTENDED:
139                                 if (advsearch_has_proper_predicate(quicksearch->asearch)) {
140                                         quicksearch->extended_search_strings =
141                                                 add_history(quicksearch->extended_search_strings,
142                                                                 g_strdup(search_string));
143                                         prefs_common.summary_quicksearch_history =
144                                                 add_history(prefs_common.summary_quicksearch_history,
145                                                                 g_strdup(search_string));
146                                 }
147                                 break;
148                         default:
149                                 quicksearch->normal_search_strings =
150                                         add_history(quicksearch->normal_search_strings,
151                                                         g_strdup(search_string));               
152                                 prefs_common.summary_quicksearch_history =
153                                         add_history(prefs_common.summary_quicksearch_history,
154                                                         g_strdup(search_string));
155                                 break;
156                 }
157
158                 quicksearch_set_popdown_strings(quicksearch);
159         }
160
161         quicksearch->want_history = FALSE;
162 }
163
164 static void quicksearch_invoke_execute(QuickSearch *quicksearch, gboolean run_only_if_fast)
165 {
166         if (quicksearch->running) {
167                 quicksearch->want_reexec = TRUE;
168                 advsearch_abort(quicksearch->asearch);
169                 return;
170         }
171
172         do {
173                 gboolean active = quicksearch->request.matchstring != NULL 
174                                    && g_strcmp0(quicksearch->request.matchstring, "");
175                 advsearch_set(quicksearch->asearch, quicksearch->request.type,
176                                 quicksearch->request.matchstring);
177
178                 if (run_only_if_fast && !advsearch_is_fast(quicksearch->asearch))
179                         return;
180
181                 quicksearch_add_to_history(quicksearch);
182
183                 quicksearch_set_active(quicksearch, active);
184
185                 quicksearch->want_reexec = FALSE;
186                 quicksearch_set_running(quicksearch, TRUE);
187                 if (quicksearch->callback != NULL)
188                         quicksearch->callback(quicksearch, quicksearch->callback_data);
189                 quicksearch_set_running(quicksearch, FALSE);
190         } while (quicksearch->want_reexec);
191 }
192
193 gboolean quicksearch_run_on_folder(QuickSearch* quicksearch, FolderItem *folderItem, MsgInfoList **result)
194 {
195         if (quicksearch_has_sat_predicate(quicksearch)) {
196                 gboolean was_running = quicksearch_is_running(quicksearch);
197                 gboolean searchres;
198
199                 if (!was_running)
200                         quicksearch_set_running(quicksearch, TRUE);
201
202                 main_window_cursor_wait(mainwindow_get_mainwindow());
203                 searchres = advsearch_search_msgs_in_folders(quicksearch->asearch, result, folderItem, FALSE);
204                 main_window_cursor_normal(mainwindow_get_mainwindow());
205
206                 if (!was_running)
207                         quicksearch_set_running(quicksearch, FALSE);
208
209                 if (quicksearch->want_reexec) {
210                         advsearch_set(quicksearch->asearch, quicksearch->request.type, "");
211                 }
212                 return searchres;
213         } else
214                 return FALSE;
215 }
216
217 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
218 {
219         return advsearch_is_fast(quicksearch->asearch);
220 }
221
222 static void quicksearch_set_type(QuickSearch *quicksearch, gint type)
223 {
224 #if !GTK_CHECK_VERSION(3, 0, 0)
225         gint index;
226         quicksearch->request.type = type;
227         index = menu_find_option_menu_index(GTK_CMOPTION_MENU(quicksearch->search_type_opt), 
228                                         GINT_TO_POINTER(type),
229                                         NULL);
230         gtk_cmoption_menu_set_history(GTK_CMOPTION_MENU(quicksearch->search_type_opt), index);  
231 #endif
232 }
233
234 static gchar *quicksearch_get_text(QuickSearch * quicksearch)
235 {
236         gchar *search_string = gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))), 0, -1);
237
238         return search_string;
239 }
240
241 static void quicksearch_set_popdown_strings(QuickSearch *quicksearch)
242 {
243         GtkWidget *search_string_entry = quicksearch->search_string_entry;
244
245 #if !GTK_CHECK_VERSION(2, 24, 0)
246         combobox_unset_popdown_strings(GTK_COMBO_BOX(search_string_entry));
247         if (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED)
248                 combobox_set_popdown_strings(GTK_COMBO_BOX(search_string_entry),
249                         quicksearch->extended_search_strings);  
250         else
251                 combobox_set_popdown_strings(GTK_COMBO_BOX(search_string_entry),
252                         quicksearch->normal_search_strings);
253 #else
254         combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry));        
255         if (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED)
256                 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry),
257                         quicksearch->extended_search_strings);  
258         else
259                 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry),
260                         quicksearch->normal_search_strings);
261 #endif
262 }
263
264 static void update_extended_buttons (QuickSearch *quicksearch)
265 {
266         GtkWidget *expr_btn = quicksearch->search_condition_expression;
267         GtkWidget *ext_btn = quicksearch->search_description;
268
269         cm_return_if_fail(expr_btn != NULL);
270         cm_return_if_fail(ext_btn != NULL);
271
272         if (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED) {
273                 gtk_widget_show(expr_btn);
274                 gtk_widget_show(ext_btn);
275         } else {
276                 gtk_widget_hide(expr_btn);
277                 gtk_widget_hide(ext_btn);
278         }
279 }
280
281 static gboolean searchbar_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
282                                   QuickSearch *qs)
283 {
284         qs->has_focus = TRUE;
285         return FALSE;
286 }
287
288 static gboolean searchbar_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
289                                   QuickSearch *qs)
290 {
291         qs->has_focus = FALSE;
292         qs->in_typing = FALSE;
293         return FALSE;
294 }
295
296 gboolean quicksearch_has_focus(QuickSearch *quicksearch)
297 {
298         return quicksearch->has_focus;
299 }
300
301 static void searchbar_run(QuickSearch *quicksearch, gboolean run_only_if_fast)
302 {
303         gchar *search_string = quicksearch_get_text(quicksearch);
304         quicksearch_set_matchstring(quicksearch, search_string);
305         g_free(search_string);
306
307         quicksearch->want_history = TRUE;
308
309         quicksearch_invoke_execute(quicksearch, run_only_if_fast);
310 }
311
312 static int searchbar_changed_timeout(void *data)
313 {
314         QuickSearch *qs = (QuickSearch *)data;
315         if (qs && prefs_common.summary_quicksearch_dynamic) {
316                 qs->in_typing = TRUE;
317                 searchbar_run(qs, TRUE);
318         }
319         return FALSE;
320 }
321
322 static void searchbar_changed_cb(GtkWidget *widget, QuickSearch *qs)
323 {
324         if (!qs->has_focus && prefs_common.summary_quicksearch_autorun) {
325                 gtk_widget_grab_focus(qs->search_string_entry);
326                 searchbar_run(qs, TRUE);
327                 return;
328         }
329
330         if (prefs_common.summary_quicksearch_dynamic) {
331                 if (qs->press_timeout_id != 0) {
332                         g_source_remove(qs->press_timeout_id);
333                 }
334                 qs->press_timeout_id = g_timeout_add(500,
335                                 searchbar_changed_timeout, qs);
336         }
337
338         if (!qs->has_focus)
339                 gtk_widget_grab_focus(qs->search_string_entry);
340 }
341
342 static gboolean searchbar_pressed(GtkWidget *widget, GdkEventKey *event,
343                                   QuickSearch *quicksearch)
344 {
345         if (event && (event->keyval == GDK_KEY_Escape)) {
346                 gchar *str;
347
348                 quicksearch->in_typing = FALSE;
349
350                 str = quicksearch_get_text(quicksearch);
351                 cm_return_val_if_fail(str != NULL, TRUE);
352
353                 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
354                 if (!*str) {
355                         summaryview_activate_quicksearch(
356                                 mainwindow_get_mainwindow()->summaryview, 
357                                 FALSE);
358                 } else {
359                         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
360                         gtk_widget_grab_focus(
361                                         mainwindow_get_mainwindow()->summaryview->ctree);
362                 }
363                 g_free(str);
364                 return TRUE;
365         }
366
367         if (event != NULL && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)) {
368                 if (quicksearch->press_timeout_id != 0) {
369                         g_source_remove(quicksearch->press_timeout_id);
370                         quicksearch->press_timeout_id = 0;
371                 }
372                 quicksearch->in_typing = FALSE;
373                 /* add expression to history list and exec quicksearch */
374                 searchbar_run(quicksearch, FALSE);
375
376                 g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event");
377                 return TRUE;
378         }
379
380         if (event && (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_Up)) {
381                 combobox_set_value_from_arrow_key(
382                                 GTK_COMBO_BOX(quicksearch->search_string_entry),
383                                 event->keyval);
384                 return TRUE;
385         }
386
387         return FALSE;
388 }
389
390 static gboolean searchtype_changed(GtkMenuItem *widget, gpointer data)
391 {
392         QuickSearch *quicksearch = (QuickSearch *)data;
393
394         prefs_common.summary_quicksearch_type = GPOINTER_TO_INT(g_object_get_data(
395                                    G_OBJECT(GTK_MENU_ITEM(gtk_menu_get_active(
396                                    GTK_MENU(quicksearch->search_type)))), MENU_VAL_ID));
397         quicksearch->request.type = prefs_common.summary_quicksearch_type;
398
399         /* Show extended search description button, only when Extended is selected */
400         update_extended_buttons(quicksearch);
401         quicksearch_set_popdown_strings(quicksearch);
402
403         quicksearch_invoke_execute(quicksearch, FALSE);
404         gtk_widget_grab_focus(quicksearch->search_string_entry);
405
406         return TRUE;
407 }
408
409 static gboolean searchtype_recursive_changed(GtkMenuItem *widget, gpointer data)
410 {
411         QuickSearch *quicksearch = (QuickSearch *)data;
412         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
413
414         prefs_common.summary_quicksearch_recurse = checked;
415
416         /* reselect the search type */
417         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
418
419         quicksearch_invoke_execute(quicksearch, FALSE);
420
421         return TRUE;
422 }
423
424 static gboolean searchtype_sticky_changed(GtkMenuItem *widget, gpointer data)
425 {
426         QuickSearch *quicksearch = (QuickSearch *)data;
427         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
428
429         prefs_common.summary_quicksearch_sticky = checked;
430
431         /* reselect the search type */
432         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
433
434         return TRUE;
435 }
436
437 static gboolean searchtype_dynamic_changed(GtkMenuItem *widget, gpointer data)
438 {
439         QuickSearch *quicksearch = (QuickSearch *)data;
440         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
441
442         prefs_common.summary_quicksearch_dynamic = checked;
443         if (checked)
444                 gtk_check_menu_item_set_active(
445                                 GTK_CHECK_MENU_ITEM(quicksearch->autorun_menuitem),
446                                 FALSE);
447
448         /* reselect the search type */
449         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
450
451         return TRUE;
452 }
453
454 static gboolean searchtype_autorun_changed(GtkMenuItem *widget, gpointer data)
455 {
456         QuickSearch *quicksearch = (QuickSearch *)data;
457         gboolean checked = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
458
459         prefs_common.summary_quicksearch_autorun = checked;
460         if (checked)
461                 gtk_check_menu_item_set_active(
462                                 GTK_CHECK_MENU_ITEM(quicksearch->dynamic_menuitem),
463                                 FALSE);
464
465         /* reselect the search type */
466         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
467
468         return TRUE;
469 }
470
471 /*
472  * Strings describing how to use Extended Search
473  *
474  * When adding new lines, remember to put 2 strings for each line
475  */
476 static gchar *search_descr_strings[] = {
477         "a",     N_("all messages"),
478         "ag #",  N_("messages whose age is greater than # days"),
479         "al #",  N_("messages whose age is less than # days"),
480         "agh #",  N_("messages whose age is greater than # hours"),
481         "alh #",  N_("messages whose age is less than # hours"),
482         "b S",   N_("messages which contain S in the message body"),
483         "B S",   N_("messages which contain S in the whole message"),
484         "c S",   N_("messages carbon-copied to S"),
485         "C S",   N_("message is either To: or Cc: to S"),
486         "D",     N_("deleted messages"), /** how I can filter deleted messages **/
487         "e S",   N_("messages which contain S in the Sender field"),
488         "E S",   N_("true if execute \"S\" succeeds"),
489         "f S",   N_("messages originating from user S"),
490         "F",     N_("forwarded messages"),
491         "ha",    N_("messages which have attachments"),
492         "h S",   N_("messages which contain S in any header name or value"),
493         "H S",   N_("messages which contain S in the value of any header"),
494         "i S",   N_("messages which contain S in Message-ID header"),
495         "I S",   N_("messages which contain S in In-Reply-To header"),
496         "k #",   N_("messages which are marked with color #"),
497         "L",     N_("locked messages"),
498         "n S",   N_("messages which are in newsgroup S"),
499         "N",     N_("new messages"),
500         "O",     N_("old messages"),
501         "p",     N_("incomplete messages (not entirely downloaded)"),
502         "r",     N_("messages which you have replied to"),
503         "R",     N_("read messages"),
504         "s S",   N_("messages which contain S in subject"),
505         "se #",  N_("messages whose score is equal to # points"),
506         "sg #",  N_("messages whose score is greater than # points"),
507         "sl #",  N_("messages whose score is lower than # points"),
508         "Se #",  N_("messages whose size is equal to # bytes"),
509         "Sg #",  N_("messages whose size is greater than # bytes"),
510         "Ss #",  N_("messages whose size is smaller than # bytes"),
511         "t S",   N_("messages which have been sent to S"),
512         "tg S",  N_("messages which tags contain S"),
513         "tagged",N_("messages which have tag(s)"),
514         "T",     N_("marked messages"),
515         "U",     N_("unread messages"),
516         "x S",   N_("messages which contain S in References header"),
517         "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
518         "y S",   N_("messages which contain S in X-Label header"),
519         "",      "" ,
520         "&amp;",         N_("logical AND operator"),
521         "|",     N_("logical OR operator"),
522         "! or ~",       N_("logical NOT operator"),
523         "%",     N_("case sensitive search"),
524         "#",     N_("match using regular expressions instead of substring search"),
525         "",      "" ,
526         " ",     N_("all filtering expressions are allowed, but cannot be mixed "
527                     "through logical operators with the expressions above"),
528         NULL,    NULL
529 };
530
531 static DescriptionWindow search_descr = {
532         NULL,
533         NULL,
534         FALSE,
535         2,
536         N_("Extended Search"),
537         N_("Extended Search allows the user to define criteria that messages must "
538            "have in order to match and be displayed in the message list.\n"
539            "The following symbols can be used:"),
540         search_descr_strings
541 };
542
543 static void search_description_cb(GtkWidget *widget)
544 {
545         search_descr.parent = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "description_window");
546         description_window_create(&search_descr);
547 };
548
549 static gboolean clear_search_cb(GtkMenuItem *widget, gpointer data)
550 {
551         QuickSearch *quicksearch = (QuickSearch *)data;
552
553         if (!quicksearch->active)
554                 return TRUE;
555
556         quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
557
558         return TRUE;
559 };
560
561 static void search_condition_expr_done(MatcherList * matchers)
562 {
563         gchar *str;
564
565         cm_return_if_fail(
566                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL);
567
568         if (matchers == NULL)
569                 return;
570
571         str = matcherlist_to_string(matchers);
572
573         if (str != NULL) {
574                 quicksearch_set(mainwindow_get_mainwindow()->summaryview->quicksearch,
575                                 prefs_common.summary_quicksearch_type, str);
576                 g_free(str);
577
578                 /* add expression to history list and exec quicksearch */
579                 searchbar_run(mainwindow_get_mainwindow()->summaryview->quicksearch, FALSE);
580         }
581 }
582
583 static gboolean search_condition_expr(GtkMenuItem *widget, gpointer data)
584 {
585         gchar * cond_str;
586         MatcherList * matchers = NULL;
587         
588         cm_return_val_if_fail(
589                         mainwindow_get_mainwindow()->summaryview->quicksearch != NULL,
590                         FALSE);
591
592         /* re-use the current quicksearch value if it's a condition expression,
593            otherwise ignore it silently */
594         cond_str = quicksearch_get_text(mainwindow_get_mainwindow()->summaryview->quicksearch);
595
596         if (*cond_str != '\0') {
597                 matchers = matcher_parser_get_cond((gchar*)cond_str, NULL);
598         }
599
600         prefs_matcher_open(matchers, search_condition_expr_done);
601
602         if (matchers != NULL)
603                 matcherlist_free(matchers);
604
605         g_free(cond_str);
606
607         return TRUE;
608 };
609
610 static void quicksearch_set_button(GtkButton *button, const gchar *icon, const gchar *text)
611 {
612         GList *children = gtk_container_get_children(GTK_CONTAINER(button));
613         GList *cur;
614         GtkWidget *box;
615         gboolean icon_visible;
616
617         g_object_get(gtk_settings_get_default(), 
618                                          "gtk-button-images", &icon_visible, 
619                                          NULL);
620
621         for (cur = children; cur; cur = cur->next)
622                 gtk_container_remove(GTK_CONTAINER(button), GTK_WIDGET(cur->data));
623         
624         g_list_free(children);
625         box = gtk_hbox_new(FALSE, 0);
626         
627         gtk_container_add(GTK_CONTAINER(button), box);
628         if (icon_visible || !text || !*text)
629                 gtk_box_pack_start(GTK_BOX(box), gtk_image_new_from_stock(icon, 
630                         GTK_ICON_SIZE_BUTTON), FALSE, FALSE, 0);
631         gtk_box_pack_start(GTK_BOX(box), gtk_label_new_with_mnemonic(text), FALSE, FALSE, 0);
632         gtk_widget_show_all(box);
633 }
634
635 static void quicksearch_error(gpointer data)
636 {
637         alertpanel_error(_("Something went wrong during search. Please check your logs."));
638 }
639
640 QuickSearch *quicksearch_new()
641 {
642         QuickSearch *quicksearch;
643
644         GtkWidget *hbox_search;
645 #if !GTK_CHECK_VERSION(3, 0, 0)
646         GtkWidget *search_type_opt;
647 #endif
648         GtkWidget *search_type;
649         GtkWidget *search_string_entry;
650         GtkWidget *search_hbox;
651         GtkWidget *search_description;
652         GtkWidget *clear_search;
653         GtkWidget *search_condition_expression;
654         GtkWidget *menuitem;
655         GtkWidget *vbox;
656
657         quicksearch = g_new0(QuickSearch, 1);
658
659         quicksearch->asearch = advsearch_new();
660         advsearch_set_on_error_cb(quicksearch->asearch, quicksearch_error, NULL);
661
662         /* init. values initally found in quicksearch_new().
663            There's no need to init. all pointers to NULL since we use g_new0
664          */
665         quicksearch->active = FALSE;
666         quicksearch->running = FALSE;
667         quicksearch->in_typing = FALSE;
668         quicksearch->press_timeout_id = 0;
669         quicksearch->normal_search_strings = NULL;
670         quicksearch->extended_search_strings = NULL;
671
672         /* quick search */
673         hbox_search = gtk_hbox_new(FALSE, 0);
674
675 #if !GTK_CHECK_VERSION(3, 0, 0)
676         search_type_opt = gtk_cmoption_menu_new();
677         gtk_widget_show(search_type_opt);
678         gtk_box_pack_start(GTK_BOX(hbox_search), search_type_opt, FALSE, FALSE, 0);
679 #endif
680
681         search_type = gtk_menu_new();
682         MENUITEM_ADD (search_type, menuitem,
683                         prefs_common_translated_header_name("Subject"), ADVANCED_SEARCH_SUBJECT);
684         g_signal_connect(G_OBJECT(menuitem), "activate",
685                          G_CALLBACK(searchtype_changed),
686                          quicksearch);
687         MENUITEM_ADD (search_type, menuitem,
688                         prefs_common_translated_header_name("From"), ADVANCED_SEARCH_FROM);
689         g_signal_connect(G_OBJECT(menuitem), "activate",
690                          G_CALLBACK(searchtype_changed),
691                          quicksearch);
692         MENUITEM_ADD (search_type, menuitem,
693                         prefs_common_translated_header_name("To"), ADVANCED_SEARCH_TO);
694         g_signal_connect(G_OBJECT(menuitem), "activate",
695                          G_CALLBACK(searchtype_changed),
696                          quicksearch);
697         MENUITEM_ADD (search_type, menuitem,
698                         prefs_common_translated_header_name("Tag"), ADVANCED_SEARCH_TAG);
699         g_signal_connect(G_OBJECT(menuitem), "activate",
700                          G_CALLBACK(searchtype_changed),
701                          quicksearch);
702         MENUITEM_ADD (search_type, menuitem,
703                         _("From/To/Cc/Subject/Tag"), ADVANCED_SEARCH_MIXED);
704         g_signal_connect(G_OBJECT(menuitem), "activate",
705                          G_CALLBACK(searchtype_changed),
706                          quicksearch);
707         MENUITEM_ADD (search_type, menuitem, _("Extended"), ADVANCED_SEARCH_EXTENDED);
708         g_signal_connect(G_OBJECT(menuitem), "activate",
709                          G_CALLBACK(searchtype_changed),
710                          quicksearch);
711
712         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), gtk_separator_menu_item_new());
713
714         menuitem = gtk_check_menu_item_new_with_label(_("Recursive"));
715         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
716
717         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
718                                         prefs_common.summary_quicksearch_recurse);
719         g_signal_connect(G_OBJECT(menuitem), "activate",
720                          G_CALLBACK(searchtype_recursive_changed),
721                          quicksearch);
722
723         menuitem = gtk_check_menu_item_new_with_label(_("Sticky"));
724         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
725
726         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
727                                         prefs_common.summary_quicksearch_sticky);
728
729         g_signal_connect(G_OBJECT(menuitem), "activate",
730                          G_CALLBACK(searchtype_sticky_changed),
731                          quicksearch);
732
733         menuitem = gtk_check_menu_item_new_with_label(_("Type-ahead"));
734         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
735
736         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
737                                         prefs_common.summary_quicksearch_dynamic);
738
739         quicksearch->dynamic_menuitem = menuitem;
740
741         g_signal_connect(G_OBJECT(menuitem), "activate",
742                          G_CALLBACK(searchtype_dynamic_changed),
743                          quicksearch);
744
745         menuitem = gtk_check_menu_item_new_with_label(_("Run on select"));
746         gtk_menu_shell_append(GTK_MENU_SHELL(search_type), menuitem);
747
748         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem),
749                                         prefs_common.summary_quicksearch_autorun);
750
751         quicksearch->autorun_menuitem = menuitem;
752
753         g_signal_connect(G_OBJECT(menuitem), "activate",
754                          G_CALLBACK(searchtype_autorun_changed),
755                          quicksearch);
756
757 #if !GTK_CHECK_VERSION(3, 0, 0)
758         gtk_cmoption_menu_set_menu(GTK_CMOPTION_MENU(search_type_opt), search_type);
759
760         quicksearch->search_type_opt = search_type_opt;
761 #endif
762         quicksearch_set_type(quicksearch, prefs_common.summary_quicksearch_type);
763
764         gtk_widget_show(search_type);
765
766 #if !GTK_CHECK_VERSION(2, 24, 0)
767         search_string_entry = gtk_combo_box_entry_new_text ();
768 #else
769         search_string_entry = gtk_combo_box_text_new_with_entry ();
770 #endif
771         gtk_combo_box_set_active(GTK_COMBO_BOX(search_string_entry), -1);
772
773         vbox = gtk_vbox_new(TRUE, 0);
774         gtk_box_pack_start(GTK_BOX(vbox), search_string_entry, FALSE, FALSE, 0);
775         gtk_box_pack_start(GTK_BOX(hbox_search), vbox, TRUE, TRUE, 4);
776
777         gtk_widget_show(vbox);
778         gtk_widget_show(search_string_entry);
779
780         search_hbox = gtk_hbox_new(FALSE, 5);
781         clear_search = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
782         gtk_box_pack_start(GTK_BOX(search_hbox), clear_search,
783                            FALSE, FALSE, 0);
784         g_signal_connect(G_OBJECT(clear_search), "clicked",
785                          G_CALLBACK(clear_search_cb), quicksearch);
786         CLAWS_SET_TIP(clear_search,
787                              _("Clear the current search"));
788         gtk_widget_show(clear_search);
789
790         search_condition_expression = gtk_button_new_from_stock(GTK_STOCK_EDIT);
791         gtk_box_pack_start(GTK_BOX(search_hbox), search_condition_expression,
792                            FALSE, FALSE, 0);
793         g_signal_connect(G_OBJECT (search_condition_expression), "clicked",
794                          G_CALLBACK(search_condition_expr),
795                          quicksearch);
796         CLAWS_SET_TIP(search_condition_expression,
797                              _("Edit search criteria"));
798         gtk_widget_show(search_condition_expression);
799
800         search_description = gtk_button_new_from_stock(GTK_STOCK_INFO);
801         gtk_box_pack_start(GTK_BOX(search_hbox), search_description,
802                            FALSE, FALSE, 0);
803         g_signal_connect(G_OBJECT(search_description), "clicked",
804                          G_CALLBACK(search_description_cb), NULL);
805         CLAWS_SET_TIP(search_description,
806                              _("Information about extended symbols"));
807         gtk_widget_show(search_description);
808
809         gtk_box_pack_start(GTK_BOX(hbox_search), search_hbox, FALSE, FALSE, 2);
810         gtk_widget_show(search_hbox);
811
812         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
813                            "key_press_event",
814                            G_CALLBACK(searchbar_pressed),
815                            quicksearch);
816
817         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
818                          "changed",
819                          G_CALLBACK(searchbar_changed_cb),
820                          quicksearch);
821
822         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
823                          "focus_in_event",
824                          G_CALLBACK(searchbar_focus_evt_in),
825                          quicksearch);
826         g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry)))),
827                          "focus_out_event",
828                          G_CALLBACK(searchbar_focus_evt_out),
829                          quicksearch);
830
831         quicksearch->hbox_search = hbox_search;
832         quicksearch->search_type = search_type;
833         quicksearch->search_string_entry = search_string_entry;
834         quicksearch->search_condition_expression = search_condition_expression;
835         quicksearch->search_description = search_description;
836         quicksearch->active = FALSE;
837         quicksearch->running = FALSE;
838         quicksearch->clear_search = clear_search;
839         quicksearch->in_typing = FALSE;
840         quicksearch->press_timeout_id = 0;
841         quicksearch->normal_search_strings = NULL;
842         quicksearch->extended_search_strings = NULL;
843
844         quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, _("_Information"));
845         quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, _("E_dit"));
846         quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, _("C_lear"));
847         
848         update_extended_buttons(quicksearch);
849
850         gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QS_ACTIVE_BG],
851                                            &qs_active_bgcolor);
852         gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QS_ACTIVE],
853                                            &qs_active_color);
854         gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QS_ERROR_BG],
855                                            &qs_error_bgcolor);
856         gtkut_convert_int_to_gdk_color(prefs_common.color[COL_QS_ERROR],
857                                            &qs_error_color);
858
859         return quicksearch;
860 }
861
862 void quicksearch_relayout(QuickSearch *quicksearch)
863 {
864         switch (prefs_common.layout_mode) {
865         case NORMAL_LAYOUT:
866         case WIDE_LAYOUT:
867         case WIDE_MSGLIST_LAYOUT:
868                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, _("_Information"));
869                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, _("E_dit"));
870                 quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, _("C_lear"));
871                 break;
872         case SMALL_LAYOUT:
873         case VERTICAL_LAYOUT:
874                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_description), GTK_STOCK_INFO, "");
875                 quicksearch_set_button(GTK_BUTTON(quicksearch->search_condition_expression), GTK_STOCK_EDIT, "");
876                 quicksearch_set_button(GTK_BUTTON(quicksearch->clear_search), GTK_STOCK_CLEAR, "");
877                 break;
878         }
879 }
880
881 GtkWidget *quicksearch_get_widget(QuickSearch *quicksearch)
882 {
883         return quicksearch->hbox_search;
884 }
885
886 void quicksearch_show(QuickSearch *quicksearch)
887 {
888         MainWindow *mainwin = mainwindow_get_mainwindow();
889         GtkWidget *ctree = NULL;
890         gtk_widget_show(quicksearch->hbox_search);
891         update_extended_buttons(quicksearch);
892         gtk_widget_grab_focus(quicksearch->search_string_entry);
893
894         if (!mainwin || !mainwin->summaryview) {
895                 return;
896         }
897         
898         ctree = summary_get_main_widget(mainwin->summaryview);
899         
900         if (ctree && mainwin->summaryview->selected)
901                 gtk_cmctree_node_moveto(GTK_CMCTREE(ctree), 
902                                 mainwin->summaryview->selected, 
903                                 0, 0.5, 0);
904 }
905
906 void quicksearch_hide(QuickSearch *quicksearch)
907 {
908         if (quicksearch_has_sat_predicate(quicksearch)) {
909                 quicksearch_set(quicksearch, prefs_common.summary_quicksearch_type, "");
910                 quicksearch_set_active(quicksearch, FALSE);
911         }
912         gtk_widget_hide(quicksearch->hbox_search);
913 }
914
915 /*
916  *\brief        Sets the matchstring.
917  *
918  *\param        quicksearch quicksearch to set
919  *\param        matchstring the match string; it is duplicated, not stored
920  */
921 static void quicksearch_set_matchstring(QuickSearch *quicksearch,
922                                         const gchar *matchstring)
923 {
924         g_free(quicksearch->request.matchstring);
925         quicksearch->request.matchstring = g_strdup(matchstring);
926 }
927
928 void quicksearch_set(QuickSearch *quicksearch, AdvancedSearchType type, const gchar *matchstring)
929 {
930         quicksearch_set_type(quicksearch, type);
931
932         if (!matchstring || !(*matchstring))
933                 quicksearch->in_typing = FALSE;
934
935         quicksearch_set_matchstring(quicksearch, matchstring);
936                 
937         g_signal_handlers_block_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
938                                         G_CALLBACK(searchbar_changed_cb), quicksearch);
939         gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
940                            matchstring);
941         g_signal_handlers_unblock_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry)))),
942                                           G_CALLBACK(searchbar_changed_cb), quicksearch);
943
944         prefs_common.summary_quicksearch_type = type;
945
946         quicksearch_invoke_execute(quicksearch, FALSE);
947 }
948
949 gboolean quicksearch_has_sat_predicate(QuickSearch *quicksearch)
950 {
951         return quicksearch->active && advsearch_has_proper_predicate(quicksearch->asearch);
952 }
953
954 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active)
955 {
956         gboolean error = FALSE;
957
958         quicksearch->active = active;
959
960         if (active && 
961                 (prefs_common.summary_quicksearch_type == ADVANCED_SEARCH_EXTENDED
962                  && !advsearch_has_proper_predicate(quicksearch->asearch)))
963                 error = TRUE;
964
965         if (active) {
966                 gtk_widget_set_sensitive(quicksearch->clear_search, TRUE);
967                         gtk_widget_modify_base(
968                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
969                                 GTK_STATE_NORMAL, error ? &qs_error_bgcolor : &qs_active_bgcolor);
970                         gtk_widget_modify_text(
971                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
972                                 GTK_STATE_NORMAL, error ? &qs_error_color : &qs_active_color);
973         } else {
974                 gtk_widget_set_sensitive(quicksearch->clear_search, FALSE);
975                         gtk_widget_modify_base(
976                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
977                                 GTK_STATE_NORMAL, NULL);
978                         gtk_widget_modify_text(
979                                 gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))),
980                                 GTK_STATE_NORMAL, NULL);
981         }
982
983         if (!active) {
984                 advsearch_abort(quicksearch->asearch);
985         }
986 }
987
988 void quicksearch_set_execute_callback(QuickSearch *quicksearch,
989                                       QuickSearchExecuteCallback callback,
990                                       gpointer data)
991 {
992         quicksearch->callback = callback;
993         quicksearch->callback_data = data;
994 }
995
996 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
997 {
998         quicksearch->running = run;
999 }
1000
1001 gboolean quicksearch_is_running(QuickSearch *quicksearch)
1002 {
1003         return quicksearch->running;
1004 }
1005
1006 void quicksearch_pass_key(QuickSearch *quicksearch, guint val, GdkModifierType mod)
1007 {
1008         GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch->search_string_entry))));
1009         glong curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1010         guint32 c;
1011         char *str = g_strdup(gtk_entry_get_text(entry));
1012         char *begin = str;
1013         char *end = NULL;
1014         char *new = NULL;
1015         char key[7] = "";
1016         gint char_len = 0;
1017
1018         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), NULL, NULL)) {
1019                 /* remove selection */
1020                 gtk_editable_delete_selection(GTK_EDITABLE(entry));
1021                 curpos = gtk_editable_get_position(GTK_EDITABLE(entry));
1022                 /* refresh string */
1023                 g_free(str);
1024                 str = g_strdup(gtk_entry_get_text(entry));
1025                 begin = str;
1026         }
1027
1028         if (!(c = gdk_keyval_to_unicode(val))) {
1029                 g_free(str);
1030                 return;
1031         }
1032         char_len = g_unichar_to_utf8(c, key);
1033         if (char_len < 0)
1034                 return;
1035         key[char_len] = '\0';
1036         if (curpos < g_utf8_strlen(str, -1)) {
1037                 gchar *stop = g_utf8_offset_to_pointer(begin, curpos);
1038                 end = g_strdup(g_utf8_offset_to_pointer(str, curpos));
1039                 *stop = '\0';
1040                 new = g_strdup_printf("%s%s%s", begin, key, end);
1041                 gtk_entry_set_text(entry, new);
1042                 g_free(end);
1043         } else {
1044                 new = g_strdup_printf("%s%s", begin, key);
1045                 gtk_entry_set_text(entry, new);
1046         }
1047         g_free(str);
1048         g_free(new);
1049         gtk_editable_set_position(GTK_EDITABLE(entry), curpos+1);
1050
1051 }
1052
1053 gboolean quicksearch_is_in_typing(QuickSearch *quicksearch)
1054 {
1055         return quicksearch->in_typing;
1056 }
1057
1058 void quicksearch_set_search_strings(QuickSearch *quicksearch)
1059 {
1060         GList *strings = prefs_common.summary_quicksearch_history;
1061         gchar *newstr = NULL;
1062         MatcherList *matcher_list = NULL;
1063
1064         if (!strings)
1065                 return;
1066
1067         matcher_parser_disable_warnings(TRUE);
1068         
1069         do {
1070                 newstr = advsearch_expand_search_string((gchar *) strings->data);
1071                 if (newstr && newstr[0] != '\0') {
1072                         if (!strchr(newstr, ' ')) {
1073                                 quicksearch->normal_search_strings =
1074                                         g_list_append(
1075                                                 quicksearch->normal_search_strings,
1076                                                 g_strdup(strings->data));
1077                         } else {
1078                                 matcher_list = matcher_parser_get_cond(newstr, FALSE);
1079                         
1080                                 if (matcher_list) {
1081                                         quicksearch->extended_search_strings =
1082                                                 g_list_prepend(
1083                                                         quicksearch->extended_search_strings,
1084                                                         g_strdup(strings->data));
1085                                         matcherlist_free(matcher_list);
1086                                 } else
1087                                         quicksearch->normal_search_strings =
1088                                                 g_list_prepend(
1089                                                         quicksearch->normal_search_strings,
1090                                                         g_strdup(strings->data));
1091                         }
1092                 }
1093                 g_free(newstr);
1094         
1095         } while ((strings = g_list_next(strings)) != NULL);
1096
1097         matcher_parser_disable_warnings(FALSE); 
1098
1099         quicksearch->normal_search_strings = g_list_reverse(quicksearch->normal_search_strings);
1100         quicksearch->extended_search_strings = g_list_reverse(quicksearch->extended_search_strings);
1101
1102         quicksearch_set_popdown_strings(quicksearch);
1103 }
1104