2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2022 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/>.
20 #include "claws-features.h"
25 #include <glib/gi18n.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <sys/types.h>
30 #include "manage_window.h"
32 #include "mainwindow.h"
34 #include "alertpanel.h"
37 #include "prefs_common.h"
47 static struct URIOpener
50 GtkWidget
*hbox_scroll
;
55 GtkWidget
*scrolledwin
;
62 static void uri_opener_list_copy_cb(gpointer action
, gpointer data
);
64 static GtkActionGroup
*uri_opener_popup_action
= NULL
;
65 static GtkWidget
*uri_opener_popup_menu
= NULL
;
67 static GtkActionEntry uri_opener_popup_entries
[] =
69 {"UriOpenerPopup", NULL
, "UriOpenerPopup", NULL
, NULL
, NULL
},
70 {"UriOpenerPopup/Copy", NULL
, N_("C_opy URL"), NULL
, NULL
, G_CALLBACK(uri_opener_list_copy_cb
) }
73 static void uri_opener_load_uris (void);
74 static void uri_opener_open_cb (GtkWidget
*widget
, gpointer data
);
75 static void uri_opener_close_cb (GtkWidget
*widget
, gpointer data
);
76 static void uri_opener_select_all_cb (GtkWidget
*widget
, gpointer data
);
77 static gboolean
key_pressed (GtkWidget
*widget
, GdkEventKey
*event
,
79 static void uri_opener_double_clicked(GtkTreeView
*list_view
,
81 GtkTreeViewColumn
*column
,
83 static void uri_opener_create(void);
84 static gint
uri_opener_list_btn_pressed(GtkWidget
*widget
, GdkEventButton
*event
,
85 GtkTreeView
*list_view
);
86 static gboolean
uri_opener_list_popup_menu(GtkWidget
*widget
, gpointer data
);
88 void uri_opener_open(MessageView
*msgview
, GSList
*uris
)
90 cm_return_if_fail(msgview
);
91 cm_return_if_fail(msgview
->mimeview
);
92 cm_return_if_fail(msgview
->mimeview
->textview
);
97 manage_window_set_transient(GTK_WINDOW(opener
.window
));
98 gtk_widget_grab_focus(opener
.close_btn
);
101 alertpanel_notice(_("There are no URLs in this email."));
105 opener
.msgview
= msgview
;
106 opener
.uris
= g_slist_copy(uris
);
107 uri_opener_load_uris();
109 gtk_widget_show(opener
.window
);
110 gtk_widget_grab_focus(opener
.urilist
);
111 gtk_window_set_modal(GTK_WINDOW(opener
.window
), TRUE
);
114 static GtkListStore
* uri_opener_create_data_store(void)
116 return gtk_list_store_new(N_URI_OPENER_COLUMNS
,
122 static void uri_opener_create_list_view_columns(GtkWidget
*list_view
)
124 GtkTreeViewColumn
*column
;
125 GtkCellRenderer
*renderer
;
127 renderer
= gtk_cell_renderer_text_new();
128 column
= gtk_tree_view_column_new_with_attributes
129 (_("Included URLs:"),
131 "markup", URI_OPENER_URL
,
133 gtk_tree_view_append_column(GTK_TREE_VIEW(list_view
), column
);
136 static GtkWidget
*uri_opener_list_view_create(void)
138 GtkTreeView
*list_view
;
139 GtkTreeSelection
*selector
;
142 model
= GTK_TREE_MODEL(uri_opener_create_data_store());
143 list_view
= GTK_TREE_VIEW(gtk_tree_view_new_with_model(model
));
144 g_object_unref(model
);
146 gtk_tree_view_set_rules_hint(list_view
, prefs_common
.use_stripes_everywhere
);
148 selector
= gtk_tree_view_get_selection(list_view
);
149 gtk_tree_selection_set_mode(selector
, GTK_SELECTION_MULTIPLE
);
151 g_signal_connect(G_OBJECT(list_view
), "row_activated",
152 G_CALLBACK(uri_opener_double_clicked
),
154 g_signal_connect(G_OBJECT(list_view
), "popup-menu",
155 G_CALLBACK(uri_opener_list_popup_menu
),
157 g_signal_connect(G_OBJECT(list_view
), "button-press-event",
158 G_CALLBACK(uri_opener_list_btn_pressed
),
161 /* create the columns */
162 uri_opener_create_list_view_columns(GTK_WIDGET(list_view
));
164 return GTK_WIDGET(list_view
);
168 static GtkWidget
*uri_opener_scrolled_win_create(void)
170 GtkWidget
*scrolledwin
;
172 scrolledwin
= gtk_scrolled_window_new(NULL
, NULL
);
173 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolledwin
),
174 GTK_SHADOW_ETCHED_IN
);
175 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin
),
176 GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
);
178 gtk_widget_set_size_request(scrolledwin
, 200, 250);
179 gtk_widget_show(scrolledwin
);
184 static void uri_opener_size_allocate_cb(GtkWidget
*widget
, GtkAllocation
*allocation
)
186 cm_return_if_fail(allocation
!= NULL
);
188 gtk_window_get_size(GTK_WINDOW(widget
),
189 &prefs_common
.uriopenerwin_width
, &prefs_common
.uriopenerwin_height
);
192 static void uri_opener_create(void)
195 GtkWidget
*hbox_scroll
;
201 GtkWidget
*select_all_btn
;
203 GtkWidget
*close_btn
;
204 GtkWidget
*scrolledwin
;
205 static GdkGeometry geometry
;
207 window
= gtkut_window_new(GTK_WINDOW_TOPLEVEL
, "uri_opener");
208 gtk_window_set_title (GTK_WINDOW(window
),
209 C_("Dialog title", "Open URLs"));
211 gtk_container_set_border_width (GTK_CONTAINER (window
), 8);
212 gtk_window_set_position (GTK_WINDOW (window
), GTK_WIN_POS_CENTER
);
213 gtk_window_set_resizable(GTK_WINDOW (window
), TRUE
);
214 gtk_window_set_type_hint(GTK_WINDOW(window
), GDK_WINDOW_TYPE_HINT_DIALOG
);
215 g_signal_connect(G_OBJECT(window
), "delete_event",
216 G_CALLBACK(uri_opener_close_cb
), NULL
);
217 g_signal_connect (G_OBJECT(window
), "size_allocate",
218 G_CALLBACK (uri_opener_size_allocate_cb
), NULL
);
219 g_signal_connect(G_OBJECT(window
), "key_press_event",
220 G_CALLBACK(key_pressed
), NULL
);
221 MANAGE_WINDOW_SIGNALS_CONNECT (window
);
223 vbox1
= gtk_box_new(GTK_ORIENTATION_VERTICAL
, 6);
224 gtkut_stock_button_set_create(&hbox1
,
225 &open_btn
, NULL
, _("Open in browser"),
226 &close_btn
, NULL
, _("Close"),
229 g_signal_connect(G_OBJECT(open_btn
), "clicked",
230 G_CALLBACK(uri_opener_open_cb
), NULL
);
232 g_signal_connect(G_OBJECT(close_btn
), "clicked",
233 G_CALLBACK(uri_opener_close_cb
), NULL
);
235 urilist
= uri_opener_list_view_create();
237 label
= gtk_label_new(_("Any phishing URLs are shown in red, followed by the actual URL."));
238 gtk_label_set_xalign(GTK_LABEL(label
), 0.0);
239 gtk_box_pack_start(GTK_BOX(vbox1
), label
, FALSE
, TRUE
, 0);
241 scrolledwin
= uri_opener_scrolled_win_create();
242 hbox_scroll
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
243 gtk_box_pack_start(GTK_BOX(hbox_scroll
), scrolledwin
, TRUE
, TRUE
, 0);
245 select_all_btn
= gtk_button_new_with_label(_("Select All"));
246 g_signal_connect(G_OBJECT(select_all_btn
), "clicked",
247 G_CALLBACK(uri_opener_select_all_cb
), NULL
);
249 hbox
= gtk_box_new(GTK_ORIENTATION_HORIZONTAL
, 0);
250 gtk_box_pack_start(GTK_BOX(hbox
), select_all_btn
, FALSE
, FALSE
, 0);
251 gtk_box_pack_start(GTK_BOX(hbox
), gtk_label_new(""), TRUE
, TRUE
, 0);
253 gtk_container_add(GTK_CONTAINER(scrolledwin
), urilist
);
254 gtk_box_pack_start(GTK_BOX(vbox1
), hbox_scroll
, TRUE
, TRUE
, 0);
255 gtk_box_pack_start(GTK_BOX(vbox1
), hbox
, FALSE
, FALSE
, 0);
256 gtk_box_pack_start(GTK_BOX(vbox1
), hbox1
, FALSE
, FALSE
, 0);
258 gtk_widget_show_all(vbox1
);
259 gtk_container_add(GTK_CONTAINER (window
), vbox1
);
261 if (!geometry
.min_height
) {
262 geometry
.min_width
= 450;
263 geometry
.min_height
= 300;
266 gtk_window_set_geometry_hints(GTK_WINDOW(window
), NULL
, &geometry
,
268 gtk_window_set_default_size(GTK_WINDOW(window
),
269 prefs_common
.uriopenerwin_width
,
270 prefs_common
.uriopenerwin_height
);
272 opener
.window
= window
;
273 opener
.hbox_scroll
= hbox_scroll
;
274 opener
.hbox1
= hbox1
;
275 opener
.vbox1
= vbox1
;
276 opener
.label
= label
;
277 opener
.urilist
= urilist
;
278 opener
.scrolledwin
= scrolledwin
;
279 opener
.open_btn
= open_btn
;
280 opener
.close_btn
= close_btn
;
284 static void uri_opener_list_view_insert_uri(GtkWidget
*list_view
,
285 GtkTreeIter
*row_iter
,
289 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
290 (GTK_TREE_VIEW(list_view
)));
291 gchar
*visible
= textview_get_visible_uri(opener
.msgview
->mimeview
->textview
, uri
);
295 if (visible
&& strcmp(visible
, uri
->uri
)) {
296 gboolean phishing_attempt
= FALSE
;
297 if (strcmp(visible
, uri
->uri
) != 0 && is_uri_string(visible
)) {
299 gchar
*visible_uri_path
;
301 uri_path
= get_uri_path(uri
->uri
);
302 visible_uri_path
= get_uri_path(visible
);
303 if (path_cmp(uri_path
, visible_uri_path
) != 0)
304 phishing_attempt
= TRUE
;
306 if (phishing_attempt
) {
307 label
= g_markup_printf_escaped("<span color=\"%s\"><b>%s</b></span>\n%s",
308 gtkut_gdk_rgba_to_string(&prefs_common
.color
[COL_LOG_ERROR
]), visible
, uri
->uri
);
310 label
= g_markup_printf_escaped("%s", uri
->uri
);
313 label
= g_markup_printf_escaped("%s", uri
->uri
);
315 if (row_iter
== NULL
) {
317 gtk_list_store_append(list_store
, &iter
);
318 gtk_list_store_set(list_store
, &iter
,
319 URI_OPENER_URL
, label
,
320 URI_OPENER_DATA
, uri
,
323 gtk_list_store_set(list_store
, row_iter
,
324 URI_OPENER_URL
, label
,
325 URI_OPENER_DATA
, uri
,
332 static void uri_opener_list_view_clear_uris(GtkWidget
*list_view
)
334 GtkListStore
*list_store
= GTK_LIST_STORE(gtk_tree_view_get_model
335 (GTK_TREE_VIEW(list_view
)));
336 gtk_list_store_clear(list_store
);
339 static void uri_opener_load_uris(void)
341 GSList
*cur
= opener
.uris
;
343 GtkTreeSelection
*selection
;
346 uri_opener_list_view_clear_uris(opener
.urilist
);
347 for (; cur
; cur
= cur
->next
) {
348 ClickableText
*uri
= (ClickableText
*)cur
->data
;
349 uri_opener_list_view_insert_uri(opener
.urilist
, NULL
, uri
);
352 g_object_ref(opener
.urilist
);
353 gtk_container_remove(GTK_CONTAINER(opener
.scrolledwin
), opener
.urilist
);
354 gtk_widget_destroy(opener
.scrolledwin
);
356 opener
.scrolledwin
= uri_opener_scrolled_win_create();
357 gtk_container_add(GTK_CONTAINER(opener
.scrolledwin
), opener
.urilist
);
358 gtk_box_pack_start(GTK_BOX(opener
.hbox_scroll
),
359 opener
.scrolledwin
, TRUE
, TRUE
, 0);
360 g_object_unref(opener
.urilist
);
362 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(opener
.urilist
));
363 if (!gtk_tree_model_get_iter_first(model
, &iter
))
365 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(opener
.urilist
));
366 gtk_tree_selection_select_iter(selection
, &iter
);
369 static void uri_opener_close(void)
371 g_slist_free(opener
.uris
);
373 gtk_window_set_modal(GTK_WINDOW(opener
.window
), FALSE
);
374 gtk_widget_hide(opener
.window
);
377 static void uri_opener_close_cb(GtkWidget
*widget
,
383 static gboolean
key_pressed(GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
386 if (event
->keyval
== GDK_KEY_Escape
)
389 if ((event
->keyval
== GDK_KEY_c
|| event
->keyval
== GDK_KEY_x
) &&
390 (event
->state
& GDK_CONTROL_MASK
)) {
391 uri_opener_list_copy_cb(NULL
, NULL
);
399 static void uri_opener_double_clicked(GtkTreeView
*list_view
,
401 GtkTreeViewColumn
*column
,
406 GtkTreeModel
*model
= gtk_tree_view_get_model(list_view
);
408 if (!gtk_tree_model_get_iter(model
, &iter
, path
))
411 gtk_tree_model_get(model
, &iter
,
412 URI_OPENER_DATA
, &uri
,
418 if (textview_uri_security_check(opener
.msgview
->mimeview
->textview
, uri
,
421 prefs_common_get_uri_cmd());
424 static void uri_opener_open_cb(GtkWidget
*widget
,
430 GtkTreeSelection
*selection
;
431 GList
*selected
, *cur
;
433 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(opener
.urilist
));
434 selected
= gtk_tree_selection_get_selected_rows(selection
, &model
);
435 cm_return_if_fail(selected
);
437 for(cur
= selected
; cur
!= NULL
; cur
= g_list_next(cur
))
439 if(!gtk_tree_model_get_iter(model
, &sel
, (GtkTreePath
*)cur
->data
))
442 gtk_tree_model_get(model
, &sel
,
443 URI_OPENER_DATA
, &uri
,
448 if (textview_uri_security_check(opener
.msgview
->mimeview
->textview
, uri
,
451 prefs_common_get_uri_cmd());
454 g_list_foreach(selected
, (GFunc
)gtk_tree_path_free
, NULL
);
455 g_list_free(selected
);
458 static void uri_opener_select_all_cb(GtkWidget
*widget
,
461 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(
462 GTK_TREE_VIEW(opener
.urilist
));
463 gtk_tree_selection_select_all(selection
);
466 static void uri_opener_list_copy_cb(gpointer action
, gpointer data
)
471 GtkTreeSelection
*selection
;
472 GList
*selected
, *cur
;
473 GString
*uri_list_str
= NULL
;
475 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(opener
.urilist
));
476 selected
= gtk_tree_selection_get_selected_rows(selection
, &model
);
477 cm_return_if_fail(selected
);
479 for(cur
= selected
; cur
!= NULL
; cur
= g_list_next(cur
))
481 if(!gtk_tree_model_get_iter(model
, &sel
, (GtkTreePath
*)cur
->data
))
484 gtk_tree_model_get(model
, &sel
,
485 URI_OPENER_DATA
, &uri
,
491 uri_list_str
= g_string_new((const gchar
*)uri
->uri
);
493 g_string_append_printf(uri_list_str
, "\n%s", uri
->uri
);
496 GtkClipboard
*clip
, *clip2
;
498 if (textview_uri_security_check(opener
.msgview
->mimeview
->textview
, uri
,
500 clip
= gtk_widget_get_clipboard (opener
.window
, GDK_SELECTION_PRIMARY
);
501 clip2
= gtk_widget_get_clipboard (opener
.window
, GDK_SELECTION_CLIPBOARD
);
502 gtk_clipboard_set_text (clip
, uri_list_str
->str
, uri_list_str
->len
);
503 gtk_clipboard_set_text (clip2
, uri_list_str
->str
, uri_list_str
->len
);
505 g_string_free(uri_list_str
, TRUE
);
508 g_list_foreach(selected
, (GFunc
)gtk_tree_path_free
, NULL
);
509 g_list_free(selected
);
512 static gint
uri_opener_list_btn_pressed(GtkWidget
*widget
, GdkEventButton
*event
,
513 GtkTreeView
*list_view
)
516 /* right-button click */
517 if (event
->button
== 3) {
518 GtkTreeModel
*model
= gtk_tree_view_get_model(list_view
);
519 GtkTreeSelection
*selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(list_view
));
520 GList
*selected
= gtk_tree_selection_get_selected_rows(selection
, &model
);
522 if (!uri_opener_popup_menu
) {
523 uri_opener_popup_action
= cm_menu_create_action_group("UriOpenerPopup", uri_opener_popup_entries
,
524 G_N_ELEMENTS(uri_opener_popup_entries
), (gpointer
)list_view
);
525 MENUITEM_ADDUI("/Menus", "UriOpenerPopup", "UriOpenerPopup", GTK_UI_MANAGER_MENU
)
526 MENUITEM_ADDUI("/Menus/UriOpenerPopup", "Copy", "UriOpenerPopup/Copy", GTK_UI_MANAGER_MENUITEM
)
527 uri_opener_popup_menu
= gtk_menu_item_get_submenu(GTK_MENU_ITEM(
528 gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/UriOpenerPopup")) );
531 /* grey out some popup menu item if there is no selected row */
532 cm_menu_set_sensitive("UriOpenerPopup/Copy", (selected
!= NULL
));
534 gtk_menu_popup_at_pointer(GTK_MENU(uri_opener_popup_menu
), NULL
);
540 static gboolean
uri_opener_list_popup_menu(GtkWidget
*widget
, gpointer data
)
542 GtkTreeView
*list_view
= (GtkTreeView
*)data
;
543 GdkEventButton event
;
546 event
.time
= gtk_get_current_event_time();
548 uri_opener_list_btn_pressed(NULL
, &event
, list_view
);