2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2021 the Claws Mail team and Colin Leroy
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.
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.
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/>.
21 #include "claws-features.h"
25 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
34 #include "prefs_common.h"
35 #include "description_window.h"
37 #include "matcher_parser.h"
38 #include "quicksearch.h"
39 #include "folderview.h"
41 #include "prefs_matcher.h"
43 #include "statusbar.h"
44 #include "advsearch.h"
45 #include "alertpanel.h"
47 struct _QuickSearchRequest
49 AdvancedSearchType type
;
52 typedef struct _QuickSearchRequest QuickSearchRequest
;
56 GtkWidget
*hbox_search
;
57 GtkWidget
*search_type_combo
;
58 GtkWidget
*search_string_entry
;
59 GtkWidget
*search_condition_expression
;
60 GtkWidget
*search_description
;
61 GtkWidget
*clear_search
;
66 QuickSearchRequest request
;
67 QuickSearchExecuteCallback callback
;
68 gpointer callback_data
;
72 guint press_timeout_id
;
74 GList
*normal_search_strings
;
75 GList
*extended_search_strings
;
77 AdvancedSearch
*asearch
;
79 gboolean want_history
;
82 static GdkColor qs_active_bgcolor
= {
89 static GdkColor qs_active_color
= {
96 static GdkColor qs_error_bgcolor
= {
103 static GdkColor qs_error_color
= {
111 SEARCH_TYPE_COL_TEXT
,
112 SEARCH_TYPE_COL_CHECKBOX
,
113 SEARCH_TYPE_COL_CHECKBOX_ACTIVE
,
114 SEARCH_TYPE_COL_ACTION
,
116 } QuickSearchTypeColumn
;
119 QS_MENU_ACTION_SUBJECT
,
123 QS_MENU_ACTION_MIXED
,
124 QS_MENU_ACTION_EXTENDED
,
125 QS_MENU_ACTION_SEPARATOR
,
126 QS_MENU_ACTION_RECURSIVE
,
127 QS_MENU_ACTION_STICKY
,
128 QS_MENU_ACTION_TYPEAHEAD
,
129 QS_MENU_ACTION_RUNONSELECT
,
131 } QuickSearchMenuActions
;
133 static void search_type_changed_cb(GtkComboBox
*combobox
,
136 void quicksearch_set_on_progress_cb(QuickSearch
* search
,
137 gboolean (*cb
)(gpointer data
, guint at
, guint matched
, guint total
), gpointer data
)
139 advsearch_set_on_progress_cb(search
->asearch
, cb
, data
);
142 static void quicksearch_set_running(QuickSearch
*quicksearch
, gboolean run
);
143 static void quicksearch_set_matchstring(QuickSearch
*quicksearch
, const gchar
*matchstring
);
144 static void quicksearch_set_active(QuickSearch
*quicksearch
, gboolean active
);
145 static void quicksearch_set_popdown_strings(QuickSearch
*quicksearch
);
147 static void quicksearch_add_to_history(QuickSearch
* quicksearch
)
149 gchar
* search_string
= quicksearch
->request
.matchstring
;
151 /* add to history, for extended search add only correct matching rules */
152 if (quicksearch
->want_history
&& !quicksearch
->in_typing
&& search_string
&& strlen(search_string
) != 0) {
153 switch (prefs_common
.summary_quicksearch_type
) {
154 case ADVANCED_SEARCH_EXTENDED
:
155 if (advsearch_has_proper_predicate(quicksearch
->asearch
)) {
156 quicksearch
->extended_search_strings
=
157 add_history(quicksearch
->extended_search_strings
,
159 prefs_common
.summary_quicksearch_history
=
160 add_history(prefs_common
.summary_quicksearch_history
,
165 quicksearch
->normal_search_strings
=
166 add_history(quicksearch
->normal_search_strings
,
168 prefs_common
.summary_quicksearch_history
=
169 add_history(prefs_common
.summary_quicksearch_history
,
174 quicksearch_set_popdown_strings(quicksearch
);
177 quicksearch
->want_history
= FALSE
;
180 static void quicksearch_invoke_execute(QuickSearch
*quicksearch
, gboolean run_only_if_fast
)
182 if (quicksearch
->running
) {
183 quicksearch
->want_reexec
= TRUE
;
184 advsearch_abort(quicksearch
->asearch
);
189 gboolean active
= quicksearch
->request
.matchstring
!= NULL
190 && g_strcmp0(quicksearch
->request
.matchstring
, "");
191 advsearch_set(quicksearch
->asearch
, quicksearch
->request
.type
,
192 quicksearch
->request
.matchstring
);
194 if (run_only_if_fast
&& !advsearch_is_fast(quicksearch
->asearch
))
197 quicksearch_add_to_history(quicksearch
);
199 quicksearch_set_active(quicksearch
, active
);
201 quicksearch
->want_reexec
= FALSE
;
202 quicksearch_set_running(quicksearch
, TRUE
);
203 if (quicksearch
->callback
!= NULL
)
204 quicksearch
->callback(quicksearch
, quicksearch
->callback_data
);
205 quicksearch_set_running(quicksearch
, FALSE
);
206 } while (quicksearch
->want_reexec
);
209 gboolean
quicksearch_run_on_folder(QuickSearch
* quicksearch
, FolderItem
*folderItem
, MsgInfoList
**result
)
211 if (quicksearch_has_sat_predicate(quicksearch
)) {
212 gboolean was_running
= quicksearch_is_running(quicksearch
);
216 quicksearch_set_running(quicksearch
, TRUE
);
218 main_window_cursor_wait(mainwindow_get_mainwindow());
219 searchres
= advsearch_search_msgs_in_folders(quicksearch
->asearch
, result
, folderItem
, FALSE
);
220 main_window_cursor_normal(mainwindow_get_mainwindow());
223 quicksearch_set_running(quicksearch
, FALSE
);
225 if (quicksearch
->want_reexec
) {
226 advsearch_set(quicksearch
->asearch
, quicksearch
->request
.type
, "");
233 gboolean
quicksearch_is_fast(QuickSearch
*quicksearch
)
235 return advsearch_is_fast(quicksearch
->asearch
);
238 static void quicksearch_set_type(QuickSearch
*quicksearch
, gint type
)
240 prefs_common_get_prefs()->summary_quicksearch_type
= type
;
241 quicksearch
->request
.type
= type
;
244 static gchar
*quicksearch_get_text(QuickSearch
* quicksearch
)
246 gchar
*search_string
= gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
)))), 0, -1);
248 return search_string
;
251 static void quicksearch_set_popdown_strings(QuickSearch
*quicksearch
)
253 GtkWidget
*search_string_entry
= quicksearch
->search_string_entry
;
255 combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry
));
256 if (prefs_common
.summary_quicksearch_type
== ADVANCED_SEARCH_EXTENDED
)
257 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry
),
258 quicksearch
->extended_search_strings
);
260 combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(search_string_entry
),
261 quicksearch
->normal_search_strings
);
264 static void update_extended_buttons (QuickSearch
*quicksearch
)
266 GtkWidget
*expr_btn
= quicksearch
->search_condition_expression
;
267 GtkWidget
*ext_btn
= quicksearch
->search_description
;
269 cm_return_if_fail(expr_btn
!= NULL
);
270 cm_return_if_fail(ext_btn
!= NULL
);
272 if (prefs_common
.summary_quicksearch_type
== ADVANCED_SEARCH_EXTENDED
) {
273 gtk_widget_show(expr_btn
);
274 gtk_widget_show(ext_btn
);
276 gtk_widget_hide(expr_btn
);
277 gtk_widget_hide(ext_btn
);
281 static gboolean
searchbar_focus_evt_in(GtkWidget
*widget
, GdkEventFocus
*event
,
284 qs
->has_focus
= TRUE
;
288 static gboolean
searchbar_focus_evt_out(GtkWidget
*widget
, GdkEventFocus
*event
,
291 qs
->has_focus
= FALSE
;
292 qs
->in_typing
= FALSE
;
296 gboolean
quicksearch_has_focus(QuickSearch
*quicksearch
)
298 return quicksearch
->has_focus
;
301 static void searchbar_run(QuickSearch
*quicksearch
, gboolean run_only_if_fast
)
303 gchar
*search_string
= quicksearch_get_text(quicksearch
);
304 quicksearch_set_matchstring(quicksearch
, search_string
);
305 g_free(search_string
);
307 quicksearch
->want_history
= TRUE
;
309 quicksearch_invoke_execute(quicksearch
, run_only_if_fast
);
312 static int searchbar_changed_timeout(void *data
)
314 QuickSearch
*qs
= (QuickSearch
*)data
;
315 if (qs
&& prefs_common
.summary_quicksearch_dynamic
) {
316 qs
->in_typing
= TRUE
;
317 searchbar_run(qs
, TRUE
);
322 static void searchbar_changed_cb(GtkWidget
*widget
, QuickSearch
*qs
)
324 if (!qs
->has_focus
&& prefs_common
.summary_quicksearch_autorun
) {
325 gtk_widget_grab_focus(qs
->search_string_entry
);
326 searchbar_run(qs
, TRUE
);
330 if (prefs_common
.summary_quicksearch_dynamic
) {
331 if (qs
->press_timeout_id
!= 0) {
332 g_source_remove(qs
->press_timeout_id
);
334 qs
->press_timeout_id
= g_timeout_add(prefs_common
.qs_press_timeout
,
335 searchbar_changed_timeout
, qs
);
339 gtk_widget_grab_focus(qs
->search_string_entry
);
342 static gboolean
searchbar_pressed(GtkWidget
*widget
, GdkEventKey
*event
,
343 QuickSearch
*quicksearch
)
345 if (event
!= NULL
&& (event
->keyval
== GDK_KEY_ISO_Left_Tab
)) {
346 /* Shift+Tab moves focus "back" */
347 gtk_widget_grab_focus(quicksearch
->search_type_combo
);
350 if (event
!= NULL
&& (event
->keyval
== GDK_KEY_Tab
)) {
351 /* Just Tab moves focus "forwards" */
352 gtk_widget_grab_focus(quicksearch
->clear_search
);
356 if (event
!= NULL
&& (event
->keyval
== GDK_KEY_Escape
)) {
359 quicksearch
->in_typing
= FALSE
;
361 str
= quicksearch_get_text(quicksearch
);
362 cm_return_val_if_fail(str
!= NULL
, TRUE
);
364 /* If the string entry is empty -> hide quicksearch bar. If not -> empty it */
366 summaryview_activate_quicksearch(
367 mainwindow_get_mainwindow()->summaryview
,
370 quicksearch_set(quicksearch
, prefs_common
.summary_quicksearch_type
, "");
371 gtk_widget_grab_focus(
372 mainwindow_get_mainwindow()->summaryview
->ctree
);
378 if (event
!= NULL
&& (event
->keyval
== GDK_KEY_Return
|| event
->keyval
== GDK_KEY_KP_Enter
)) {
379 if (quicksearch
->press_timeout_id
!= 0) {
380 g_source_remove(quicksearch
->press_timeout_id
);
381 quicksearch
->press_timeout_id
= 0;
383 quicksearch
->in_typing
= FALSE
;
384 /* add expression to history list and exec quicksearch */
385 searchbar_run(quicksearch
, FALSE
);
387 g_signal_stop_emission_by_name(G_OBJECT(widget
), "key_press_event");
391 if (event
&& (event
->keyval
== GDK_KEY_Down
|| event
->keyval
== GDK_KEY_Up
)) {
392 combobox_set_value_from_arrow_key(
393 GTK_COMBO_BOX(quicksearch
->search_string_entry
),
402 * Strings describing how to use Extended Search
404 * When adding new lines, remember to put 2 strings for each line
406 static gchar
*search_descr_strings
[] = {
407 "a", N_("all messages"),
408 "ag #", N_("messages whose age is greater than # days"),
409 "al #", N_("messages whose age is less than # days"),
410 "agh #", N_("messages whose age is greater than # hours"),
411 "alh #", N_("messages whose age is less than # hours"),
412 "b S", N_("messages which contain S in the message body"),
413 "B S", N_("messages which contain S in the whole message"),
414 "c S", N_("messages carbon-copied to S"),
415 "C S", N_("message is either To: or Cc: to S"),
416 "D", N_("deleted messages"), /** how I can filter deleted messages **/
417 "da \"YYYY-MM-dd HH:mm:ss\"", N_("messages whose date is after requested date "
418 "(time is optional)"),
419 "db \"YYYY-MM-dd HH:mm:ss\"", N_("messages whose date is before requested date "
420 "(time is optional)"),
421 "e S", N_("messages which contain S in the Sender field"),
422 "E S", N_("true if execute \"S\" succeeds"),
423 "f S", N_("messages originating from user S"),
424 "F", N_("forwarded messages"),
425 "ha", N_("messages which have attachments"),
426 "h S", N_("messages which contain S in any header name or value"),
427 "H S", N_("messages which contain S in the value of any header"),
428 "i S", N_("messages which contain S in Message-ID header"),
429 "I S", N_("messages which contain S in In-Reply-To header"),
430 "k #", N_("messages which are marked with color #"),
431 "L", N_("locked messages"),
432 "n S", N_("messages which are in newsgroup S"),
433 "N", N_("new messages"),
434 "O", N_("old messages"),
435 "p", N_("incomplete messages (not entirely downloaded)"),
436 "r", N_("messages which you have replied to"),
437 "R", N_("read messages"),
438 "s S", N_("messages which contain S in subject"),
439 "se #", N_("messages whose score is equal to # points"),
440 "sg #", N_("messages whose score is greater than # points"),
441 "sl #", N_("messages whose score is lower than # points"),
442 "Se #", N_("messages whose size is equal to # bytes"),
443 "Sg #", N_("messages whose size is greater than # bytes"),
444 "Ss #", N_("messages whose size is smaller than # bytes"),
445 "t S", N_("messages which have been sent to S"),
446 "tg S", N_("messages which tags contain S"),
447 "tagged",N_("messages which have tag(s)"),
448 "T", N_("marked messages"),
449 "U", N_("unread messages"),
450 "x S", N_("messages which contain S in References header"),
451 "X \"cmd args\"", N_("messages returning 0 when passed to command - %F is message file"),
452 "y S", N_("messages which contain S in X-Label header"),
454 "&", N_("logical AND operator"),
455 "|", N_("logical OR operator"),
456 "! or ~", N_("logical NOT operator"),
457 "%", N_("case sensitive search"),
458 "#", N_("match using regular expressions instead of substring search"),
460 " ", N_("all filtering expressions are allowed, but cannot be mixed "
461 "through logical operators with the expressions above"),
465 static DescriptionWindow search_descr
= {
470 N_("Extended Search"),
471 N_("Extended Search allows the user to define criteria that messages must "
472 "have in order to match and be displayed in the message list.\n"
473 "The following symbols can be used:"),
477 static void search_description_cb(GtkWidget
*widget
)
479 search_descr
.parent
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "description_window");
480 description_window_create(&search_descr
);
483 static gboolean
clear_search_cb(GtkMenuItem
*widget
, gpointer data
)
485 QuickSearch
*quicksearch
= (QuickSearch
*)data
;
487 quicksearch_set(quicksearch
, prefs_common
.summary_quicksearch_type
, "");
492 static void search_condition_expr_done(MatcherList
* matchers
)
497 mainwindow_get_mainwindow()->summaryview
->quicksearch
!= NULL
);
499 if (matchers
== NULL
)
502 str
= matcherlist_to_string(matchers
);
505 quicksearch_set(mainwindow_get_mainwindow()->summaryview
->quicksearch
,
506 prefs_common
.summary_quicksearch_type
, str
);
509 /* add expression to history list and exec quicksearch */
510 searchbar_run(mainwindow_get_mainwindow()->summaryview
->quicksearch
, FALSE
);
514 static gboolean
search_condition_expr(GtkMenuItem
*widget
, gpointer data
)
517 MatcherList
* matchers
= NULL
;
519 cm_return_val_if_fail(
520 mainwindow_get_mainwindow()->summaryview
->quicksearch
!= NULL
,
523 /* re-use the current quicksearch value, expanding it so it also works
524 * with extended symbols */
525 cond_str
= quicksearch_get_text(mainwindow_get_mainwindow()->summaryview
->quicksearch
);
527 if (*cond_str
!= '\0') {
528 gchar
*newstr
= advsearch_expand_search_string(cond_str
);
530 if (newstr
&& newstr
[0] != '\0')
531 matchers
= matcher_parser_get_cond(newstr
, FALSE
);
535 prefs_matcher_open(matchers
, search_condition_expr_done
);
537 if (matchers
!= NULL
)
538 matcherlist_free(matchers
);
545 static void quicksearch_set_button(GtkButton
*button
, const gchar
*icon
, const gchar
*text
)
547 GList
*children
= gtk_container_get_children(GTK_CONTAINER(button
));
550 gboolean icon_visible
;
552 g_object_get(gtk_settings_get_default(),
553 "gtk-button-images", &icon_visible
,
556 for (cur
= children
; cur
; cur
= cur
->next
)
557 gtk_container_remove(GTK_CONTAINER(button
), GTK_WIDGET(cur
->data
));
559 g_list_free(children
);
560 box
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
562 gtk_container_add(GTK_CONTAINER(button
), box
);
563 if (icon_visible
|| !text
|| !*text
|| icon
!= NULL
)
564 gtk_box_pack_start(GTK_BOX(box
), gtk_image_new_from_icon_name(icon
,
565 GTK_ICON_SIZE_BUTTON
), FALSE
, FALSE
, 0);
566 gtk_box_pack_start(GTK_BOX(box
), gtk_label_new_with_mnemonic(text
), FALSE
, FALSE
, 0);
567 gtk_widget_show_all(box
);
570 static gboolean
search_type_combo_separator_func(GtkTreeModel
*model
,
571 GtkTreeIter
*iter
, gpointer data
)
575 cm_return_val_if_fail(model
!= NULL
, FALSE
);
577 gtk_tree_model_get(model
, iter
, 0, &txt
, -1);
586 static void quicksearch_error(gpointer data
)
588 alertpanel_error(_("Something went wrong during search. Please check your logs."));
591 static void select_correct_combobox_menuitem(QuickSearch
*quicksearch
)
593 gint active_menuitem
= 0;
595 GtkWidget
*combobox
= quicksearch
->search_type_combo
;
597 /* Figure out which menuitem to set as active. QS_MENU_ACTION_ aliases
598 * are in the same order as order of their menu items, so we can
599 * use them as index for gtk_combo_box_set_active().
601 * However, ADVANCED_SEARCH_ aliases are in a different order,
602 * and even if they weren't, we do not want to depend on them
603 * always being in correct order, so we have to map them to
604 * our QS_MENU_ACTION_ aliases manually. */
605 active_type
= prefs_common_get_prefs()->summary_quicksearch_type
;
606 switch (active_type
) {
607 case ADVANCED_SEARCH_SUBJECT
:
608 active_menuitem
= QS_MENU_ACTION_SUBJECT
;
610 case ADVANCED_SEARCH_FROM
:
611 active_menuitem
= QS_MENU_ACTION_FROM
;
613 case ADVANCED_SEARCH_TO
:
614 active_menuitem
= QS_MENU_ACTION_TO
;
616 case ADVANCED_SEARCH_TAG
:
617 active_menuitem
= QS_MENU_ACTION_TAG
;
619 case ADVANCED_SEARCH_MIXED
:
620 active_menuitem
= QS_MENU_ACTION_MIXED
;
622 case ADVANCED_SEARCH_EXTENDED
:
623 active_menuitem
= QS_MENU_ACTION_EXTENDED
;
626 g_warning("unhandled advsearch type %d in quicksearch", active_type
);
630 /* We just want to quietly make the correct menuitem active
631 * without triggering handlers of the combobox "changed" signal,
632 * so we temporarily block handling of that signal. */
633 g_signal_handlers_block_matched(G_OBJECT(combobox
),
634 G_SIGNAL_MATCH_FUNC
, 0, 0, 0,
635 (gpointer
)search_type_changed_cb
,
636 (gpointer
)quicksearch
);
637 gtk_combo_box_set_active(GTK_COMBO_BOX(combobox
), active_menuitem
);
638 g_signal_handlers_unblock_matched(G_OBJECT(combobox
),
639 G_SIGNAL_MATCH_FUNC
, 0, 0, 0,
640 (gpointer
)search_type_changed_cb
,
641 (gpointer
)quicksearch
);
644 static gboolean
set_search_type_checkboxes_func(GtkTreeModel
*model
,
649 gboolean has_checkbox
;
652 gboolean active
= -1;
653 PrefsCommon
*prefs
= prefs_common_get_prefs();
655 gtk_tree_model_get(model
, iter
,
656 SEARCH_TYPE_COL_CHECKBOX
, &has_checkbox
,
657 SEARCH_TYPE_COL_CHECKBOX_ACTIVE
, &cur_active
,
658 SEARCH_TYPE_COL_ACTION
, &action
,
664 prefs
= prefs_common_get_prefs();
666 case QS_MENU_ACTION_RECURSIVE
:
667 active
= prefs
->summary_quicksearch_recurse
;
669 case QS_MENU_ACTION_STICKY
:
670 active
= prefs
->summary_quicksearch_sticky
;
672 case QS_MENU_ACTION_TYPEAHEAD
:
673 active
= prefs
->summary_quicksearch_dynamic
;
675 case QS_MENU_ACTION_RUNONSELECT
:
676 active
= prefs
->summary_quicksearch_autorun
;
680 if (active
!= cur_active
) {
681 gtk_list_store_set(GTK_LIST_STORE(model
), iter
,
682 SEARCH_TYPE_COL_CHECKBOX_ACTIVE
, active
,
689 /* A function to make status of the toggleable menu items
690 * in search type combobox reflect configured prefs */
691 static void set_search_type_checkboxes(GtkComboBox
*combobox
)
695 cm_return_if_fail(combobox
!= NULL
);
697 model
= gtk_combo_box_get_model(GTK_COMBO_BOX(combobox
));
702 gtk_tree_model_foreach(model
, set_search_type_checkboxes_func
, NULL
);
705 static void search_type_changed_cb(GtkComboBox
*combobox
,
708 QuickSearch
*quicksearch
= (QuickSearch
*)user_data
;
711 gboolean has_checkbox
, checkbox_active
;
713 PrefsCommon
*prefs
= prefs_common_get_prefs();
715 cm_return_if_fail(quicksearch
!= NULL
);
716 cm_return_if_fail(gtk_combo_box_get_active_iter(combobox
, &iter
));
718 model
= gtk_combo_box_get_model(combobox
);
720 gtk_tree_model_get(model
, &iter
,
721 SEARCH_TYPE_COL_CHECKBOX
, &has_checkbox
,
722 SEARCH_TYPE_COL_CHECKBOX_ACTIVE
, &checkbox_active
,
723 SEARCH_TYPE_COL_ACTION
, &action
,
726 /* React based on which menuitem was selected */
728 case QS_MENU_ACTION_SUBJECT
:
729 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_SUBJECT
);
731 case QS_MENU_ACTION_FROM
:
732 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_FROM
);
734 case QS_MENU_ACTION_TO
:
735 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_TO
);
737 case QS_MENU_ACTION_TAG
:
738 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_TAG
);
740 case QS_MENU_ACTION_MIXED
:
741 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_MIXED
);
743 case QS_MENU_ACTION_EXTENDED
:
744 quicksearch_set_type(quicksearch
, ADVANCED_SEARCH_EXTENDED
);
746 case QS_MENU_ACTION_RECURSIVE
:
747 prefs
->summary_quicksearch_recurse
= !checkbox_active
;
749 case QS_MENU_ACTION_STICKY
:
750 prefs
->summary_quicksearch_sticky
= !checkbox_active
;
752 case QS_MENU_ACTION_TYPEAHEAD
:
753 prefs
->summary_quicksearch_dynamic
= !checkbox_active
;
755 case QS_MENU_ACTION_RUNONSELECT
:
756 prefs
->summary_quicksearch_autorun
= !checkbox_active
;
759 update_extended_buttons(quicksearch
);
760 /* If one of the toggleable items has been activated, there's
763 /* TYPEAHEAD is mutually exclusive with RUNONSELECT */
764 if (action
== QS_MENU_ACTION_TYPEAHEAD
&& !checkbox_active
)
765 prefs
->summary_quicksearch_autorun
= FALSE
;
767 /* RUNONSELECT is mutually exclusive with TYPEAHEAD */
768 if (action
== QS_MENU_ACTION_RUNONSELECT
&& !checkbox_active
)
769 prefs
->summary_quicksearch_dynamic
= FALSE
;
771 /* Update state of the toggleable menu items */
772 set_search_type_checkboxes(combobox
);
774 /* Activate one of the search types' menu entries, so that
775 * the current search type is visible on a closed combobox */
776 select_correct_combobox_menuitem(quicksearch
);
779 /* Update search results */
780 quicksearch_invoke_execute(quicksearch
, FALSE
);
782 /* Give focus to the text entry */
783 gtk_widget_grab_focus(quicksearch
->search_string_entry
);
786 QuickSearch
*quicksearch_new()
788 QuickSearch
*quicksearch
;
790 GtkWidget
*hbox_search
;
791 GtkWidget
*search_type_combo
;
792 GtkWidget
*search_string_entry
;
793 GtkWidget
*search_hbox
;
794 GtkWidget
*search_description
;
795 GtkWidget
*clear_search
;
796 GtkWidget
*search_condition_expression
;
799 GtkCellRenderer
*renderer
;
802 quicksearch
= g_new0(QuickSearch
, 1);
804 quicksearch
->asearch
= advsearch_new();
805 advsearch_set_on_error_cb(quicksearch
->asearch
, quicksearch_error
, NULL
);
807 /* init. values initally found in quicksearch_new().
808 There's no need to init. all pointers to NULL since we use g_new0
810 quicksearch
->active
= FALSE
;
811 quicksearch
->running
= FALSE
;
812 quicksearch
->in_typing
= FALSE
;
813 quicksearch
->press_timeout_id
= 0;
814 quicksearch
->normal_search_strings
= NULL
;
815 quicksearch
->extended_search_strings
= NULL
;
818 hbox_search
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
820 menu
= gtk_list_store_new(4,
826 search_type_combo
= gtk_combo_box_new_with_model(GTK_TREE_MODEL(menu
));
827 gtk_widget_set_focus_on_click(GTK_WIDGET(search_type_combo
), FALSE
);
828 gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(search_type_combo
),
829 (GtkTreeViewRowSeparatorFunc
)search_type_combo_separator_func
,
832 renderer
= gtk_cell_renderer_toggle_new();
833 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(search_type_combo
), renderer
, TRUE
);
834 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(search_type_combo
),
836 "visible", SEARCH_TYPE_COL_CHECKBOX
,
837 "active", SEARCH_TYPE_COL_CHECKBOX_ACTIVE
,
839 renderer
= gtk_cell_renderer_text_new();
840 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(search_type_combo
), renderer
, TRUE
);
841 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(search_type_combo
),
842 renderer
, "text", SEARCH_TYPE_COL_TEXT
, NULL
);
844 gtk_box_pack_start(GTK_BOX(hbox_search
), search_type_combo
, FALSE
, FALSE
, 0);
846 gtk_list_store_append(menu
, &iter
);
847 gtk_list_store_set(menu
, &iter
,
848 SEARCH_TYPE_COL_TEXT
, _("Subject"),
849 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
850 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_SUBJECT
,
852 gtk_list_store_append(menu
, &iter
);
853 gtk_list_store_set(menu
, &iter
,
854 SEARCH_TYPE_COL_TEXT
, _("From"),
855 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
856 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_FROM
,
858 gtk_list_store_append(menu
, &iter
);
859 gtk_list_store_set(menu
, &iter
,
860 SEARCH_TYPE_COL_TEXT
, _("To"),
861 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
862 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_TO
,
864 gtk_list_store_append(menu
, &iter
);
865 gtk_list_store_set(menu
, &iter
,
866 SEARCH_TYPE_COL_TEXT
, _("Tag"),
867 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
868 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_TAG
,
870 gtk_list_store_append(menu
, &iter
);
871 gtk_list_store_set(menu
, &iter
,
872 SEARCH_TYPE_COL_TEXT
, _("From/To/Cc/Subject/Tag"),
873 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
874 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_MIXED
,
876 gtk_list_store_append(menu
, &iter
);
877 gtk_list_store_set(menu
, &iter
,
878 SEARCH_TYPE_COL_TEXT
, _("Extended"),
879 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
880 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_EXTENDED
,
882 gtk_list_store_append(menu
, &iter
); /* Separator */
883 gtk_list_store_set(menu
, &iter
,
884 SEARCH_TYPE_COL_TEXT
, NULL
,
885 SEARCH_TYPE_COL_CHECKBOX
, FALSE
,
886 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_SEPARATOR
,
888 gtk_list_store_append(menu
, &iter
);
889 gtk_list_store_set(menu
, &iter
,
890 SEARCH_TYPE_COL_TEXT
, _("Recursive"),
891 SEARCH_TYPE_COL_CHECKBOX
, TRUE
,
892 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_RECURSIVE
,
894 gtk_list_store_append(menu
, &iter
);
895 gtk_list_store_set(menu
, &iter
,
896 SEARCH_TYPE_COL_TEXT
, _("Sticky"),
897 SEARCH_TYPE_COL_CHECKBOX
, TRUE
,
898 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_STICKY
,
900 gtk_list_store_append(menu
, &iter
);
901 gtk_list_store_set(menu
, &iter
,
902 SEARCH_TYPE_COL_TEXT
, _("Type-ahead"),
903 SEARCH_TYPE_COL_CHECKBOX
, TRUE
,
904 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_TYPEAHEAD
,
906 gtk_list_store_append(menu
, &iter
);
907 gtk_list_store_set(menu
, &iter
,
908 SEARCH_TYPE_COL_TEXT
, _("Run on select"),
909 SEARCH_TYPE_COL_CHECKBOX
, TRUE
,
910 SEARCH_TYPE_COL_ACTION
, QS_MENU_ACTION_RUNONSELECT
,
913 g_signal_connect(G_OBJECT(search_type_combo
), "changed",
914 G_CALLBACK(search_type_changed_cb
), quicksearch
);
916 gtk_widget_show(search_type_combo
);
918 search_string_entry
= gtk_combo_box_text_new_with_entry ();
919 gtk_combo_box_set_active(GTK_COMBO_BOX(search_string_entry
), -1);
921 vbox
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 0);
922 gtk_box_pack_start(GTK_BOX(vbox
), search_string_entry
, FALSE
, FALSE
, 0);
923 gtk_box_pack_start(GTK_BOX(hbox_search
), vbox
, TRUE
, TRUE
, 4);
925 gtk_widget_show(vbox
);
926 gtk_widget_show(search_string_entry
);
928 search_hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 5);
929 clear_search
= gtkut_stock_button("edit-clear", _("C_lear"));
930 gtk_box_pack_start(GTK_BOX(search_hbox
), clear_search
,
932 g_signal_connect(G_OBJECT(clear_search
), "clicked",
933 G_CALLBACK(clear_search_cb
), quicksearch
);
934 CLAWS_SET_TIP(clear_search
,
935 _("Clear the current search"));
936 gtk_widget_show(clear_search
);
938 search_condition_expression
= gtk_button_new_with_mnemonic("_Edit");
939 gtk_box_pack_start(GTK_BOX(search_hbox
), search_condition_expression
,
941 g_signal_connect(G_OBJECT (search_condition_expression
), "clicked",
942 G_CALLBACK(search_condition_expr
),
944 CLAWS_SET_TIP(search_condition_expression
,
945 _("Edit search criteria"));
946 gtk_widget_show(search_condition_expression
);
948 search_description
= gtkut_stock_button("dialog-information", _("_Information"));
949 gtk_box_pack_start(GTK_BOX(search_hbox
), search_description
,
951 g_signal_connect(G_OBJECT(search_description
), "clicked",
952 G_CALLBACK(search_description_cb
), NULL
);
953 CLAWS_SET_TIP(search_description
,
954 _("Information about extended symbols"));
955 gtk_widget_show(search_description
);
957 gtk_box_pack_start(GTK_BOX(hbox_search
), search_hbox
, FALSE
, FALSE
, 2);
958 gtk_widget_show(search_hbox
);
960 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry
)))),
962 G_CALLBACK(searchbar_pressed
),
965 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry
)))),
967 G_CALLBACK(searchbar_changed_cb
),
970 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry
)))),
972 G_CALLBACK(searchbar_focus_evt_in
),
974 g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((search_string_entry
)))),
976 G_CALLBACK(searchbar_focus_evt_out
),
979 quicksearch
->hbox_search
= hbox_search
;
980 quicksearch
->search_type_combo
= search_type_combo
;
981 quicksearch
->search_string_entry
= search_string_entry
;
982 quicksearch
->search_condition_expression
= search_condition_expression
;
983 quicksearch
->search_description
= search_description
;
984 quicksearch
->active
= FALSE
;
985 quicksearch
->running
= FALSE
;
986 quicksearch
->clear_search
= clear_search
;
987 quicksearch
->in_typing
= FALSE
;
988 quicksearch
->press_timeout_id
= 0;
989 quicksearch
->normal_search_strings
= NULL
;
990 quicksearch
->extended_search_strings
= NULL
;
992 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_description
), "dialog-information", _("_Information"));
993 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_condition_expression
), NULL
, _("E_dit"));
994 quicksearch_set_button(GTK_BUTTON(quicksearch
->clear_search
), "edit-clear", _("C_lear"));
996 update_extended_buttons(quicksearch
);
998 GTKUT_GDKRGBA_TO_GDKCOLOR(prefs_common
.color
[COL_QS_ACTIVE_BG
],
1000 GTKUT_GDKRGBA_TO_GDKCOLOR(prefs_common
.color
[COL_QS_ACTIVE
],
1002 GTKUT_GDKRGBA_TO_GDKCOLOR(prefs_common
.color
[COL_QS_ERROR_BG
],
1004 GTKUT_GDKRGBA_TO_GDKCOLOR(prefs_common
.color
[COL_QS_ERROR
],
1007 /* Update state of the search type combobox to reflect
1008 * current quicksearch prefs */
1009 set_search_type_checkboxes(GTK_COMBO_BOX(search_type_combo
));
1010 select_correct_combobox_menuitem(quicksearch
);
1015 void quicksearch_relayout(QuickSearch
*quicksearch
)
1017 switch (prefs_common
.layout_mode
) {
1020 case WIDE_MSGLIST_LAYOUT
:
1021 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_description
), "dialog-information", _("_Information"));
1022 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_condition_expression
), NULL
, _("E_dit"));
1023 quicksearch_set_button(GTK_BUTTON(quicksearch
->clear_search
), "edit-clear", _("C_lear"));
1026 case VERTICAL_LAYOUT
:
1027 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_description
), "dialog-information", "");
1028 quicksearch_set_button(GTK_BUTTON(quicksearch
->search_condition_expression
), NULL
, _("E_dit"));
1029 quicksearch_set_button(GTK_BUTTON(quicksearch
->clear_search
), "edit-clear", "");
1034 GtkWidget
*quicksearch_get_widget(QuickSearch
*quicksearch
)
1036 return quicksearch
->hbox_search
;
1039 GtkWidget
*quicksearch_get_entry(QuickSearch
*quicksearch
)
1041 return gtk_bin_get_child(GTK_BIN(quicksearch
->search_string_entry
));
1044 void quicksearch_show(QuickSearch
*quicksearch
)
1047 MainWindow
*mainwin
= mainwindow_get_mainwindow();
1048 gtk_widget_show(quicksearch
->hbox_search
);
1049 update_extended_buttons(quicksearch
);
1050 gtk_widget_grab_focus(quicksearch
->search_string_entry
);
1052 if (!mainwin
|| !mainwin
->summaryview
) {
1056 active_type
= prefs_common_get_prefs()->summary_quicksearch_type
;
1057 quicksearch_set_type(quicksearch
, active_type
);
1060 void quicksearch_hide(QuickSearch
*quicksearch
)
1062 if (quicksearch_has_sat_predicate(quicksearch
)) {
1063 quicksearch_set(quicksearch
, prefs_common
.summary_quicksearch_type
, "");
1064 quicksearch_set_active(quicksearch
, FALSE
);
1066 gtk_widget_hide(quicksearch
->hbox_search
);
1070 *\brief Sets the matchstring.
1072 *\param quicksearch quicksearch to set
1073 *\param matchstring the match string; it is duplicated, not stored
1075 static void quicksearch_set_matchstring(QuickSearch
*quicksearch
,
1076 const gchar
*matchstring
)
1078 g_free(quicksearch
->request
.matchstring
);
1079 quicksearch
->request
.matchstring
= g_strdup(matchstring
);
1082 void quicksearch_set(QuickSearch
*quicksearch
, AdvancedSearchType type
, const gchar
*matchstring
)
1084 quicksearch_set_type(quicksearch
, type
);
1085 select_correct_combobox_menuitem(quicksearch
);
1087 if (!matchstring
|| !(*matchstring
))
1088 quicksearch
->in_typing
= FALSE
;
1090 quicksearch_set_matchstring(quicksearch
, matchstring
);
1092 g_signal_handlers_block_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
)))),
1093 G_CALLBACK(searchbar_changed_cb
), quicksearch
);
1094 gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
)))),
1096 g_signal_handlers_unblock_by_func(G_OBJECT(gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
)))),
1097 G_CALLBACK(searchbar_changed_cb
), quicksearch
);
1099 prefs_common
.summary_quicksearch_type
= type
;
1101 quicksearch_invoke_execute(quicksearch
, FALSE
);
1104 gboolean
quicksearch_has_sat_predicate(QuickSearch
*quicksearch
)
1106 return quicksearch
->active
&& advsearch_has_proper_predicate(quicksearch
->asearch
);
1109 static void quicksearch_set_active(QuickSearch
*quicksearch
, gboolean active
)
1111 gboolean error
= FALSE
;
1113 quicksearch
->active
= active
;
1116 (prefs_common
.summary_quicksearch_type
== ADVANCED_SEARCH_EXTENDED
1117 && !advsearch_has_proper_predicate(quicksearch
->asearch
)))
1121 gtk_widget_modify_base(
1122 gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
))),
1123 GTK_STATE_NORMAL
, error
? &qs_error_bgcolor
: &qs_active_bgcolor
);
1124 gtk_widget_modify_text(
1125 gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
))),
1126 GTK_STATE_NORMAL
, error
? &qs_error_color
: &qs_active_color
);
1128 gtk_widget_modify_base(
1129 gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
))),
1130 GTK_STATE_NORMAL
, NULL
);
1131 gtk_widget_modify_text(
1132 gtk_bin_get_child(GTK_BIN((quicksearch
->search_string_entry
))),
1133 GTK_STATE_NORMAL
, NULL
);
1137 advsearch_abort(quicksearch
->asearch
);
1141 void quicksearch_set_execute_callback(QuickSearch
*quicksearch
,
1142 QuickSearchExecuteCallback callback
,
1145 quicksearch
->callback
= callback
;
1146 quicksearch
->callback_data
= data
;
1149 static void quicksearch_set_running(QuickSearch
*quicksearch
, gboolean run
)
1151 quicksearch
->running
= run
;
1154 gboolean
quicksearch_is_running(QuickSearch
*quicksearch
)
1156 return quicksearch
->running
;
1159 gboolean
quicksearch_is_in_typing(QuickSearch
*quicksearch
)
1161 return quicksearch
->in_typing
;
1164 void quicksearch_set_search_strings(QuickSearch
*quicksearch
)
1166 GList
*strings
= prefs_common
.summary_quicksearch_history
;
1167 gchar
*newstr
= NULL
;
1168 MatcherList
*matcher_list
= NULL
;
1173 matcher_parser_disable_warnings(TRUE
);
1176 newstr
= advsearch_expand_search_string((gchar
*) strings
->data
);
1177 if (newstr
&& newstr
[0] != '\0') {
1178 if (!strchr(newstr
, ' ')) {
1179 quicksearch
->normal_search_strings
=
1181 quicksearch
->normal_search_strings
,
1182 g_strdup(strings
->data
));
1184 matcher_list
= matcher_parser_get_cond(newstr
, FALSE
);
1187 quicksearch
->extended_search_strings
=
1189 quicksearch
->extended_search_strings
,
1190 g_strdup(strings
->data
));
1191 matcherlist_free(matcher_list
);
1193 quicksearch
->normal_search_strings
=
1195 quicksearch
->normal_search_strings
,
1196 g_strdup(strings
->data
));
1201 } while ((strings
= g_list_next(strings
)) != NULL
);
1203 matcher_parser_disable_warnings(FALSE
);
1205 quicksearch
->normal_search_strings
= g_list_reverse(quicksearch
->normal_search_strings
);
1206 quicksearch
->extended_search_strings
= g_list_reverse(quicksearch
->extended_search_strings
);
1208 quicksearch_set_popdown_strings(quicksearch
);