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