1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  * Copyright (C) 1997-2013 Stuart Parmenter and others,
4  *                         See the file AUTHORS for a list.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 #include "balsa-mime-widget-callbacks.h"
26 
27 #include <string.h>
28 #include "balsa-app.h"
29 #include <glib/gi18n.h>
30 #include "libbalsa-vfs.h"
31 #include "balsa-message.h"
32 #include "balsa-mime-widget.h"
33 
34 #include <gdk/gdkkeysyms.h>
35 
36 #if HAVE_MACOSX_DESKTOP
37 #  include "macosx-helpers.h"
38 #endif
39 
40 
41 void
balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item,LibBalsaMessageBody * mime_body)42 balsa_mime_widget_ctx_menu_cb(GtkWidget * menu_item,
43 			      LibBalsaMessageBody * mime_body)
44 {
45     GError *err = NULL;
46     gboolean result;
47 
48     g_return_if_fail(mime_body != NULL);
49     result = libbalsa_vfs_launch_app_for_body(mime_body,
50                                               G_OBJECT(menu_item),
51                                               &err);
52     if (!result)
53         balsa_information(LIBBALSA_INFORMATION_WARNING,
54                           _("Could not launch application: %s"),
55                           err ? err->message : "Unknown error");
56     g_clear_error(&err);
57 }
58 
59 
60 /** Pops up a "save part" dialog for a message part.
61 
62     @param parent_widget the widget located in the window that is to
63     became a parent for the dialog box.
64 
65     @param mime_body message part to be saved.
66 */
67 void
balsa_mime_widget_ctx_menu_save(GtkWidget * parent_widget,LibBalsaMessageBody * mime_body)68 balsa_mime_widget_ctx_menu_save(GtkWidget * parent_widget,
69 				LibBalsaMessageBody * mime_body)
70 {
71     gchar *cont_type, *title;
72     GtkWidget *save_dialog;
73     gchar *file_uri;
74     LibbalsaVfs *save_file;
75     gboolean do_save;
76     GError *err = NULL;
77 
78     g_return_if_fail(mime_body != NULL);
79 
80     cont_type = libbalsa_message_body_get_mime_type(mime_body);
81     title = g_strdup_printf(_("Save %s MIME Part"), cont_type);
82 
83     save_dialog =
84 	gtk_file_chooser_dialog_new(title,
85                                     balsa_get_parent_window(parent_widget),
86 				    GTK_FILE_CHOOSER_ACTION_SAVE,
87 				    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
88 				    GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
89 #if HAVE_MACOSX_DESKTOP
90     libbalsa_macosx_menu_for_parent(save_dialog, balsa_get_parent_window(parent_widget));
91 #endif
92     gtk_dialog_set_default_response(GTK_DIALOG(save_dialog),
93 				    GTK_RESPONSE_OK);
94     g_free(title);
95     g_free(cont_type);
96 
97     gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(save_dialog),
98                                     libbalsa_vfs_local_only());
99     if (balsa_app.save_dir)
100         gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(save_dialog),
101                                                 balsa_app.save_dir);
102 
103     if (mime_body->filename) {
104         gchar * filename = g_strdup(mime_body->filename);
105 	libbalsa_utf8_sanitize(&filename, balsa_app.convert_unknown_8bit,
106 			       NULL);
107 	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(save_dialog),
108 					  filename);
109 	g_free(filename);
110     }
111 
112     gtk_window_set_modal(GTK_WINDOW(save_dialog), TRUE);
113     if (gtk_dialog_run(GTK_DIALOG(save_dialog)) != GTK_RESPONSE_OK) {
114 	gtk_widget_destroy(save_dialog);
115 	return;
116     }
117 
118     /* get the file name */
119     file_uri = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(save_dialog));
120     gtk_widget_destroy(save_dialog);
121     if (!(save_file = libbalsa_vfs_new_from_uri(file_uri))) {
122         balsa_information(LIBBALSA_INFORMATION_ERROR,
123                           _("Could not construct URI from %s"),
124                           file_uri);
125         g_free(file_uri);
126 	return;
127     }
128 
129     /* remember the folder uri */
130     g_free(balsa_app.save_dir);
131     balsa_app.save_dir = g_strdup(libbalsa_vfs_get_folder(save_file));
132 
133     /* get a confirmation to overwrite if the file exists */
134     if (libbalsa_vfs_file_exists(save_file)) {
135 	GtkWidget *confirm;
136 
137 	/* File exists. check if they really want to overwrite */
138 	confirm = gtk_message_dialog_new(GTK_WINDOW(balsa_app.main_window),
139 					 GTK_DIALOG_MODAL,
140 					 GTK_MESSAGE_QUESTION,
141 					 GTK_BUTTONS_YES_NO,
142 					 _("File already exists. Overwrite?"));
143 #if HAVE_MACOSX_DESKTOP
144 	libbalsa_macosx_menu_for_parent(confirm, GTK_WINDOW(balsa_app.main_window));
145 #endif
146 	do_save =
147 	    (gtk_dialog_run(GTK_DIALOG(confirm)) == GTK_RESPONSE_YES);
148 	gtk_widget_destroy(confirm);
149 	if (do_save)
150 	    if (libbalsa_vfs_file_unlink(save_file, &err) != 0)
151                 balsa_information(LIBBALSA_INFORMATION_ERROR,
152                                   _("Unlink %s: %s"),
153                                   file_uri, err ? err->message : "Unknown error");
154     } else
155 	do_save = TRUE;
156 
157     /* save the file */
158     if (do_save) {
159 	if (!libbalsa_message_body_save_vfs(mime_body, save_file,
160                                             LIBBALSA_MESSAGE_BODY_UNSAFE,
161                                             mime_body->body_type ==
162                                             LIBBALSA_MESSAGE_BODY_TYPE_TEXT,
163                                             &err)) {
164 	    balsa_information(LIBBALSA_INFORMATION_ERROR,
165 			      _("Could not save %s: %s"),
166 			      file_uri, err ? err->message : "Unknown error");
167             g_clear_error(&err);
168         }
169     }
170 
171     g_object_unref(save_file);
172     g_free(file_uri);
173 }
174 
175 static void
scroll_change(GtkAdjustment * adj,gint diff,BalsaMessage * bm)176 scroll_change(GtkAdjustment * adj, gint diff, BalsaMessage * bm)
177 {
178     gdouble upper =
179         gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj);
180     gdouble value;
181 
182     if (bm && gtk_adjustment_get_value(adj) >= upper && diff > 0) {
183         if (balsa_window_next_unread(balsa_app.main_window))
184             /* We're changing mailboxes, and GtkNotebook will grab the
185              * focus, so we want to grab it back the next time we lose
186              * it. */
187             bm->focus_state = BALSA_MESSAGE_FOCUS_STATE_HOLD;
188         return;
189     }
190 
191     value = gtk_adjustment_get_value(adj);
192     value += (gdouble) diff;
193 
194     gtk_adjustment_set_value(adj, MIN(value, upper));
195 }
196 
197 gint
balsa_mime_widget_key_press_event(GtkWidget * widget,GdkEventKey * event,BalsaMessage * bm)198 balsa_mime_widget_key_press_event(GtkWidget * widget, GdkEventKey * event,
199 				  BalsaMessage * bm)
200 {
201     GtkAdjustment *adj;
202     int page_adjust;
203 
204     adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW
205                                               (bm->scroll));
206 
207     page_adjust = balsa_app.pgdownmod ?
208         (gtk_adjustment_get_page_size(adj) * balsa_app.pgdown_percent) /
209         100 : gtk_adjustment_get_page_increment(adj);
210 
211     switch (event->keyval) {
212     case GDK_KEY_Up:
213         scroll_change(adj, -gtk_adjustment_get_step_increment(adj), NULL);
214         break;
215     case GDK_KEY_Down:
216         scroll_change(adj, gtk_adjustment_get_step_increment(adj), NULL);
217         break;
218     case GDK_KEY_Page_Up:
219         scroll_change(adj, -page_adjust, NULL);
220         break;
221     case GDK_KEY_Page_Down:
222         scroll_change(adj, page_adjust, NULL);
223         break;
224     case GDK_KEY_Home:
225         if (event->state & GDK_CONTROL_MASK)
226             scroll_change(adj, -gtk_adjustment_get_value(adj), NULL);
227         else
228             return FALSE;
229         break;
230     case GDK_KEY_End:
231         if (event->state & GDK_CONTROL_MASK)
232             scroll_change(adj, gtk_adjustment_get_upper(adj), NULL);
233         else
234             return FALSE;
235         break;
236     case GDK_KEY_F10:
237         if (event->state & GDK_SHIFT_MASK) {
238 	    GtkWidget *current_widget = balsa_message_current_part_widget(bm);
239 
240 	    if (current_widget) {
241                 gboolean retval;
242 
243                 g_signal_emit_by_name(current_widget, "popup-menu", &retval);
244 
245                 return retval;
246             } else
247 		return FALSE;
248         } else
249             return FALSE;
250         break;
251     case GDK_KEY_space:
252         scroll_change(adj, page_adjust, bm);
253         break;
254 
255     default:
256         return FALSE;
257     }
258     return TRUE;
259 }
260 
261 
262 gint
balsa_mime_widget_limit_focus(GtkWidget * widget,GdkEventFocus * event,BalsaMessage * bm)263 balsa_mime_widget_limit_focus(GtkWidget * widget, GdkEventFocus * event, BalsaMessage * bm)
264 {
265     /* Disable can_focus on other message parts so that TAB does not
266      * attempt to move the focus on them. */
267     GList *list = g_list_append(NULL, widget);
268 
269     gtk_container_set_focus_chain(GTK_CONTAINER(bm->bm_widget->container), list);
270     g_list_free(list);
271     if (bm->focus_state == BALSA_MESSAGE_FOCUS_STATE_NO)
272         bm->focus_state = BALSA_MESSAGE_FOCUS_STATE_YES;
273     return FALSE;
274 }
275 
276 
277 gint
balsa_mime_widget_unlimit_focus(GtkWidget * widget,GdkEventFocus * event,BalsaMessage * bm)278 balsa_mime_widget_unlimit_focus(GtkWidget * widget, GdkEventFocus * event, BalsaMessage * bm)
279 {
280     gtk_container_unset_focus_chain(GTK_CONTAINER(bm->bm_widget->container));
281     if (bm->message) {
282         BalsaMessageFocusState focus_state = bm->focus_state;
283         if (focus_state == BALSA_MESSAGE_FOCUS_STATE_HOLD) {
284             balsa_message_grab_focus(bm);
285             bm->focus_state = BALSA_MESSAGE_FOCUS_STATE_YES;
286         } else
287             bm->focus_state = BALSA_MESSAGE_FOCUS_STATE_NO;
288     }
289     return FALSE;
290 }
291