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 }