talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

message_search.c (12003B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2025 the Claws Mail team and Hiroyuki Yamamoto
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  *
     18  */
     19 
     20 #include "defs.h"
     21 
     22 #include <glib.h>
     23 #include <glib/gi18n.h>
     24 #include <gdk/gdkkeysyms.h>
     25 #include <gtk/gtk.h>
     26 #include <stdio.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 
     30 #include "main.h"
     31 #include "message_search.h"
     32 #include "messageview.h"
     33 #include "compose.h"
     34 #include "utils.h"
     35 #include "gtkutils.h"
     36 #include "combobox.h"
     37 #include "manage_window.h"
     38 #include "alertpanel.h"
     39 #include "manual.h"
     40 #include "prefs_common.h"
     41 
     42 static struct MessageSearchWindow {
     43 	GtkWidget *window;
     44 	GtkWidget *body_entry;
     45 	GtkWidget *case_checkbtn;
     46 	GtkWidget *help_btn;
     47 	GtkWidget *prev_btn;
     48 	GtkWidget *next_btn;
     49 	GtkWidget *close_btn;
     50 	GtkWidget *stop_btn;
     51 
     52 	SearchInterface *interface;
     53 	void *interface_obj;
     54 
     55 	gboolean is_searching;
     56 	gboolean body_entry_has_focus;
     57 } search_window;
     58 
     59 static SearchInterface compose_interface = {
     60 	.search_string_backward = (SearchStringFunc) compose_search_string_backward,
     61 	.set_position = (SetPositionFunc) compose_set_position,
     62 	.search_string = (SearchStringFunc) compose_search_string,
     63 };
     64 
     65 static SearchInterface messageview_interface = {
     66 	.set_position = (SetPositionFunc) messageview_set_position,
     67 	.search_string = (SearchStringFunc) messageview_search_string,
     68 	.search_string_backward = (SearchStringFunc) messageview_search_string_backward,
     69 };
     70 
     71 static void message_search_create	(void);
     72 static void message_search_execute	(gboolean	 backward);
     73 
     74 static void message_search_prev_clicked	(GtkButton	*button,
     75 					 gpointer	 data);
     76 static void message_search_next_clicked	(GtkButton	*button,
     77 					 gpointer	 data);
     78 static void message_search_stop_clicked	(GtkButton	*button,
     79 					 gpointer	 data);
     80 static void body_changed		(void);
     81 static gboolean body_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
     82 			      	  gpointer data);
     83 static gboolean body_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
     84 			      	  gpointer data);
     85 static gboolean key_pressed		(GtkWidget	*widget,
     86 					 GdkEventKey	*event,
     87 					 gpointer	 data);
     88 
     89 
     90 #define GTK_BUTTON_SET_SENSITIVE(widget,sensitive) {					\
     91 	gtk_widget_set_sensitive(widget, sensitive);					\
     92 }
     93 
     94 static void message_show_stop_button(void)
     95 {
     96 	gtk_widget_hide(search_window.close_btn);
     97 	gtk_widget_show(search_window.stop_btn);
     98 	GTK_BUTTON_SET_SENSITIVE(search_window.prev_btn, FALSE)
     99 	GTK_BUTTON_SET_SENSITIVE(search_window.next_btn, FALSE)
    100 }
    101 
    102 static void message_hide_stop_button(void)
    103 {
    104 	gtk_widget_hide(search_window.stop_btn);
    105 	gtk_widget_show(search_window.close_btn);
    106 	gtk_widget_set_sensitive(search_window.prev_btn, TRUE);
    107 	gtk_widget_set_sensitive(search_window.next_btn, TRUE);
    108 }
    109 
    110 void message_search(MessageView *messageview)
    111 {
    112 	message_search_other(&messageview_interface, (void *)messageview);
    113 }
    114 
    115 void message_search_compose(Compose *compose)
    116 {
    117 	message_search_other(&compose_interface, (void *)compose);
    118 }
    119 
    120 void message_search_close (void *obj)
    121 {
    122 	if(!search_window.window) {
    123 		return;
    124 	}
    125 	if (search_window.interface_obj == obj) {
    126 		gtk_widget_hide(search_window.window);
    127 		search_window.interface_obj = NULL;
    128 	}
    129 }
    130 
    131 void message_search_other(SearchInterface *interface, void *obj)
    132 {
    133 	if (!search_window.window)
    134 		message_search_create();
    135 	else
    136 		gtk_widget_hide(search_window.window);
    137 
    138 	search_window.interface_obj = obj;
    139 	search_window.interface = interface;
    140 
    141 	gtk_widget_grab_focus(search_window.next_btn);
    142 	gtk_widget_grab_focus(search_window.body_entry);
    143 	gtk_widget_show(search_window.window);
    144 }
    145 
    146 
    147 static void message_search_create(void)
    148 {
    149 	GtkWidget *window;
    150 
    151 	GtkWidget *vbox1;
    152 	GtkWidget *hbox1;
    153 	GtkWidget *body_label;
    154 	GtkWidget *body_entry;
    155 
    156 	GtkWidget *checkbtn_hbox;
    157 	GtkWidget *case_checkbtn;
    158 
    159 	GtkWidget *confirm_area;
    160 	GtkWidget *help_btn;
    161 	GtkWidget *prev_btn;
    162 	GtkWidget *next_btn;
    163 	GtkWidget *close_btn;
    164 	GtkWidget *stop_btn;
    165 
    166 	window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "message_search");
    167 	gtk_window_set_title (GTK_WINDOW (window),
    168 			      _("Find in current message"));
    169 	gtk_widget_set_size_request (window, 450, -1);
    170 	gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
    171 	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
    172 	gtk_container_set_border_width (GTK_CONTAINER (window), 8);
    173 	g_signal_connect(G_OBJECT(window), "delete_event",
    174 			 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
    175 	g_signal_connect(G_OBJECT(window), "key_press_event",
    176 			 G_CALLBACK(key_pressed), NULL);
    177 	MANAGE_WINDOW_SIGNALS_CONNECT(window);
    178 
    179 	vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    180 	gtk_widget_show (vbox1);
    181 	gtk_container_add (GTK_CONTAINER (window), vbox1);
    182 
    183 	hbox1 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
    184 	gtk_widget_show (hbox1);
    185 	gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0);
    186 
    187 	body_label = gtk_label_new (_("Find text:"));
    188 	gtk_widget_show (body_label);
    189 	gtk_box_pack_start (GTK_BOX (hbox1), body_label, FALSE, FALSE, 0);
    190 
    191 	body_entry = gtk_combo_box_text_new_with_entry ();
    192 	gtk_combo_box_set_active(GTK_COMBO_BOX(body_entry), -1);
    193 	if (prefs_common.message_search_history)
    194 		combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(body_entry),
    195 				prefs_common.message_search_history);
    196 	gtk_widget_show (body_entry);
    197 	gtk_box_pack_start (GTK_BOX (hbox1), body_entry, TRUE, TRUE, 0);
    198 	g_signal_connect(G_OBJECT(body_entry), "changed",
    199 			 G_CALLBACK(body_changed), NULL);
    200 	g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((body_entry)))),
    201 			 "focus_in_event", G_CALLBACK(body_entry_focus_evt_in), NULL);
    202 	g_signal_connect(G_OBJECT(gtk_bin_get_child(GTK_BIN((body_entry)))),
    203 			 "focus_out_event", G_CALLBACK(body_entry_focus_evt_out), NULL);
    204 
    205 	checkbtn_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
    206 	gtk_widget_show (checkbtn_hbox);
    207 	gtk_box_pack_start (GTK_BOX (vbox1), checkbtn_hbox, TRUE, TRUE, 0);
    208 	gtk_container_set_border_width (GTK_CONTAINER (checkbtn_hbox), 8);
    209 
    210 	case_checkbtn = gtk_check_button_new_with_label (_("Case sensitive"));
    211 	gtk_widget_show (case_checkbtn);
    212 	gtk_box_pack_start (GTK_BOX (checkbtn_hbox), case_checkbtn,
    213 			    FALSE, FALSE, 0);
    214 
    215 	confirm_area = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
    216 	gtk_widget_show (confirm_area);
    217 	gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area),
    218 				  GTK_BUTTONBOX_END);
    219 	gtk_box_set_spacing(GTK_BOX(confirm_area), 5);
    220 
    221 	gtkut_stock_button_add_help(confirm_area, &help_btn);
    222 
    223 	prev_btn = gtkut_stock_button("go-previous", _("_Previous"));
    224 	gtk_widget_set_can_default(prev_btn, TRUE);
    225 	gtk_box_pack_start(GTK_BOX(confirm_area), prev_btn, TRUE, TRUE, 0);
    226 	gtk_widget_show(prev_btn);
    227 
    228 	next_btn = gtkut_stock_button("go-next", _("_Next"));
    229 	gtk_widget_set_can_default(next_btn, TRUE);
    230 	gtk_box_pack_start(GTK_BOX(confirm_area), next_btn, TRUE, TRUE, 0);
    231 	gtk_widget_show(next_btn);
    232 
    233 	close_btn = gtkut_stock_button("window-close", _("_Close"));
    234 	gtk_widget_set_can_default(close_btn, TRUE);
    235 	gtk_box_pack_start(GTK_BOX(confirm_area), close_btn, TRUE, TRUE, 0);
    236 	gtk_widget_show(close_btn);
    237 
    238 	/* stop button hidden */
    239 	stop_btn = gtk_button_new_with_mnemonic(_("_Stop"));
    240 	gtk_widget_set_can_default(stop_btn, TRUE);
    241 	gtk_box_pack_start(GTK_BOX(confirm_area), stop_btn, TRUE, TRUE, 0);
    242 
    243 	gtk_widget_show (confirm_area);
    244 	gtk_box_pack_start (GTK_BOX (vbox1), confirm_area, FALSE, FALSE, 0);
    245 	gtk_widget_grab_default(next_btn);
    246 
    247 	g_signal_connect(G_OBJECT(help_btn), "clicked",
    248 			 G_CALLBACK(manual_open_with_anchor_cb),
    249 			 MANUAL_ANCHOR_SEARCHING);
    250 	g_signal_connect(G_OBJECT(prev_btn), "clicked",
    251 			 G_CALLBACK(message_search_prev_clicked), NULL);
    252 	g_signal_connect(G_OBJECT(next_btn), "clicked",
    253 			 G_CALLBACK(message_search_next_clicked), NULL);
    254 	g_signal_connect_closure
    255 		(G_OBJECT(close_btn), "clicked",
    256 		 g_cclosure_new_swap(G_CALLBACK(gtk_widget_hide),
    257 				     window, NULL),
    258 		 FALSE);
    259 	g_signal_connect(G_OBJECT(stop_btn), "clicked",
    260 			 G_CALLBACK(message_search_stop_clicked), NULL);
    261 
    262 	search_window.window = window;
    263 	search_window.body_entry = body_entry;
    264 	search_window.case_checkbtn = case_checkbtn;
    265 	search_window.help_btn = help_btn;
    266 	search_window.prev_btn = prev_btn;
    267 	search_window.next_btn = next_btn;
    268 	search_window.close_btn = close_btn;
    269 	search_window.stop_btn = stop_btn;
    270 }
    271 
    272 static void message_search_execute(gboolean backward)
    273 {
    274 	void *interface_obj = search_window.interface_obj;
    275 	gboolean case_sens;
    276 	gboolean all_searched = FALSE;
    277 	gchar *body_str;
    278 
    279 	body_str = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(search_window.body_entry));
    280 	if (!body_str)
    281 		body_str = gtk_editable_get_chars(
    282 				GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(search_window.body_entry))),0,-1);
    283 	if (!body_str || *body_str == '\0') return;
    284 
    285 	/* add to history */
    286 	combobox_unset_popdown_strings(GTK_COMBO_BOX_TEXT(search_window.body_entry));
    287 	prefs_common.message_search_history = add_history(
    288 			prefs_common.message_search_history, body_str);
    289 	combobox_set_popdown_strings(GTK_COMBO_BOX_TEXT(search_window.body_entry),
    290 			prefs_common.message_search_history);
    291 
    292 	case_sens = gtk_toggle_button_get_active
    293 		(GTK_TOGGLE_BUTTON(search_window.case_checkbtn));
    294 
    295 	search_window.is_searching = TRUE;
    296 	message_show_stop_button();
    297 
    298 	for (; search_window.is_searching;) {
    299 		gchar *str;
    300 		AlertValue val;
    301 
    302 		if (backward) {
    303 			if (search_window.interface->search_string_backward
    304 				(interface_obj, body_str, case_sens) == TRUE)
    305 				break;
    306 		} else {
    307 			if (search_window.interface->search_string
    308 				(interface_obj, body_str, case_sens) == TRUE)
    309 				break;
    310 		}
    311 
    312 		if (all_searched) {
    313 			alertpanel_full(_("Search failed"),
    314 					_("Search string not found."),
    315 				        "window-close-symbolic", _("_Close"), NULL, NULL, NULL, NULL,
    316 				        FALSE, ALERTFOCUS_FIRST, NULL, ALERT_WARNING);
    317 			break;
    318 		}
    319 
    320 		all_searched = TRUE;
    321 
    322 		if (backward)
    323 			str = _("Beginning of message reached; "
    324 				"continue from end?");
    325 		else
    326 			str = _("End of message reached; "
    327 				"continue from beginning?");
    328 
    329 		val = alertpanel(_("Search finished"), str,
    330 				 NULL, _("_No"), NULL, _("_Yes"), NULL, NULL,
    331 				 ALERTFOCUS_SECOND);
    332 		if (G_ALERTALTERNATE == val) {
    333 			manage_window_focus_in(search_window.window,
    334 					       NULL, NULL);
    335 			search_window.interface->set_position(interface_obj,
    336 							backward ? -1 : 0);
    337 		} else
    338 			break;
    339 	}
    340 
    341 	search_window.is_searching = FALSE;
    342 	message_hide_stop_button();
    343 	g_free(body_str);
    344 }
    345 
    346 static void body_changed(void)
    347 {
    348 	if (!search_window.body_entry_has_focus)
    349 		gtk_widget_grab_focus(search_window.body_entry);
    350 }
    351 
    352 static gboolean body_entry_focus_evt_in(GtkWidget *widget, GdkEventFocus *event,
    353 			      	  gpointer data)
    354 {
    355 	search_window.body_entry_has_focus = TRUE;
    356 	return FALSE;
    357 }
    358 
    359 static gboolean body_entry_focus_evt_out(GtkWidget *widget, GdkEventFocus *event,
    360 			      	  gpointer data)
    361 {
    362 	search_window.body_entry_has_focus = FALSE;
    363 	return FALSE;
    364 }
    365 
    366 static void message_search_prev_clicked(GtkButton *button, gpointer data)
    367 {
    368 	message_search_execute(TRUE);
    369 }
    370 
    371 static void message_search_next_clicked(GtkButton *button, gpointer data)
    372 {
    373 	message_search_execute(FALSE);
    374 }
    375 
    376 static gboolean key_pressed(GtkWidget *widget, GdkEventKey *event,
    377 			    gpointer data)
    378 {
    379 	if (event && (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter)) {
    380 		message_search_execute(FALSE);
    381 	}
    382 	return FALSE;
    383 }
    384 
    385 static void message_search_stop_clicked(GtkButton *button, gpointer data)
    386 {
    387 	search_window.is_searching = FALSE;
    388 }