1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3 * Copyright (C) 1998-2013 Stuart Parmenter and others, see AUTHORS file.
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 2, or (at your option)
8 * 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 /* FONT SELECTION DISCUSSION:
20 We use pango now.
21 Locale data is then used exclusively for the spelling checking.
22 */
23
24
25 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
26 # include "config.h"
27 #endif /* HAVE_CONFIG_H */
28 #include "sendmsg-window.h"
29
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #define GNOME_PAD_SMALL 4
34 #include <gio/gio.h>
35 #include <glib/gi18n.h>
36 #include <glib/gstdio.h>
37 #include <ctype.h>
38 #include <glib.h>
39
40 #ifdef HAVE_LOCALE_H
41 #include <locale.h>
42 #endif
43
44 #include <sys/wait.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #ifdef HAVE_UNISTD_H
49 #include <unistd.h>
50 #endif
51 #include <errno.h>
52 #include "libbalsa.h"
53 #include "misc.h"
54 #include "send.h"
55 #include "html.h"
56
57 #include "balsa-app.h"
58 #include "balsa-message.h"
59 #include "balsa-index.h"
60 #include "balsa-icons.h"
61
62 #ifdef BALSA_USE_THREADS
63 #include <pthread.h>
64 #include "threads.h"
65 #endif
66
67 #include "missing.h"
68 #include "ab-window.h"
69 #include "address-view.h"
70 #include "print.h"
71 #include "macosx-helpers.h"
72
73 #include <enchant/enchant.h>
74 #if HAVE_GTKSPELL
75 #include "gtkspell/gtkspell.h"
76 #else /* HAVE_GTKSPELL */
77 #include "spell-check.h"
78 #endif /* HAVE_GTKSPELL */
79 #if HAVE_GTKSOURCEVIEW
80 #include <gtksourceview/gtksource.h>
81 #endif /* HAVE_GTKSOURCEVIEW */
82
83 #define GNOME_MIME_BUG_WORKAROUND 1
84 typedef struct {
85 pid_t pid_editor;
86 gchar *filename;
87 BalsaSendmsg *bsmsg;
88 } balsa_edit_with_gnome_data;
89
90 typedef enum { QUOTE_HEADERS, QUOTE_ALL, QUOTE_NOPREFIX } QuoteType;
91
92 static void include_file_cb (GtkAction * action, BalsaSendmsg * bsmsg);
93 static void toolbar_send_message_cb
94 (GtkAction * action, BalsaSendmsg * bsmsg);
95 static void send_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
96 static void queue_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
97 static void save_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
98
99 static gint message_postpone(BalsaSendmsg * bsmsg);
100 static void postpone_message_cb(GtkAction * action, BalsaSendmsg * bsmsg);
101
102 #if !defined(ENABLE_TOUCH_UI)
103 static void page_setup_cb (GtkAction * action, BalsaSendmsg * bsmsg);
104 #endif /* ENABLE_TOUCH_UI */
105 static void print_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
106 static void attach_clicked (GtkAction * action, gpointer data);
107
108 static gboolean attach_message(BalsaSendmsg *bsmsg, LibBalsaMessage *message);
109 static void insert_selected_messages(BalsaSendmsg * bsmsg, QuoteType type);
110 static void attach_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
111 static void include_message_cb (GtkAction * action, BalsaSendmsg * bsmsg);
112
113 static void close_window_cb (GtkAction * action, gpointer data);
114
115 static void balsa_sendmsg_destroy_handler(BalsaSendmsg * bsmsg);
116 static void check_readiness(BalsaSendmsg * bsmsg);
117 static void init_menus(BalsaSendmsg *);
118 static void toggle_from_cb (GtkToggleAction * toggle_action,
119 BalsaSendmsg * bsmsg);
120 static void toggle_recipients_cb (GtkToggleAction * toggle_action,
121 BalsaSendmsg * bsmsg);
122 #if !defined(ENABLE_TOUCH_UI)
123 static void toggle_replyto_cb (GtkToggleAction * toggle_action,
124 BalsaSendmsg * bsmsg);
125 #endif /* ENABLE_TOUCH_UI */
126 static void toggle_fcc_cb (GtkToggleAction * toggle_action,
127 BalsaSendmsg * bsmsg);
128 static void toggle_reqdispnotify_cb(GtkToggleAction * toggle_action,
129 BalsaSendmsg * bsmsg);
130 static void sw_show_toolbar_cb (GtkToggleAction * action,
131 BalsaSendmsg * bsmsg);
132 static void toggle_format_cb (GtkToggleAction * toggle_action,
133 BalsaSendmsg * bsmsg);
134 static void toggle_mp_alt_cb (GtkToggleAction * toggle_action,
135 BalsaSendmsg * bsmsg);
136 #ifdef HAVE_GPGME
137 static void toggle_sign_cb (GtkToggleAction * toggle_action,
138 BalsaSendmsg * bsmsg);
139 static void toggle_encrypt_cb (GtkToggleAction * toggle_action,
140 BalsaSendmsg * bsmsg);
141 #if !defined(ENABLE_TOUCH_UI)
142 static void gpg_mode_radio_cb(GtkRadioAction * action,
143 GtkRadioAction * current,
144 BalsaSendmsg * bsmsg);
145 #endif /* ENABLE_TOUCH_UI */
146 static void bsmsg_setup_gpg_ui(BalsaSendmsg *bsmsg);
147 static void bsmsg_update_gpg_ui_on_ident_change(BalsaSendmsg *bsmsg,
148 LibBalsaIdentity *new_ident);
149 static void bsmsg_setup_gpg_ui_by_mode(BalsaSendmsg *bsmsg, gint mode);
150 #endif
151
152 #if defined(ENABLE_TOUCH_UI)
153 static gboolean bsmsg_check_format_compatibility(GtkWindow *parent,
154 const char *filename);
155 #endif /* ENABLE_TOUCH_UI */
156
157 #if !HAVE_GTKSPELL
158 static void spell_check_cb(GtkAction * action, BalsaSendmsg * bsmsg);
159 static void sw_spell_check_response(BalsaSpellCheck * spell_check,
160 gint response, BalsaSendmsg * bsmsg);
161 #else
162 static void spell_check_menu_cb(GtkToggleAction * action,
163 BalsaSendmsg * bsmsg);
164 #endif /* HAVE_GTKSPELL */
165
166 static void address_book_cb(LibBalsaAddressView * address_view,
167 GtkTreeRowReference * row_ref,
168 BalsaSendmsg * bsmsg);
169 static void address_book_response(GtkWidget * ab, gint response,
170 LibBalsaAddressView * address_view);
171
172 static void set_locale(BalsaSendmsg * bsmsg, gint idx);
173
174 #if !defined(ENABLE_TOUCH_UI)
175 static void edit_with_gnome(GtkAction * action, BalsaSendmsg* bsmsg);
176 #endif
177 static void change_identity_dialog_cb(GtkAction * action,
178 BalsaSendmsg * bsmsg);
179 static void replace_identity_signature(BalsaSendmsg* bsmsg,
180 LibBalsaIdentity* new_ident,
181 LibBalsaIdentity* old_ident,
182 gint* replace_offset, gint siglen,
183 const gchar* new_sig);
184 static void update_bsmsg_identity(BalsaSendmsg*, LibBalsaIdentity*);
185
186 static void sw_size_alloc_cb(GtkWidget * window, GtkAllocation * alloc);
187 static GString *quote_message_body(BalsaSendmsg * bsmsg,
188 LibBalsaMessage * message,
189 QuoteType type);
190 static void set_list_post_address(BalsaSendmsg * bsmsg);
191 static gboolean set_list_post_rfc2369(BalsaSendmsg * bsmsg,
192 const gchar * url);
193 static const gchar *rfc2822_skip_comments(const gchar * str);
194 static void sendmsg_window_set_title(BalsaSendmsg * bsmsg);
195
196 #if !HAVE_GTKSOURCEVIEW
197 /* Undo/Redo buffer helpers. */
198 static void sw_buffer_save(BalsaSendmsg * bsmsg);
199 static void sw_buffer_swap(BalsaSendmsg * bsmsg, gboolean undo);
200 #endif /* HAVE_GTKSOURCEVIEW */
201 static void sw_buffer_signals_connect(BalsaSendmsg * bsmsg);
202 #if !HAVE_GTKSOURCEVIEW || !HAVE_GTKSPELL
203 static void sw_buffer_signals_disconnect(BalsaSendmsg * bsmsg);
204 #endif /* !HAVE_GTKSOURCEVIEW || !HAVE_GTKSPELL */
205 #if !HAVE_GTKSOURCEVIEW
206 static void sw_buffer_set_undo(BalsaSendmsg * bsmsg, gboolean undo,
207 gboolean redo);
208 #endif /* HAVE_GTKSOURCEVIEW */
209
210 /* Standard DnD types */
211 enum {
212 TARGET_MESSAGES,
213 TARGET_URI_LIST,
214 TARGET_EMAIL,
215 TARGET_STRING
216 };
217
218 static GtkTargetEntry drop_types[] = {
219 {"x-application/x-message-list", GTK_TARGET_SAME_APP, TARGET_MESSAGES},
220 {"text/uri-list", 0, TARGET_URI_LIST},
221 { "STRING", 0, TARGET_STRING },
222 { "text/plain", 0, TARGET_STRING },
223 };
224
225 static GtkTargetEntry email_field_drop_types[] = {
226 {"x-application/x-email", 0, TARGET_EMAIL}
227 };
228
229 static void sw_undo_cb (GtkAction * action, BalsaSendmsg * bsmsg);
230 static void sw_redo_cb (GtkAction * action, BalsaSendmsg * bsmsg);
231 static void cut_cb (GtkAction * action, BalsaSendmsg * bsmsg);
232 static void copy_cb (GtkAction * action, BalsaSendmsg * bsmsg);
233 static void paste_cb (GtkAction * action, BalsaSendmsg * bsmsg);
234 static void select_all_cb (GtkAction * action, BalsaSendmsg * bsmsg);
235 static void wrap_body_cb (GtkAction * action, BalsaSendmsg * bsmsg);
236 static void reflow_selected_cb (GtkAction * action, BalsaSendmsg * bsmsg);
237 static void insert_signature_cb(GtkAction * action, BalsaSendmsg * bsmsg);
238 static void quote_messages_cb (GtkAction * action, BalsaSendmsg * bsmsg);
239 static void lang_set_cb(GtkWidget *widget, BalsaSendmsg *bsmsg);
240
241 static void bsmsg_set_subject_from_body(BalsaSendmsg * bsmsg,
242 LibBalsaMessageBody * body,
243 LibBalsaIdentity * ident);
244
245 /* the array of locale names and charset names included in the MIME
246 type information.
247 if you add a new encoding here add to SendCharset in libbalsa.c
248 */
249 struct SendLocales {
250 const gchar *locale, *charset, *lang_name;
251 } locales[] = {
252 /* Translators: please use the initial letter of each language as
253 * its accelerator; this is a long list, and unique accelerators
254 * cannot be found. */
255 {"pt_BR", "ISO-8859-1", N_("_Brazilian Portuguese")},
256 {"ca_ES", "ISO-8859-15", N_("_Catalan")},
257 {"zh_CN.GB2312", "gb2312", N_("_Chinese Simplified")},
258 {"zh_TW.Big5", "big5", N_("_Chinese Traditional")},
259 {"cs_CZ", "ISO-8859-2", N_("_Czech")},
260 {"da_DK", "ISO-8859-1", N_("_Danish")},
261 {"nl_NL", "ISO-8859-15", N_("_Dutch")},
262 {"en_US", "ISO-8859-1", N_("_English (American)")},
263 {"en_GB", "ISO-8859-1", N_("_English (British)")},
264 {"eo_XX", "UTF-8", N_("_Esperanto")},
265 {"et_EE", "ISO-8859-15", N_("_Estonian")},
266 {"fi_FI", "ISO-8859-15", N_("_Finnish")},
267 {"fr_FR", "ISO-8859-15", N_("_French")},
268 {"de_DE", "ISO-8859-15", N_("_German")},
269 {"de_AT", "ISO-8859-15", N_("_German (Austrian)")},
270 {"de_CH", "ISO-8859-1", N_("_German (Swiss)")},
271 {"el_GR", "ISO-8859-7", N_("_Greek")},
272 {"he_IL", "UTF-8", N_("_Hebrew")},
273 {"hu_HU", "ISO-8859-2", N_("_Hungarian")},
274 {"it_IT", "ISO-8859-15", N_("_Italian")},
275 {"ja_JP", "ISO-2022-JP", N_("_Japanese (JIS)")},
276 {"kk_KZ", "UTF-8", N_("_Kazakh")},
277 {"ko_KR", "euc-kr", N_("_Korean")},
278 {"lv_LV", "ISO-8859-13", N_("_Latvian")},
279 {"lt_LT", "ISO-8859-13", N_("_Lithuanian")},
280 {"no_NO", "ISO-8859-1", N_("_Norwegian")},
281 {"pl_PL", "ISO-8859-2", N_("_Polish")},
282 {"pt_PT", "ISO-8859-15", N_("_Portugese")},
283 {"ro_RO", "ISO-8859-2", N_("_Romanian")},
284 {"ru_RU", "KOI8-R", N_("_Russian")},
285 {"sr_Cyrl", "ISO-8859-5", N_("_Serbian")},
286 {"sr_Latn", "ISO-8859-2", N_("_Serbian (Latin)")},
287 {"sk_SK", "ISO-8859-2", N_("_Slovak")},
288 {"es_ES", "ISO-8859-15", N_("_Spanish")},
289 {"sv_SE", "ISO-8859-1", N_("_Swedish")},
290 {"tt_RU", "UTF-8", N_("_Tatar")},
291 {"tr_TR", "ISO-8859-9", N_("_Turkish")},
292 {"uk_UK", "KOI8-U", N_("_Ukrainian")},
293 {"", "UTF-8", N_("_Generic UTF-8")}
294 };
295
296 static const gchar *
sw_preferred_charset(BalsaSendmsg * bsmsg)297 sw_preferred_charset(BalsaSendmsg * bsmsg)
298 {
299 guint i;
300
301 for (i = 0; i < G_N_ELEMENTS(locales); i++)
302 if (bsmsg->spell_check_lang && locales[i].locale
303 && strcmp(bsmsg->spell_check_lang, locales[i].locale) == 0)
304 return locales[i].charset;
305
306 return NULL;
307 }
308
309 /* ===================================================================
310 Balsa menus. Touchpad has some simplified menus which do not
311 overlap very much with the default balsa menus. They are here
312 because they represent an alternative probably appealing to the all
313 proponents of GNOME2 dumbify approach (OK, I am bit unfair here).
314 */
315
316 static const GtkActionEntry entries[] = {
317 {"FileMenu", NULL, N_("_File")},
318 {"EditMenu", NULL, N_("_Edit")},
319 {"ShowMenu", NULL, N_("_Show")},
320 {"LanguageMenu", NULL, N_("_Language")},
321 {"OptionsMenu", NULL, N_("_Options")},
322 #if defined(ENABLE_TOUCH_UI)
323 {"ToolsMenu", NULL, N_("_Tools")},
324 /* Less frequently used entries of the 'File' menu */
325 {"FileMoreMenu", NULL, N_("_More")},
326 /* Less frequently used entries of the 'Edit' menu */
327 {"EditMoreMenu", NULL, N_("_More")},
328 /* Less frequently used entries of the 'Tools' menu */
329 {"ToolsMoreMenu", NULL, N_("_More")},
330 #endif /* ENABLE_TOUCH_UI */
331 {"IncludeFile", GTK_STOCK_OPEN, N_("_Include File..."), NULL,
332 N_("Include a file"), G_CALLBACK(include_file_cb)},
333 {"AttachFile", BALSA_PIXMAP_ATTACHMENT, N_("_Attach File..."), NULL,
334 N_("Attach a file"), G_CALLBACK(attach_clicked)},
335 {"IncludeMessages", NULL, N_("I_nclude Message(s)"), NULL,
336 N_("Include selected message(s)"), G_CALLBACK(include_message_cb)},
337 {"AttachMessages", NULL, N_("Attach _Message(s)"), NULL,
338 N_("Attach selected message(s)"), G_CALLBACK(attach_message_cb)},
339 {"Save", GTK_STOCK_SAVE, N_("_Save"), "<control>S",
340 N_("Save this message"), G_CALLBACK(save_message_cb)},
341 #if !defined(ENABLE_TOUCH_UI)
342 {"PageSetup", NULL, N_("Page _Setup"), NULL,
343 N_("Set up page for printing"), G_CALLBACK(page_setup_cb)},
344 #endif /* ENABLE_TOUCH_UI */
345 {"Print", GTK_STOCK_PRINT, N_("_Print..."), "<control>P",
346 N_("Print the edited message"), G_CALLBACK(print_message_cb)},
347 {"Close", GTK_STOCK_CLOSE, N_("_Close"), "<control>W",
348 NULL, G_CALLBACK(close_window_cb)},
349 {"Undo", GTK_STOCK_UNDO, N_("_Undo"), "<control>Z",
350 N_("Undo most recent change"), G_CALLBACK(sw_undo_cb)},
351 {"Redo", GTK_STOCK_REDO, N_("_Redo"), "<shift><control>Z",
352 N_("Redo most recent change"), G_CALLBACK(sw_redo_cb)},
353 {"Cut", GTK_STOCK_CUT, N_("Cu_t"), "<control>X",
354 N_("Cut the selected text"), G_CALLBACK(cut_cb)},
355 {"Copy", GTK_STOCK_COPY, N_("_Copy"), "<control>C",
356 N_("Copy to the clipboard"), G_CALLBACK(copy_cb)},
357 {"Paste", GTK_STOCK_PASTE, N_("_Paste"), "<control>V",
358 N_("Paste from the clipboard"), G_CALLBACK(paste_cb)},
359 {"SelectAll", NULL, N_("Select _All"), "<control>A",
360 NULL, G_CALLBACK(select_all_cb)},
361 {"WrapBody", NULL, N_("_Wrap Body"), "<control>B",
362 N_("Wrap message lines"), G_CALLBACK(wrap_body_cb)},
363 {"Reflow", NULL, N_("_Reflow Selected Text"), "<control>R",
364 NULL, G_CALLBACK(reflow_selected_cb)},
365 {"InsertSignature", NULL, N_("Insert Si_gnature"), "<control>G",
366 NULL, G_CALLBACK(insert_signature_cb)},
367 {"QuoteMessages", NULL, N_("_Quote Message(s)"), NULL,
368 NULL, G_CALLBACK(quote_messages_cb)},
369 #if !HAVE_GTKSPELL
370 {"CheckSpelling", GTK_STOCK_SPELL_CHECK, N_("C_heck Spelling"), NULL,
371 N_("Check the spelling of the message"),
372 G_CALLBACK(spell_check_cb)},
373 #endif /* HAVE_GTKSPELL */
374 {"SelectIdentity", BALSA_PIXMAP_IDENTITY, N_("Select _Identity..."),
375 NULL, N_("Select the Identity to use for the message"),
376 G_CALLBACK(change_identity_dialog_cb)},
377 #if !defined(ENABLE_TOUCH_UI)
378 {"EditWithGnome", GTK_STOCK_EDIT, N_("_Edit with Gnome-Editor"),
379 NULL, N_("Edit the current message with the default Gnome editor"),
380 G_CALLBACK(edit_with_gnome)},
381 #endif /* ENABLE_TOUCH_UI */
382 };
383
384 /* Actions that are sensitive only when the message is ready to send */
385 static const GtkActionEntry ready_entries[] = {
386 /* All three "Send" and "Queue" actions have the same
387 * stock_id; the first in this list defines the action tied to the
388 * toolbar's "Send" button, so "ToolbarSend" must come before
389 * the others. */
390 {"ToolbarSend", BALSA_PIXMAP_SEND, N_("Sen_d"), "<control>Return",
391 N_("Send this message"), G_CALLBACK(toolbar_send_message_cb)},
392 {"Send", BALSA_PIXMAP_SEND, N_("Sen_d"), "<control>Return",
393 N_("Send this message"), G_CALLBACK(send_message_cb)},
394 #if !defined(ENABLE_TOUCH_UI)
395 {"Queue", BALSA_PIXMAP_SEND, N_("_Queue"), NULL,
396 N_("Queue this message in Outbox for sending"),
397 G_CALLBACK(queue_message_cb)},
398 {"Postpone", BALSA_PIXMAP_POSTPONE, N_("_Postpone"), NULL,
399 N_("Save this message and close"), G_CALLBACK(postpone_message_cb)},
400 #else /* ENABLE_TOUCH_UI */
401 {"Queue", BALSA_PIXMAP_SEND, N_("Send _Later"), "<control>Q",
402 N_("Queue this message in Outbox for sending"),
403 G_CALLBACK(queue_message_cb)},
404 {"Postpone", BALSA_PIXMAP_POSTPONE, N_("Sa_ve and Close"), NULL,
405 NULL, G_CALLBACK(postpone_message_cb)},
406 #endif /* ENABLE_TOUCH_UI */
407 };
408
409 /* Toggle items */
410 static const GtkToggleActionEntry toggle_entries[] = {
411 #if HAVE_GTKSPELL
412 {"CheckSpelling", GTK_STOCK_SPELL_CHECK, N_("C_heck Spelling"), NULL,
413 N_("Check the spelling of the message"),
414 G_CALLBACK(spell_check_menu_cb), FALSE},
415 #endif /* HAVE_GTKSPELL */
416 {"ShowToolbar", NULL, N_("Too_lbar"), NULL, NULL,
417 G_CALLBACK(sw_show_toolbar_cb), TRUE},
418 {"From", NULL, N_("F_rom"), NULL, NULL,
419 G_CALLBACK(toggle_from_cb), TRUE},
420 {"Recipients", NULL, N_("Rec_ipients"), NULL, NULL,
421 G_CALLBACK(toggle_recipients_cb), TRUE},
422 #if !defined(ENABLE_TOUCH_UI)
423 {"ReplyTo", NULL, N_("R_eply To"), NULL, NULL,
424 G_CALLBACK(toggle_replyto_cb), TRUE},
425 #endif /* ENABLE_TOUCH_UI */
426 {"Fcc", NULL, N_("F_cc"), NULL, NULL,
427 G_CALLBACK(toggle_fcc_cb), TRUE},
428 {"RequestMDN", BALSA_PIXMAP_REQUEST_MDN,
429 N_("_Request Disposition Notification"), NULL,
430 N_("Request Message Disposition Notification"),
431 G_CALLBACK(toggle_reqdispnotify_cb), FALSE},
432 {"Flowed", NULL, N_("_Format = Flowed"), NULL,
433 NULL, G_CALLBACK(toggle_format_cb), FALSE},
434 /* Send as message/alternative with text/plain and text/html parts */
435 {"SendMPAlt", NULL, N_("Send as plain text and _HTML"), NULL,
436 NULL, G_CALLBACK(toggle_mp_alt_cb), FALSE},
437 #ifdef HAVE_GPGME
438 #if !defined(ENABLE_TOUCH_UI)
439 {"SignMessage", BALSA_PIXMAP_GPG_SIGN, N_("_Sign Message"), NULL,
440 N_("Sign message using GPG"), G_CALLBACK(toggle_sign_cb), FALSE},
441 {"EncryptMessage", BALSA_PIXMAP_GPG_ENCRYPT, N_("_Encrypt Message"),
442 NULL, N_("Encrypt message using GPG"), G_CALLBACK(toggle_encrypt_cb),
443 FALSE},
444 #else /* ENABLE_TOUCH_UI */
445 {"SignMessage", BALSA_PIXMAP_GPG_SIGN, N_("_Sign Message"), NULL,
446 N_("signs the message using GnuPG"),
447 G_CALLBACK(toggle_sign_cb), FALSE},
448 {"EncryptMessage", BALSA_PIXMAP_GPG_ENCRYPT, N_("_Encrypt Message"),
449 NULL,
450 N_("signs the message using GnuPG for all To: and CC: recipients"),
451 G_CALLBACK(toggle_encrypt_cb), FALSE},
452 #endif /* ENABLE_TOUCH_UI */
453 #endif /* HAVE_GPGME */
454 };
455
456 #if !defined(ENABLE_TOUCH_UI)
457 /* Radio items */
458 #ifdef HAVE_GPGME
459 static const GtkRadioActionEntry gpg_mode_radio_entries[] = {
460 {"MimeMode", NULL, N_("_GnuPG uses MIME mode"),
461 NULL, NULL, LIBBALSA_PROTECT_RFC3156},
462 {"OldOpenPgpMode", NULL, N_("_GnuPG uses old OpenPGP mode"),
463 NULL, NULL, LIBBALSA_PROTECT_OPENPGP},
464 #ifdef HAVE_SMIME
465 {"SMimeMode", NULL, N_("_S/MIME mode (GpgSM)"),
466 NULL, NULL, LIBBALSA_PROTECT_SMIMEV3}
467 #endif /* HAVE_SMIME */
468 };
469 #endif /* HAVE_GPGME */
470 #endif /* ENABLE_TOUCH_UI */
471
472 static const char *ui_description =
473 #if !defined(ENABLE_TOUCH_UI)
474 "<ui>"
475 " <menubar name='MainMenu'>"
476 " <menu action='FileMenu'>"
477 " <menuitem action='IncludeFile'/>"
478 " <menuitem action='AttachFile'/>"
479 " <menuitem action='IncludeMessages'/>"
480 " <menuitem action='AttachMessages'/>"
481 " <separator/>"
482 " <menuitem action='Send'/>"
483 " <menuitem action='Queue'/>"
484 " <menuitem action='Postpone'/>"
485 " <menuitem action='Save'/>"
486 " <separator/>"
487 " <menuitem action='PageSetup'/>"
488 " <menuitem action='Print'/>"
489 " <separator/>"
490 " <menuitem action='Close'/>"
491 " </menu>"
492 " <menu action='EditMenu'>"
493 " <menuitem action='Undo'/>"
494 " <menuitem action='Redo'/>"
495 " <separator/>"
496 " <menuitem action='Cut'/>"
497 " <menuitem action='Copy'/>"
498 " <menuitem action='Paste'/>"
499 " <menuitem action='SelectAll'/>"
500 " <separator/>"
501 " <menuitem action='WrapBody'/>"
502 " <menuitem action='Reflow'/>"
503 " <separator/>"
504 " <menuitem action='InsertSignature'/>"
505 " <menuitem action='QuoteMessages'/>"
506 " <separator/>"
507 " <menuitem action='CheckSpelling'/>"
508 " <separator/>"
509 " <menuitem action='SelectIdentity'/>"
510 " <separator/>"
511 " <menuitem action='EditWithGnome'/>"
512 " </menu>"
513 " <menu action='ShowMenu'>"
514 " <menuitem action='ShowToolbar'/>"
515 " <separator/>"
516 " <menuitem action='From'/>"
517 " <menuitem action='Recipients'/>"
518 " <menuitem action='ReplyTo'/>"
519 " <menuitem action='Fcc'/>"
520 " </menu>"
521 " <menu action='LanguageMenu'>"
522 " </menu>"
523 " <menu action='OptionsMenu'>"
524 " <menuitem action='RequestMDN'/>"
525 " <menuitem action='Flowed'/>"
526 " <menuitem action='SendMPAlt'/>"
527 #ifdef HAVE_GPGME
528 " <separator/>"
529 " <menuitem action='SignMessage'/>"
530 " <menuitem action='EncryptMessage'/>"
531 " <menuitem action='MimeMode'/>"
532 " <menuitem action='OldOpenPgpMode'/>"
533 #ifdef HAVE_SMIME
534 " <menuitem action='SMimeMode'/>"
535 #endif /* HAVE_SMIME */
536 #endif /* HAVE_GPGME */
537 " </menu>"
538 " </menubar>"
539 " <toolbar name='Toolbar'>"
540 " </toolbar>"
541 "</ui>";
542 #else /* ENABLE_TOUCH_UI */
543 "<ui>"
544 " <menubar name='MainMenu'>"
545 " <menu action='FileMenu'>"
546 " <menuitem action='AttachFile'/>"
547 " <separator/>"
548 " <menuitem action='Save'/>"
549 " <menuitem action='Print'/>"
550 " <menu action='FileMoreMenu'>"
551 " <menuitem action='IncludeFile'/>"
552 " <menuitem action='IncludeMessages'/>"
553 " <menuitem action='AttachMessages'/>"
554 " </menu>"
555 " <menuitem action='Postpone'/>"
556 " <separator/>"
557 " <menuitem action='Send'/>"
558 " <menuitem action='Queue'/>"
559 " <separator/>"
560 " <menuitem action='Close'/>"
561 " </menu>"
562 " <menu action='EditMenu'>"
563 " <menuitem action='Undo'/>"
564 " <menuitem action='Redo'/>"
565 " <separator/>"
566 " <menuitem action='Cut'/>"
567 " <menuitem action='Copy'/>"
568 " <menuitem action='Paste'/>"
569 " <menuitem action='SelectAll'/>"
570 " <separator/>"
571 " <separator/>"
572 " <menuitem action='InsertSignature'/>"
573 " <menu action='EditMoreMenu'>"
574 " <menuitem action='WrapBody'/>"
575 " <menuitem action='Reflow'/>"
576 " <separator/>"
577 " <menuitem action='QuoteMessages'/>"
578 " </menu>"
579 " </menu>"
580 " <menu action='ShowMenu'>"
581 " <menuitem action='From'/>"
582 " <menuitem action='Recipients'/>"
583 " <menuitem action='Fcc'/>"
584 " </menu>"
585 " <menu action='ToolsMenu'>"
586 " <menuitem action='CheckSpelling'/>"
587 " <menu action='LanguageMenu'>"
588 " </menu>"
589 " <separator/>"
590 " <menuitem action='SelectIdentity'/>"
591 " <menuitem action='RequestMDN'/>"
592 " <menu action='ToolsMoreMenu'>"
593 " <menuitem action='Flowed'/>"
594 #ifdef HAVE_GPGME
595 " <separator/>"
596 " <menuitem action='SignMessage'/>"
597 " <menuitem action='EncryptMessage'/>"
598 #endif /* HAVE_GPGME */
599 " </menu>"
600 " </menu>"
601 " </menubar>"
602 " <toolbar name='Toolbar'>"
603 " </toolbar>"
604 "</ui>";
605 #endif /* ENABLE_TOUCH_UI */
606
607 /* Create a GtkUIManager for a compose window, with all the actions, but
608 * no ui.
609 */
610 GtkUIManager *
sendmsg_window_ui_manager_new(BalsaSendmsg * bsmsg)611 sendmsg_window_ui_manager_new(BalsaSendmsg * bsmsg)
612 {
613 GtkUIManager *ui_manager;
614 GtkActionGroup *action_group;
615
616 ui_manager = gtk_ui_manager_new();
617
618 action_group = gtk_action_group_new("ComposeWindow");
619 gtk_action_group_set_translation_domain(action_group, NULL);
620 if (bsmsg)
621 bsmsg->action_group = action_group;
622 gtk_action_group_add_actions(action_group, entries,
623 G_N_ELEMENTS(entries), bsmsg);
624 gtk_action_group_add_toggle_actions(action_group, toggle_entries,
625 G_N_ELEMENTS(toggle_entries),
626 bsmsg);
627
628 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
629
630 action_group = gtk_action_group_new("ComposeWindowReady");
631 gtk_action_group_set_translation_domain(action_group, NULL);
632 if (bsmsg)
633 bsmsg->ready_action_group = action_group;
634 gtk_action_group_add_actions(action_group, ready_entries,
635 G_N_ELEMENTS(ready_entries), bsmsg);
636
637 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
638
639 #ifdef HAVE_GPGME
640 #if !defined(ENABLE_TOUCH_UI)
641 action_group = gtk_action_group_new("ComposeWindowGPG");
642 gtk_action_group_set_translation_domain(action_group, NULL);
643 if (bsmsg)
644 bsmsg->gpg_action_group = action_group;
645 gtk_action_group_add_radio_actions(action_group,
646 gpg_mode_radio_entries,
647 G_N_ELEMENTS
648 (gpg_mode_radio_entries), 0,
649 G_CALLBACK(gpg_mode_radio_cb),
650 bsmsg);
651
652 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
653 #endif /* ENABLE_TOUCH_UI */
654 #endif /* HAVE_GPGME */
655
656 return ui_manager;
657 }
658
659 /* ===================================================================
660 * attachment related stuff
661 * =================================================================== */
662
663 enum {
664 ATTACH_INFO_COLUMN = 0,
665 ATTACH_ICON_COLUMN,
666 ATTACH_TYPE_COLUMN,
667 ATTACH_MODE_COLUMN,
668 ATTACH_SIZE_COLUMN,
669 ATTACH_DESC_COLUMN,
670 ATTACH_NUM_COLUMNS
671 };
672
673 typedef struct _BalsaAttachInfo BalsaAttachInfo;
674 typedef struct _BalsaAttachInfoClass BalsaAttachInfoClass;
675
676 static const gchar * const attach_modes[] =
677 {NULL, N_("Attachment"), N_("Inline"), N_("Reference") };
678
679 struct _BalsaAttachInfo {
680 GObject parent_object;
681
682 BalsaSendmsg *bm; /* send message back reference */
683
684 GtkWidget *popup_menu; /* popup menu */
685 LibbalsaVfs *file_uri; /* file uri of the attachment */
686 gchar *uri_ref; /* external body URI reference */
687 gchar *force_mime_type; /* force using this particular mime type */
688 gchar *charset; /* forced character set */
689 gboolean delete_on_destroy; /* destroy the file when not used any more */
690 gint mode; /* LIBBALSA_ATTACH_AS_ATTACHMENT etc. */
691 LibBalsaMessageHeaders *headers; /* information about a forwarded message */
692 };
693
694 struct _BalsaAttachInfoClass {
695 GObjectClass parent_class;
696 };
697
698
699 static GType balsa_attach_info_get_type();
700 static void balsa_attach_info_init(GObject *object, gpointer data);
701 static BalsaAttachInfo* balsa_attach_info_new();
702 static void balsa_attach_info_destroy(GObject * object);
703
704
705 #define BALSA_MSG_ATTACH_MODEL(x) gtk_tree_view_get_model(GTK_TREE_VIEW((x)->attachments[1]))
706
707
708 #define TYPE_BALSA_ATTACH_INFO \
709 (balsa_attach_info_get_type ())
710 #define BALSA_ATTACH_INFO(obj) \
711 (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_BALSA_ATTACH_INFO, BalsaAttachInfo))
712 #define IS_BALSA_ATTACH_INFO(obj) \
713 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_BALSA_ATTACH_INFO))
714
715 static void
balsa_attach_info_class_init(BalsaAttachInfoClass * klass)716 balsa_attach_info_class_init(BalsaAttachInfoClass *klass)
717 {
718 GObjectClass *object_class = G_OBJECT_CLASS(klass);
719
720 object_class->finalize = balsa_attach_info_destroy;
721 }
722
723 static GType
balsa_attach_info_get_type()724 balsa_attach_info_get_type()
725 {
726 static GType balsa_attach_info_type = 0 ;
727
728 if (!balsa_attach_info_type) {
729 static const GTypeInfo balsa_attach_info_info =
730 {
731 sizeof (BalsaAttachInfoClass),
732 (GBaseInitFunc) NULL,
733 (GBaseFinalizeFunc) NULL,
734 (GClassInitFunc) balsa_attach_info_class_init,
735 (GClassFinalizeFunc) NULL,
736 NULL,
737 sizeof(BalsaAttachInfo),
738 0,
739 (GInstanceInitFunc) balsa_attach_info_init
740 };
741 balsa_attach_info_type =
742 g_type_register_static (G_TYPE_OBJECT, "BalsaAttachInfo",
743 &balsa_attach_info_info, 0);
744 }
745 return balsa_attach_info_type;
746 }
747
748 static void
balsa_attach_info_init(GObject * object,gpointer data)749 balsa_attach_info_init(GObject *object, gpointer data)
750 {
751 BalsaAttachInfo * info = BALSA_ATTACH_INFO(object);
752
753 info->popup_menu = NULL;
754 info->file_uri = NULL;
755 info->force_mime_type = NULL;
756 info->charset = NULL;
757 info->delete_on_destroy = FALSE;
758 info->mode = LIBBALSA_ATTACH_AS_ATTACHMENT;
759 info->headers = NULL;
760 }
761
762 static BalsaAttachInfo*
balsa_attach_info_new(BalsaSendmsg * bm)763 balsa_attach_info_new(BalsaSendmsg *bm)
764 {
765 BalsaAttachInfo * info = g_object_new(TYPE_BALSA_ATTACH_INFO, NULL);
766
767 info->bm = bm;
768 return info;
769 }
770
771 static void
balsa_attach_info_destroy(GObject * object)772 balsa_attach_info_destroy(GObject * object)
773 {
774 BalsaAttachInfo * info;
775 GObjectClass *parent_class;
776
777 g_return_if_fail(object != NULL);
778 g_return_if_fail(IS_BALSA_ATTACH_INFO(object));
779 info = BALSA_ATTACH_INFO(object);
780
781 /* unlink the file if necessary */
782 if (info->delete_on_destroy && info->file_uri) {
783 gchar * folder_name;
784
785 /* unlink the file */
786 if (balsa_app.debug)
787 fprintf (stderr, "%s:%s: unlink `%s'\n", __FILE__, __FUNCTION__,
788 libbalsa_vfs_get_uri_utf8(info->file_uri));
789 libbalsa_vfs_file_unlink(info->file_uri, NULL);
790
791 /* remove the folder if possible */
792 folder_name = g_filename_from_uri(libbalsa_vfs_get_folder(info->file_uri),
793 NULL, NULL);
794 if (folder_name) {
795 if (balsa_app.debug)
796 fprintf (stderr, "%s:%s: rmdir `%s'\n", __FILE__, __FUNCTION__,
797 folder_name);
798 g_rmdir(folder_name);
799 g_free(folder_name);
800 }
801 }
802
803 /* clean up memory */
804 if (info->popup_menu)
805 gtk_widget_destroy(info->popup_menu);
806 if (info->file_uri)
807 g_object_unref(G_OBJECT(info->file_uri));
808 g_free(info->force_mime_type);
809 g_free(info->charset);
810 libbalsa_message_headers_destroy(info->headers);
811
812 parent_class = g_type_class_peek_parent(G_OBJECT_GET_CLASS(object));
813 parent_class->finalize(object);
814 }
815
816 /* ===================================================================
817 * end of attachment related stuff
818 * =================================================================== */
819
820
821 static void
append_comma_separated(GtkEditable * editable,const gchar * text)822 append_comma_separated(GtkEditable *editable, const gchar * text)
823 {
824 gint position;
825
826 if (!text || !*text)
827 return;
828
829 gtk_editable_set_position(editable, -1);
830 position = gtk_editable_get_position(editable);
831 if (position > 0)
832 gtk_editable_insert_text(editable, ", ", 2, &position);
833 gtk_editable_insert_text(editable, text, -1, &position);
834 gtk_editable_set_position(editable, position);
835 }
836
837 /* the callback handlers */
838 #define BALSA_SENDMSG_ADDRESS_BOOK_KEY "balsa-sendmsg-address-book"
839 #define BALSA_SENDMSG_ROW_REF_KEY "balsa-sendmsg-row-ref"
840 static void
address_book_cb(LibBalsaAddressView * address_view,GtkTreeRowReference * row_ref,BalsaSendmsg * bsmsg)841 address_book_cb(LibBalsaAddressView * address_view,
842 GtkTreeRowReference * row_ref,
843 BalsaSendmsg * bsmsg)
844 {
845 GtkWidget *ab;
846 GtkTreeRowReference *row_ref_copy;
847
848 /* Show only one dialog per window. */
849 ab = g_object_get_data(G_OBJECT(bsmsg->window),
850 BALSA_SENDMSG_ADDRESS_BOOK_KEY);
851 if (ab) {
852 gtk_window_present(GTK_WINDOW(ab));
853 return;
854 }
855
856 gtk_widget_set_sensitive(GTK_WIDGET(address_view), FALSE);
857
858 ab = balsa_ab_window_new(TRUE, GTK_WINDOW(bsmsg->window));
859 gtk_window_set_destroy_with_parent(GTK_WINDOW(ab), TRUE);
860 g_signal_connect(G_OBJECT(ab), "response",
861 G_CALLBACK(address_book_response), address_view);
862 row_ref_copy = gtk_tree_row_reference_copy(row_ref);
863 g_object_set_data_full(G_OBJECT(ab), BALSA_SENDMSG_ROW_REF_KEY,
864 row_ref_copy,
865 (GDestroyNotify) gtk_tree_row_reference_free);
866 g_object_set_data(G_OBJECT(bsmsg->window),
867 BALSA_SENDMSG_ADDRESS_BOOK_KEY, ab);
868 gtk_widget_show_all(ab);
869 }
870
871 /* Callback for the "response" signal for the address book dialog. */
872 static void
address_book_response(GtkWidget * ab,gint response,LibBalsaAddressView * address_view)873 address_book_response(GtkWidget * ab, gint response,
874 LibBalsaAddressView * address_view)
875 {
876 GtkWindow *parent = gtk_window_get_transient_for(GTK_WINDOW(ab));
877 GtkTreeRowReference *row_ref =
878 g_object_get_data(G_OBJECT(ab), BALSA_SENDMSG_ROW_REF_KEY);
879
880 if (response == GTK_RESPONSE_OK) {
881 gchar *t = balsa_ab_window_get_recipients(BALSA_AB_WINDOW(ab));
882 libbalsa_address_view_add_to_row(address_view, row_ref, t);
883 g_free(t);
884 }
885
886 gtk_widget_destroy(ab);
887 g_object_set_data(G_OBJECT(parent), BALSA_SENDMSG_ADDRESS_BOOK_KEY,
888 NULL);
889 gtk_widget_set_sensitive(GTK_WIDGET(address_view), TRUE);
890 }
891
892 static void
sw_delete_draft(BalsaSendmsg * bsmsg)893 sw_delete_draft(BalsaSendmsg * bsmsg)
894 {
895 LibBalsaMessage *message = bsmsg->draft_message;
896 if (message && message->mailbox && !message->mailbox->readonly)
897 libbalsa_message_change_flags(message,
898 LIBBALSA_MESSAGE_FLAG_DELETED, 0);
899 }
900
901 static gint
delete_handler(BalsaSendmsg * bsmsg)902 delete_handler(BalsaSendmsg * bsmsg)
903 {
904 InternetAddressList *list;
905 const InternetAddress *ia;
906 const gchar *tmp;
907 gint reply;
908 GtkWidget *d;
909
910 if (balsa_app.debug)
911 printf("%s\n", __func__);
912
913 if (bsmsg->state == SENDMSG_STATE_CLEAN)
914 return FALSE;
915
916 list = libbalsa_address_view_get_list(bsmsg->recipient_view, "To:");
917 ia = internet_address_list_get_address(list, 0);
918 tmp = ia && ia->name ? ia->name : _("(No name)");
919
920 d = gtk_message_dialog_new(GTK_WINDOW(bsmsg->window),
921 GTK_DIALOG_DESTROY_WITH_PARENT,
922 GTK_MESSAGE_QUESTION,
923 GTK_BUTTONS_YES_NO,
924 _("The message to '%s' is modified.\n"
925 "Save message to Draftbox?"), tmp);
926 #if HAVE_MACOSX_DESKTOP
927 libbalsa_macosx_menu_for_parent(d, GTK_WINDOW(bsmsg->window));
928 #endif
929 g_object_unref(list);
930 gtk_dialog_set_default_response(GTK_DIALOG(d), GTK_RESPONSE_YES);
931 gtk_dialog_add_button(GTK_DIALOG(d),
932 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
933 reply = gtk_dialog_run(GTK_DIALOG(d));
934 gtk_widget_destroy(d);
935
936 switch (reply) {
937 case GTK_RESPONSE_YES:
938 if (bsmsg->state == SENDMSG_STATE_MODIFIED)
939 if (!message_postpone(bsmsg))
940 return TRUE;
941 break;
942 case GTK_RESPONSE_NO:
943 if (!bsmsg->is_continue)
944 sw_delete_draft(bsmsg);
945 break;
946 default:
947 return TRUE;
948 }
949
950 return FALSE;
951 }
952
953 static gint
delete_event_cb(GtkWidget * widget,GdkEvent * e,gpointer data)954 delete_event_cb(GtkWidget * widget, GdkEvent * e, gpointer data)
955 {
956 BalsaSendmsg* bsmsg = data;
957 return delete_handler(bsmsg);
958 }
959
960 static void
close_window_cb(GtkAction * action,gpointer data)961 close_window_cb(GtkAction * action, gpointer data)
962 {
963 BalsaSendmsg* bsmsg = data;
964 BALSA_DEBUG_MSG("close_window_cb: start\n");
965 if(!delete_handler(bsmsg))
966 gtk_widget_destroy(bsmsg->window);
967 BALSA_DEBUG_MSG("close_window_cb: end\n");
968 }
969
970 static gint
destroy_event_cb(GtkWidget * widget,gpointer data)971 destroy_event_cb(GtkWidget * widget, gpointer data)
972 {
973 balsa_sendmsg_destroy_handler((BalsaSendmsg *) data);
974 return TRUE;
975 }
976
977 /* the balsa_sendmsg destructor; copies first the shown headers setting
978 to the balsa_app structure.
979 */
980 #define BALSA_SENDMSG_WINDOW_KEY "balsa-sendmsg-window-key"
981 static void
balsa_sendmsg_destroy_handler(BalsaSendmsg * bsmsg)982 balsa_sendmsg_destroy_handler(BalsaSendmsg * bsmsg)
983 {
984 gboolean quit_on_close;
985
986 g_assert(bsmsg != NULL);
987
988 g_signal_handler_disconnect(G_OBJECT(balsa_app.main_window),
989 bsmsg->delete_sig_id);
990 g_signal_handler_disconnect(G_OBJECT(balsa_app.main_window),
991 bsmsg->identities_changed_id);
992 if(balsa_app.debug) g_message("balsa_sendmsg_destroy()_handler: Start.");
993
994 if (bsmsg->parent_message) {
995 if (bsmsg->parent_message->mailbox)
996 libbalsa_mailbox_close(bsmsg->parent_message->mailbox,
997 /* Respect pref setting: */
998 balsa_app.expunge_on_close);
999 g_object_unref(G_OBJECT(bsmsg->parent_message));
1000 bsmsg->parent_message = NULL;
1001 }
1002
1003 if (bsmsg->draft_message) {
1004 g_object_set_data(G_OBJECT(bsmsg->draft_message),
1005 BALSA_SENDMSG_WINDOW_KEY, NULL);
1006 if (bsmsg->draft_message->mailbox)
1007 libbalsa_mailbox_close(bsmsg->draft_message->mailbox,
1008 /* Respect pref setting: */
1009 balsa_app.expunge_on_close);
1010 g_object_unref(G_OBJECT(bsmsg->draft_message));
1011 bsmsg->draft_message = NULL;
1012 }
1013
1014 if (balsa_app.debug)
1015 printf("balsa_sendmsg_destroy_handler: Freeing bsmsg\n");
1016 gtk_widget_destroy(bsmsg->window);
1017 quit_on_close = bsmsg->quit_on_close;
1018 g_free(bsmsg->fcc_url);
1019 g_free(bsmsg->in_reply_to);
1020 if(bsmsg->references) {
1021 g_list_foreach(bsmsg->references, (GFunc) g_free, NULL);
1022 g_list_free(bsmsg->references);
1023 bsmsg->references = NULL;
1024 }
1025
1026 #if !HAVE_GTKSPELL
1027 if (bsmsg->spell_checker)
1028 gtk_widget_destroy(bsmsg->spell_checker);
1029 #endif /* HAVE_GTKSPELL */
1030 if (bsmsg->autosave_timeout_id) {
1031 g_source_remove(bsmsg->autosave_timeout_id);
1032 bsmsg->autosave_timeout_id = 0;
1033 }
1034
1035 #if !HAVE_GTKSOURCEVIEW
1036 g_object_unref(bsmsg->buffer2);
1037 #endif /* HAVE_GTKSOURCEVIEW */
1038
1039 /* Move the current identity to the start of the list */
1040 balsa_app.identities = g_list_remove(balsa_app.identities,
1041 bsmsg->ident);
1042 balsa_app.identities = g_list_prepend(balsa_app.identities,
1043 bsmsg->ident);
1044
1045 g_free(bsmsg);
1046
1047 if (quit_on_close) {
1048 #ifdef BALSA_USE_THREADS
1049 libbalsa_wait_for_sending_thread(-1);
1050 #endif
1051 gtk_main_quit();
1052 }
1053 if(balsa_app.debug) g_message("balsa_sendmsg_destroy(): Stop.");
1054 }
1055
1056 /* language menu helper functions */
1057 /* find_locale_index_by_locale:
1058 finds the longest fit so the one who has en_GB will gent en_US if en_GB
1059 is not defined.
1060 NOTE: test for the 'C' locale would not be necessary if people set LANG
1061 instead of LC_ALL. But it is simpler to set it here instead of answering
1062 the questions (OTOH, I am afraid that people will start claiming "but
1063 balsa can recognize my language!" on failures in other software.
1064 */
1065 static gint
find_locale_index_by_locale(const gchar * locale)1066 find_locale_index_by_locale(const gchar * locale)
1067 {
1068 unsigned i, j, maxfit = 0;
1069 gint maxpos = -1;
1070
1071 if (!locale || strcmp(locale, "C") == 0)
1072 locale = "en_US";
1073 for (i = 0; i < ELEMENTS(locales); i++) {
1074 for (j = 0; locale[j] && locales[i].locale[j] == locale[j]; j++);
1075 if (j > maxfit) {
1076 maxfit = j;
1077 maxpos = i;
1078 }
1079 }
1080 return maxpos;
1081 }
1082
1083 static void
sw_buffer_signals_block(BalsaSendmsg * bsmsg,GtkTextBuffer * buffer)1084 sw_buffer_signals_block(BalsaSendmsg * bsmsg, GtkTextBuffer * buffer)
1085 {
1086 g_signal_handler_block(buffer, bsmsg->changed_sig_id);
1087 #if !HAVE_GTKSOURCEVIEW
1088 g_signal_handler_block(buffer, bsmsg->delete_range_sig_id);
1089 #endif /* HAVE_GTKSOURCEVIEW */
1090 g_signal_handler_block(buffer, bsmsg->insert_text_sig_id);
1091 }
1092
1093 static void
sw_buffer_signals_unblock(BalsaSendmsg * bsmsg,GtkTextBuffer * buffer)1094 sw_buffer_signals_unblock(BalsaSendmsg * bsmsg, GtkTextBuffer * buffer)
1095 {
1096 g_signal_handler_unblock(buffer, bsmsg->changed_sig_id);
1097 #if !HAVE_GTKSOURCEVIEW
1098 g_signal_handler_unblock(buffer, bsmsg->delete_range_sig_id);
1099 #endif /* HAVE_GTKSOURCEVIEW */
1100 g_signal_handler_unblock(buffer, bsmsg->insert_text_sig_id);
1101 }
1102
1103 static const gchar *const address_types[] =
1104 { N_("To:"), N_("Cc:"), N_("Bcc:") };
1105
1106 #if !defined(ENABLE_TOUCH_UI)
1107 static gboolean
edit_with_gnome_check(gpointer data)1108 edit_with_gnome_check(gpointer data) {
1109 FILE *tmp;
1110 balsa_edit_with_gnome_data *data_real = (balsa_edit_with_gnome_data *)data;
1111 GtkTextBuffer *buffer;
1112
1113 pid_t pid;
1114 gchar line[81]; /* FIXME:All lines should wrap at this line */
1115 /* Editor not ready */
1116 pid = waitpid (data_real->pid_editor, NULL, WNOHANG);
1117 if(pid == -1) {
1118 perror("waitpid");
1119 return TRUE;
1120 } else if(pid == 0) return TRUE;
1121
1122 tmp = fopen(data_real->filename, "r");
1123 if(tmp == NULL){
1124 perror("fopen");
1125 return TRUE;
1126 }
1127 gdk_threads_enter();
1128 if (balsa_app.edit_headers) {
1129 while (fgets(line, sizeof(line), tmp)) {
1130 guint type;
1131
1132 if (line[strlen(line) - 1] == '\n')
1133 line[strlen(line) - 1] = '\0';
1134
1135 if (libbalsa_str_has_prefix(line, _("Subject:")) == 0) {
1136 gtk_entry_set_text(GTK_ENTRY(data_real->bsmsg->subject[1]),
1137 line + strlen(_("Subject:")) + 1);
1138 continue;
1139 }
1140
1141 for (type = 0;
1142 type < G_N_ELEMENTS(address_types);
1143 type++) {
1144 const gchar *type_string = _(address_types[type]);
1145 if (libbalsa_str_has_prefix(line, type_string))
1146 libbalsa_address_view_set_from_string
1147 (data_real->bsmsg->recipient_view,
1148 address_types[type],
1149 line + strlen(type_string) + 1);
1150 }
1151 }
1152 }
1153 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(data_real->bsmsg->text));
1154
1155 #if !HAVE_GTKSOURCEVIEW
1156 sw_buffer_save(data_real->bsmsg);
1157 #endif /* HAVE_GTKSOURCEVIEW */
1158 sw_buffer_signals_block(data_real->bsmsg, buffer);
1159 gtk_text_buffer_set_text(buffer, "", 0);
1160 while(fgets(line, sizeof(line), tmp))
1161 gtk_text_buffer_insert_at_cursor(buffer, line, -1);
1162 sw_buffer_signals_unblock(data_real->bsmsg, buffer);
1163
1164 g_free(data_real->filename);
1165 fclose(tmp);
1166 unlink(data_real->filename);
1167 gtk_widget_set_sensitive(data_real->bsmsg->text, TRUE);
1168 g_free(data);
1169 gdk_threads_leave();
1170
1171 return FALSE;
1172 }
1173
1174 /* Edit the current file with an external editor.
1175 *
1176 * We fork twice current process, so we get:
1177 *
1178 * - Old (parent) process (this needs to continue because we don't want
1179 * balsa to 'hang' until the editor exits
1180 * - New (child) process (forks and waits for child to finish)
1181 * - New (grandchild) process (executes editor)
1182 */
1183 static void
edit_with_gnome(GtkAction * action,BalsaSendmsg * bsmsg)1184 edit_with_gnome(GtkAction * action, BalsaSendmsg* bsmsg)
1185 {
1186 static const char TMP_PATTERN[] = "/tmp/balsa-edit-XXXXXX";
1187 gchar filename[sizeof(TMP_PATTERN)];
1188 balsa_edit_with_gnome_data *data;
1189 pid_t pid;
1190 FILE *tmp;
1191 int tmpfd;
1192 GtkTextBuffer *buffer;
1193 GtkTextIter start, end;
1194 gchar *p;
1195 GAppInfo *app;
1196 char **argv;
1197 int argc;
1198
1199 app = g_app_info_get_default_for_type("text/plain", FALSE);
1200 if (!app) {
1201 balsa_information_parented(GTK_WINDOW(bsmsg->window),
1202 LIBBALSA_INFORMATION_ERROR,
1203 _("Gnome editor is not defined"
1204 " in your preferred applications."));
1205 return;
1206 }
1207
1208 argc = 2;
1209 argv = g_new0 (char *, argc + 1);
1210 argv[0] = g_strdup(g_app_info_get_executable(app));
1211 strcpy(filename, TMP_PATTERN);
1212 argv[1] =
1213 g_strdup_printf("%s%s",
1214 g_app_info_supports_uris(app) ? "file://" : "",
1215 filename);
1216 /* FIXME: how can I detect if the called application needs the
1217 * terminal??? */
1218 g_object_unref(app);
1219
1220 tmpfd = mkstemp(filename);
1221 tmp = fdopen(tmpfd, "w+");
1222
1223 if(balsa_app.edit_headers) {
1224 guint type;
1225
1226 fprintf(tmp, "%s %s\n", _("Subject:"),
1227 gtk_entry_get_text(GTK_ENTRY(bsmsg->subject[1])));
1228 for (type = 0; type < G_N_ELEMENTS(address_types); type++) {
1229 InternetAddressList *list =
1230 libbalsa_address_view_get_list(bsmsg->recipient_view,
1231 address_types[type]);
1232 gchar *p = internet_address_list_to_string(list, FALSE);
1233 g_object_unref(list);
1234 fprintf(tmp, "%s %s\n", _(address_types[type]), p);
1235 g_free(p);
1236 }
1237 fprintf(tmp, "\n");
1238 }
1239
1240 gtk_widget_set_sensitive(GTK_WIDGET(bsmsg->text), FALSE);
1241 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
1242 gtk_text_buffer_get_bounds(buffer, &start, &end);
1243 p = gtk_text_iter_get_text(&start, &end);
1244 fputs(p, tmp);
1245 g_free(p);
1246 fclose(tmp);
1247 if ((pid = fork()) < 0) {
1248 perror ("fork");
1249 g_strfreev(argv);
1250 return;
1251 }
1252 if (pid == 0) {
1253 setpgid(0, 0);
1254 execvp (argv[0], argv);
1255 perror ("execvp");
1256 g_strfreev (argv);
1257 exit(127);
1258 }
1259 g_strfreev (argv);
1260 /* Return immediately. We don't want balsa to 'hang' */
1261 data = g_malloc(sizeof(balsa_edit_with_gnome_data));
1262 data->pid_editor = pid;
1263 data->filename = g_strdup(filename);
1264 data->bsmsg = bsmsg;
1265 g_timeout_add(200, (GSourceFunc)edit_with_gnome_check, data);
1266 }
1267
1268 #endif /* ENABLE_TOUCH_UI */
1269
1270 static void
change_identity_dialog_cb(GtkAction * action,BalsaSendmsg * bsmsg)1271 change_identity_dialog_cb(GtkAction * action, BalsaSendmsg* bsmsg)
1272 {
1273 libbalsa_identity_select_dialog(GTK_WINDOW(bsmsg->window),
1274 _("Select Identity"),
1275 balsa_app.identities,
1276 bsmsg->ident,
1277 ((LibBalsaIdentityCallback)
1278 update_bsmsg_identity),
1279 bsmsg);
1280 }
1281
1282
1283 /* NOTE: replace_offset and siglen are utf-8 character offsets. */
1284 static void
replace_identity_signature(BalsaSendmsg * bsmsg,LibBalsaIdentity * new_ident,LibBalsaIdentity * old_ident,gint * replace_offset,gint siglen,const gchar * new_sig)1285 replace_identity_signature(BalsaSendmsg* bsmsg, LibBalsaIdentity* new_ident,
1286 LibBalsaIdentity* old_ident, gint* replace_offset,
1287 gint siglen, const gchar* new_sig)
1288 {
1289 gint newsiglen;
1290 GtkTextBuffer *buffer =
1291 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
1292 GtkTextIter ins, end;
1293 GtkTextMark *mark;
1294 gboolean insert_signature;
1295
1296 /* Save cursor */
1297 gtk_text_buffer_get_iter_at_mark(buffer, &ins,
1298 gtk_text_buffer_get_insert(buffer));
1299 mark = gtk_text_buffer_create_mark(buffer, NULL, &ins, TRUE);
1300
1301 gtk_text_buffer_get_iter_at_offset(buffer, &ins,
1302 *replace_offset);
1303 gtk_text_buffer_get_iter_at_offset(buffer, &end,
1304 *replace_offset + siglen);
1305 gtk_text_buffer_delete(buffer, &ins, &end);
1306
1307 newsiglen = strlen(new_sig);
1308
1309 switch (bsmsg->type) {
1310 case SEND_NORMAL:
1311 default:
1312 insert_signature = TRUE;
1313 break;
1314 case SEND_REPLY:
1315 case SEND_REPLY_ALL:
1316 case SEND_REPLY_GROUP:
1317 insert_signature = new_ident->sig_whenreply;
1318 break;
1319 case SEND_FORWARD_ATTACH:
1320 case SEND_FORWARD_INLINE:
1321 insert_signature = new_ident->sig_whenforward;
1322 break;
1323 }
1324 if (insert_signature) {
1325
1326 /* see if sig location is probably going to be the same */
1327 if (new_ident->sig_prepend == old_ident->sig_prepend) {
1328 /* account for sig length difference in replacement offset */
1329 *replace_offset += newsiglen - siglen;
1330 } else if (new_ident->sig_prepend) {
1331 /* sig location not the same between idents, take a WAG and
1332 * put it at the start of the message */
1333 gtk_text_buffer_get_start_iter(buffer, &ins);
1334 *replace_offset += newsiglen;
1335 } else {
1336 /* put it at the end of the message */
1337 gtk_text_buffer_get_end_iter(buffer, &ins);
1338 }
1339
1340 gtk_text_buffer_place_cursor(buffer, &ins);
1341 gtk_text_buffer_insert_at_cursor(buffer, new_sig, -1);
1342 }
1343
1344 /* Restore cursor */
1345 gtk_text_buffer_get_iter_at_mark(buffer, &ins, mark);
1346 gtk_text_buffer_place_cursor(buffer, &ins);
1347 gtk_text_buffer_delete_mark(buffer, mark);
1348 }
1349
1350 /*
1351 * GtkAction helpers
1352 */
1353
1354 static GtkAction *
sw_get_action(BalsaSendmsg * bsmsg,const gchar * action_name)1355 sw_get_action(BalsaSendmsg * bsmsg, const gchar * action_name)
1356 {
1357 GtkAction *action =
1358 gtk_action_group_get_action(bsmsg->action_group, action_name);
1359
1360 #ifdef HAVE_GPGME
1361 #if !defined(ENABLE_TOUCH_UI)
1362 if (!action)
1363 action =
1364 gtk_action_group_get_action(bsmsg->gpg_action_group,
1365 action_name);
1366 #endif /* ENABLE_TOUCH_UI */
1367 #endif /* HAVE_GPGME */
1368
1369 return action;
1370 }
1371
1372 static void
sw_set_sensitive(BalsaSendmsg * bsmsg,const gchar * action_name,gboolean sensitive)1373 sw_set_sensitive(BalsaSendmsg * bsmsg, const gchar * action_name,
1374 gboolean sensitive)
1375 {
1376 GtkAction *action = sw_get_action(bsmsg, action_name);
1377 gtk_action_set_sensitive(action, sensitive);
1378 }
1379
1380 #if !HAVE_GTKSOURCEVIEW
1381 static gboolean
sw_get_sensitive(BalsaSendmsg * bsmsg,const gchar * action_name)1382 sw_get_sensitive(BalsaSendmsg * bsmsg, const gchar * action_name)
1383 {
1384 GtkAction *action = sw_get_action(bsmsg, action_name);
1385 return gtk_action_get_sensitive(action);
1386 }
1387 #endif /* HAVE_GTKSOURCEVIEW */
1388
1389 /* Set the state of a GtkToggleAction.
1390 * Note: most calls expect the corresponding action to be taken, so we
1391 * do not block any handlers.
1392 */
1393 static void
sw_set_active(BalsaSendmsg * bsmsg,const gchar * action_name,gboolean active)1394 sw_set_active(BalsaSendmsg * bsmsg, const gchar * action_name,
1395 gboolean active)
1396 {
1397 GtkAction *action = sw_get_action(bsmsg, action_name);
1398 gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), active);
1399 }
1400
1401 static gboolean
sw_get_active(BalsaSendmsg * bsmsg,const gchar * action_name)1402 sw_get_active(BalsaSendmsg * bsmsg, const gchar * action_name)
1403 {
1404 GtkAction *action = sw_get_action(bsmsg, action_name);
1405 return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
1406 }
1407
1408 /*
1409 * end of GtkAction helpers
1410 */
1411
1412 /*
1413 * update_bsmsg_identity
1414 *
1415 * Change the specified BalsaSendmsg current identity, and update the
1416 * corresponding fields.
1417 * */
1418 static void
update_bsmsg_identity(BalsaSendmsg * bsmsg,LibBalsaIdentity * ident)1419 update_bsmsg_identity(BalsaSendmsg* bsmsg, LibBalsaIdentity* ident)
1420 {
1421 GtkTextBuffer *buffer =
1422 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
1423 GtkTextIter start, end;
1424
1425 gint replace_offset = 0;
1426 gint siglen;
1427 gint i = 0;
1428
1429 gboolean found_sig = FALSE;
1430 gchar* old_sig;
1431 gchar* new_sig;
1432 gchar* message_text;
1433 gchar* compare_str;
1434 gchar** message_split;
1435 gchar* tmpstr;
1436 const gchar* subject;
1437 gint replen, fwdlen;
1438
1439 LibBalsaIdentity* old_ident;
1440 gboolean reply_type = (bsmsg->type == SEND_REPLY ||
1441 bsmsg->type == SEND_REPLY_ALL ||
1442 bsmsg->type == SEND_REPLY_GROUP);
1443 gboolean forward_type = (bsmsg->type == SEND_FORWARD_ATTACH ||
1444 bsmsg->type == SEND_FORWARD_INLINE);
1445
1446 g_return_if_fail(ident != NULL);
1447
1448
1449 /* change entries to reflect new identity */
1450 gtk_combo_box_set_active(GTK_COMBO_BOX(bsmsg->from[1]),
1451 g_list_index(balsa_app.identities, ident));
1452
1453 #if !defined(ENABLE_TOUCH_UI)
1454 if (ident->replyto && *ident->replyto) {
1455 libbalsa_address_view_set_from_string(bsmsg->replyto_view,
1456 "Reply To:",
1457 ident->replyto);
1458 gtk_widget_show(bsmsg->replyto[0]);
1459 gtk_widget_show(bsmsg->replyto[1]);
1460 } else if (!sw_get_active(bsmsg, "ReplyTo")) {
1461 gtk_widget_hide(bsmsg->replyto[0]);
1462 gtk_widget_hide(bsmsg->replyto[1]);
1463 }
1464 #endif
1465
1466 if (bsmsg->ident->bcc) {
1467 InternetAddressList *bcc_list, *ident_list;
1468
1469 bcc_list =
1470 libbalsa_address_view_get_list(bsmsg->recipient_view, "Bcc:");
1471
1472 ident_list = internet_address_list_parse_string(bsmsg->ident->bcc);
1473 if (ident_list) {
1474 /* Remove any Bcc addresses that came from the old identity
1475 * from the list. */
1476 gint ident_list_len = internet_address_list_length(ident_list);
1477 gint i;
1478
1479 for (i = 0; i < internet_address_list_length(bcc_list); i++) {
1480 InternetAddress *ia =
1481 internet_address_list_get_address (bcc_list, i);
1482 gint j;
1483
1484 for (j = 0; j < ident_list_len; j++) {
1485 InternetAddress *ia2 =
1486 internet_address_list_get_address(ident_list, j);
1487 if (libbalsa_ia_rfc2821_equal(ia, ia2))
1488 break;
1489 }
1490
1491 if (j < ident_list_len) {
1492 /* This address was found in the identity. */
1493 internet_address_list_remove_at(bcc_list, i);
1494 --i;
1495 }
1496 }
1497 g_object_unref(ident_list);
1498 }
1499
1500 /* Add the new Bcc addresses, if any: */
1501 ident_list = internet_address_list_parse_string(ident->bcc);
1502 if (ident_list) {
1503 internet_address_list_append(bcc_list, ident_list);
1504 g_object_unref(ident_list);
1505 }
1506
1507 /* Set the resulting list: */
1508 libbalsa_address_view_set_from_list(bsmsg->recipient_view, "Bcc:",
1509 bcc_list);
1510 g_object_unref(bcc_list);
1511 }
1512
1513 /* change the subject to use the reply/forward strings */
1514 subject = gtk_entry_get_text(GTK_ENTRY(bsmsg->subject[1]));
1515
1516 /*
1517 * If the subject begins with the old reply string
1518 * Then replace it with the new reply string.
1519 * Else, if the subject begins with the old forward string
1520 * Then replace it with the new forward string.
1521 * Else, if the old reply string was empty, and the message
1522 * is a reply, OR the old forward string was empty, and the
1523 * message is a forward
1524 * Then call bsmsg_set_subject_from_body()
1525 * Else assume the user hand edited the subject and does
1526 * not want it altered
1527 */
1528
1529 old_ident = bsmsg->ident;
1530 if (((replen = strlen(old_ident->reply_string)) > 0) &&
1531 (strncmp(subject, old_ident->reply_string, replen) == 0)) {
1532 tmpstr = g_strconcat(ident->reply_string, &(subject[replen]), NULL);
1533 gtk_entry_set_text(GTK_ENTRY(bsmsg->subject[1]), tmpstr);
1534 g_free(tmpstr);
1535 } else if (((fwdlen = strlen(old_ident->forward_string)) > 0) &&
1536 (strncmp(subject, old_ident->forward_string, fwdlen) == 0)) {
1537 tmpstr = g_strconcat(ident->forward_string, &(subject[fwdlen]), NULL);
1538 gtk_entry_set_text(GTK_ENTRY(bsmsg->subject[1]), tmpstr);
1539 g_free(tmpstr);
1540 } else {
1541 if ( (replen == 0 && reply_type) ||
1542 (fwdlen == 0 && forward_type) ) {
1543 LibBalsaMessage *msg = bsmsg->parent_message ?
1544 bsmsg->parent_message : bsmsg->draft_message;
1545 bsmsg_set_subject_from_body(bsmsg, msg->body_list, ident);
1546 }
1547 }
1548
1549 /* -----------------------------------------------------------
1550 * remove/add the signature depending on the new settings, change
1551 * the signature if path changed */
1552
1553 /* reconstruct the old signature to search with */
1554 old_sig = libbalsa_identity_get_signature(old_ident,
1555 GTK_WINDOW(bsmsg->window));
1556
1557 /* switch identities in bsmsg here so we can use read_signature
1558 * again */
1559 bsmsg->ident = ident;
1560 if ( (reply_type && ident->sig_whenreply)
1561 || (forward_type && ident->sig_whenforward)
1562 || (bsmsg->type == SEND_NORMAL && ident->sig_sending))
1563 new_sig = libbalsa_identity_get_signature(ident,
1564 GTK_WINDOW(bsmsg->window));
1565 else
1566 new_sig = NULL;
1567 if(!new_sig) new_sig = g_strdup("");
1568
1569 gtk_text_buffer_get_bounds(buffer, &start, &end);
1570 message_text = gtk_text_iter_get_text(&start, &end);
1571 if (!old_sig) {
1572 replace_offset = bsmsg->ident->sig_prepend
1573 ? 0 : g_utf8_strlen(message_text, -1);
1574 replace_identity_signature(bsmsg, ident, old_ident, &replace_offset,
1575 0, new_sig);
1576 } else {
1577 /* split on sig separator */
1578 message_split = g_strsplit(message_text, "\n-- \n", 0);
1579 siglen = g_utf8_strlen(old_sig, -1);
1580
1581 /* check the special case of starting a message with a sig */
1582 compare_str = g_strconcat("\n", message_split[0], NULL);
1583
1584 if (g_ascii_strncasecmp(old_sig, compare_str, siglen) == 0) {
1585 g_free(compare_str);
1586 replace_identity_signature(bsmsg, ident, old_ident,
1587 &replace_offset, siglen - 1, new_sig);
1588 found_sig = TRUE;
1589 } else {
1590 g_free(compare_str);
1591 while (message_split[i]) {
1592 /* put sig separator back to search */
1593 compare_str = g_strconcat("\n-- \n", message_split[i], NULL);
1594
1595 /* try to find occurance of old signature */
1596 if (g_ascii_strncasecmp(old_sig, compare_str, siglen) == 0) {
1597 replace_identity_signature(bsmsg, ident, old_ident,
1598 &replace_offset, siglen,
1599 new_sig);
1600 found_sig = TRUE;
1601 }
1602
1603 replace_offset +=
1604 g_utf8_strlen(i ? compare_str : message_split[i], -1);
1605 g_free(compare_str);
1606 i++;
1607 }
1608 }
1609 /* if no sig seperators found, do a slower brute force
1610 * approach. We could have stopped earlier if the message was
1611 * empty, but we didn't. Now, it is really time to do
1612 * that... */
1613 if (*message_text && !found_sig) {
1614 compare_str = message_text;
1615 replace_offset = 0;
1616
1617 /* check the special case of starting a message with a sig */
1618 tmpstr = g_strconcat("\n", message_text, NULL);
1619
1620 if (g_ascii_strncasecmp(old_sig, tmpstr, siglen) == 0) {
1621 g_free(tmpstr);
1622 replace_identity_signature(bsmsg, ident, old_ident,
1623 &replace_offset, siglen - 1,
1624 new_sig);
1625 } else {
1626 g_free(tmpstr);
1627 replace_offset++;
1628 compare_str = g_utf8_next_char(compare_str);
1629 while (*compare_str) {
1630 if (g_ascii_strncasecmp(old_sig, compare_str, siglen) == 0) {
1631 replace_identity_signature(bsmsg, ident, old_ident,
1632 &replace_offset, siglen,
1633 new_sig);
1634 }
1635 replace_offset++;
1636 compare_str = g_utf8_next_char(compare_str);
1637 }
1638 }
1639 }
1640 g_strfreev(message_split);
1641 }
1642 sw_set_active(bsmsg, "SendMPAlt", bsmsg->ident->send_mp_alternative);
1643
1644 #ifdef HAVE_GPGME
1645 bsmsg_update_gpg_ui_on_ident_change(bsmsg, ident);
1646 #endif
1647
1648 g_free(old_sig);
1649 g_free(new_sig);
1650 g_free(message_text);
1651
1652 libbalsa_address_view_set_domain(bsmsg->recipient_view, ident->domain);
1653
1654 sw_set_active(bsmsg, "RequestMDN", ident->request_mdn);
1655 }
1656
1657
1658 static void
sw_size_alloc_cb(GtkWidget * window,GtkAllocation * alloc)1659 sw_size_alloc_cb(GtkWidget * window, GtkAllocation * alloc)
1660 {
1661 GdkWindow *gdk_window;
1662
1663 if (!(gdk_window = gtk_widget_get_window(window)))
1664 return;
1665
1666 if (!(balsa_app.sw_maximized = gdk_window_get_state(gdk_window)
1667 & GDK_WINDOW_STATE_MAXIMIZED)) {
1668 balsa_app.sw_height = alloc->height;
1669 balsa_app.sw_width = alloc->width;
1670 }
1671 }
1672
1673
1674 /* remove_attachment - right mouse button callback */
1675 static void
remove_attachment(GtkWidget * menu_item,BalsaAttachInfo * info)1676 remove_attachment(GtkWidget * menu_item, BalsaAttachInfo *info)
1677 {
1678 GtkTreeIter iter;
1679 GtkTreeModel *model;
1680 GtkTreeSelection *selection;
1681 BalsaAttachInfo *test_info;
1682
1683 g_return_if_fail(info->bm != NULL);
1684
1685 /* get the selected element */
1686 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(info->bm->attachments[1]));
1687 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1688 return;
1689
1690 /* make sure we got the right element */
1691 gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &test_info, -1);
1692 if (test_info != info) {
1693 if (test_info)
1694 g_object_unref(test_info);
1695 return;
1696 }
1697 g_object_unref(test_info);
1698
1699 /* remove the attachment */
1700 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
1701 }
1702
1703 static void
set_attach_menu_sensitivity(GtkWidget * widget,gpointer data)1704 set_attach_menu_sensitivity(GtkWidget * widget, gpointer data)
1705 {
1706 gint mode =
1707 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "new-mode"));
1708
1709 if (mode)
1710 gtk_widget_set_sensitive(widget, mode != GPOINTER_TO_INT(data));
1711 }
1712
1713 /* change attachment mode - right mouse button callback */
1714 static void
change_attach_mode(GtkWidget * menu_item,BalsaAttachInfo * info)1715 change_attach_mode(GtkWidget * menu_item, BalsaAttachInfo *info)
1716 {
1717 gint new_mode =
1718 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item),
1719 "new-mode"));
1720 GtkTreeIter iter;
1721 GtkTreeModel *model;
1722 GtkTreeSelection *selection;
1723 BalsaAttachInfo *test_info;
1724
1725 g_return_if_fail(info->bm != NULL);
1726
1727 /* get the selected element */
1728 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(info->bm->attachments[1]));
1729 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1730 return;
1731
1732 /* make sure we got the right element */
1733 gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &test_info, -1);
1734 if (test_info != info) {
1735 if (test_info)
1736 g_object_unref(test_info);
1737 return;
1738 }
1739 g_object_unref(test_info);
1740
1741 /* verify that the user *really* wants to attach as reference */
1742 if (info->mode != new_mode && new_mode == LIBBALSA_ATTACH_AS_EXTBODY) {
1743 GtkWidget *extbody_dialog, *parent;
1744 gint result;
1745
1746 parent = gtk_widget_get_toplevel(menu_item);
1747 extbody_dialog =
1748 gtk_message_dialog_new(GTK_WINDOW(parent),
1749 GTK_DIALOG_DESTROY_WITH_PARENT,
1750 GTK_MESSAGE_QUESTION,
1751 GTK_BUTTONS_YES_NO,
1752 _("Saying yes will not send the file "
1753 "`%s' itself, but just a MIME "
1754 "message/external-body reference. "
1755 "Note that the recipient must "
1756 "have proper permissions to see the "
1757 "`real' file.\n\n"
1758 "Do you really want to attach "
1759 "this file as reference?"),
1760 libbalsa_vfs_get_uri_utf8(info->file_uri));
1761 #if HAVE_MACOSX_DESKTOP
1762 libbalsa_macosx_menu_for_parent(extbody_dialog, GTK_WINDOW(parent));
1763 #endif
1764 gtk_window_set_title(GTK_WINDOW(extbody_dialog),
1765 _("Attach as Reference?"));
1766 result = gtk_dialog_run(GTK_DIALOG(extbody_dialog));
1767 gtk_widget_destroy(extbody_dialog);
1768 if (result != GTK_RESPONSE_YES)
1769 return;
1770 }
1771
1772 /* change the attachment mode */
1773 info->mode = new_mode;
1774 gtk_list_store_set(GTK_LIST_STORE(model), &iter, ATTACH_MODE_COLUMN,
1775 info->mode, -1);
1776
1777 /* set the menu's sensitivities */
1778 gtk_container_forall(GTK_CONTAINER(gtk_widget_get_parent(menu_item)),
1779 set_attach_menu_sensitivity,
1780 GINT_TO_POINTER(info->mode));
1781 }
1782
1783
1784 /* attachment vfs menu - right mouse button callback */
1785 static void
attachment_menu_vfs_cb(GtkWidget * menu_item,BalsaAttachInfo * info)1786 attachment_menu_vfs_cb(GtkWidget * menu_item, BalsaAttachInfo * info)
1787 {
1788 GError *err = NULL;
1789 gboolean result;
1790
1791 g_return_if_fail(info != NULL);
1792
1793 result = libbalsa_vfs_launch_app(info->file_uri,
1794 G_OBJECT(menu_item),
1795 &err);
1796 if (!result)
1797 balsa_information(LIBBALSA_INFORMATION_WARNING,
1798 _("Could not launch application: %s"),
1799 err ? err->message : "Unknown error");
1800 g_clear_error(&err);
1801 }
1802
1803
1804 /* URL external body - right mouse button callback */
1805 static void
on_open_url_cb(GtkWidget * menu_item,BalsaAttachInfo * info)1806 on_open_url_cb(GtkWidget * menu_item, BalsaAttachInfo * info)
1807 {
1808 GdkScreen *screen;
1809 GError *err = NULL;
1810 const gchar * uri;
1811
1812 g_return_if_fail(info != NULL);
1813 uri = libbalsa_vfs_get_uri(info->file_uri);
1814 g_return_if_fail(uri != NULL);
1815
1816 g_message("open URL %s", uri);
1817 screen = gtk_widget_get_screen(menu_item);
1818 gtk_show_uri(screen, uri, gtk_get_current_event_time(), &err);
1819 if (err) {
1820 balsa_information(LIBBALSA_INFORMATION_WARNING,
1821 _("Error showing %s: %s\n"),
1822 uri, err->message);
1823 g_error_free(err);
1824 }
1825 }
1826
1827
1828 static void
show_attachment_widget(BalsaSendmsg * bsmsg)1829 show_attachment_widget(BalsaSendmsg *bsmsg)
1830 {
1831 int pos;
1832 for(pos=0; pos<4; pos++)
1833 gtk_widget_show_all(GTK_WIDGET(bsmsg->attachments[pos]));
1834 }
1835
1836 static void
hide_attachment_widget(BalsaSendmsg * bsmsg)1837 hide_attachment_widget(BalsaSendmsg *bsmsg)
1838 {
1839 int pos;
1840 for(pos=0; pos<4; pos++)
1841 gtk_widget_hide(GTK_WIDGET(bsmsg->attachments[pos]));
1842 }
1843
1844 /* Ask the user for a charset; returns ((LibBalsaCodeset) -1) on cancel. */
1845 static void
sw_charset_combo_box_changed(GtkComboBox * combo_box,GtkWidget * charset_button)1846 sw_charset_combo_box_changed(GtkComboBox * combo_box,
1847 GtkWidget * charset_button)
1848 {
1849 gtk_widget_set_sensitive(charset_button,
1850 gtk_combo_box_get_active(combo_box) == 0);
1851 }
1852
1853 static LibBalsaCodeset
sw_get_user_codeset(BalsaSendmsg * bsmsg,gboolean * change_type,const gchar * mime_type,const char * fname)1854 sw_get_user_codeset(BalsaSendmsg * bsmsg, gboolean * change_type,
1855 const gchar * mime_type, const char *fname)
1856 {
1857 GtkWidget *combo_box = NULL;
1858 gint codeset = -1;
1859 GtkWidget *dialog =
1860 gtk_dialog_new_with_buttons(_("Choose charset"),
1861 GTK_WINDOW(bsmsg->window),
1862 GTK_DIALOG_DESTROY_WITH_PARENT,
1863 GTK_STOCK_OK, GTK_RESPONSE_OK,
1864 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1865 NULL);
1866 gchar *msg = g_strdup_printf
1867 (_("File\n%s\nis not encoded in US-ASCII or UTF-8.\n"
1868 "Please choose the charset used to encode the file."),
1869 fname);
1870 GtkWidget *info = gtk_label_new(msg);
1871 GtkWidget *charset_button = libbalsa_charset_button_new();
1872 GtkBox *content_box;
1873
1874 #if HAVE_MACOSX_DESKTOP
1875 libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(bsmsg->window));
1876 #endif
1877
1878 g_free(msg);
1879 content_box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
1880 gtk_box_pack_start(content_box, info, FALSE, TRUE, 5);
1881 gtk_box_pack_start(content_box, charset_button, TRUE, TRUE, 5);
1882 gtk_widget_show(info);
1883 gtk_widget_show(charset_button);
1884 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
1885
1886 if (change_type) {
1887 GtkWidget *label = gtk_label_new(_("Attach as MIME type:"));
1888 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
1889 combo_box = gtk_combo_box_text_new();
1890
1891 gtk_box_pack_start(content_box, hbox, TRUE, TRUE, 5);
1892 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
1893 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_box),
1894 mime_type);
1895 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_box),
1896 "application/octet-stream");
1897 gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), 0);
1898 g_signal_connect(G_OBJECT(combo_box), "changed",
1899 G_CALLBACK(sw_charset_combo_box_changed),
1900 charset_button);
1901 gtk_box_pack_start(GTK_BOX(hbox), combo_box, TRUE, TRUE, 0);
1902 gtk_widget_show_all(hbox);
1903 }
1904
1905 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
1906 if (change_type)
1907 *change_type =
1908 gtk_combo_box_get_active(GTK_COMBO_BOX(combo_box)) != 0;
1909 if (!change_type || !*change_type)
1910 codeset = gtk_combo_box_get_active(GTK_COMBO_BOX(charset_button));
1911 }
1912
1913 gtk_widget_destroy(dialog);
1914 return (LibBalsaCodeset) codeset;
1915 }
1916
1917 static gboolean
sw_set_charset(BalsaSendmsg * bsmsg,const gchar * filename,const gchar * content_type,gboolean * change_type,gchar ** attach_charset)1918 sw_set_charset(BalsaSendmsg * bsmsg, const gchar * filename,
1919 const gchar * content_type, gboolean * change_type,
1920 gchar ** attach_charset)
1921 {
1922 const gchar *charset;
1923 LibBalsaTextAttribute attr;
1924
1925 attr = libbalsa_text_attr_file(filename);
1926 if ((gint) attr < 0)
1927 return FALSE;
1928
1929 if (attr == 0)
1930 charset = "us-ascii";
1931 else if (attr & LIBBALSA_TEXT_HI_UTF8)
1932 charset = "UTF-8";
1933 else {
1934 LibBalsaCodesetInfo *info;
1935 LibBalsaCodeset codeset =
1936 sw_get_user_codeset(bsmsg, change_type, content_type, filename);
1937 if (*change_type)
1938 return TRUE;
1939 if (codeset == (LibBalsaCodeset) (-1))
1940 return FALSE;
1941
1942 info = &libbalsa_codeset_info[codeset];
1943 charset = info->std;
1944 if (info->win && (attr & LIBBALSA_TEXT_HI_CTRL)) {
1945 charset = info->win;
1946 balsa_information_parented(GTK_WINDOW(bsmsg->window),
1947 LIBBALSA_INFORMATION_WARNING,
1948 _("Character set for file %s changed "
1949 "from \"%s\" to \"%s\"."), filename,
1950 info->std, info->win);
1951 }
1952 }
1953 *attach_charset = g_strdup(charset);
1954
1955 return TRUE;
1956 }
1957
1958
1959 static LibBalsaMessageHeaders *
get_fwd_mail_headers(const gchar * mailfile)1960 get_fwd_mail_headers(const gchar *mailfile)
1961 {
1962 int fd;
1963 GMimeStream *stream;
1964 GMimeParser *parser;
1965 GMimeMessage *message;
1966 LibBalsaMessageHeaders *headers;
1967
1968 /* try to open the mail file */
1969 if ((fd = open(mailfile, O_RDONLY)) == -1)
1970 return NULL;
1971 if ((stream = g_mime_stream_fs_new(fd)) == NULL) {
1972 close(fd);
1973 return NULL;
1974 }
1975
1976 /* parse the file */
1977 parser = g_mime_parser_new();
1978 g_mime_parser_init_with_stream(parser, stream);
1979 message = g_mime_parser_construct_message (parser);
1980 g_object_unref (parser);
1981 g_object_unref(stream);
1982 close(fd);
1983
1984 /* get the headers from the gmime message */
1985 headers = g_new0(LibBalsaMessageHeaders, 1);
1986 libbalsa_message_headers_from_gmime(headers, message);
1987 if (!headers->subject) {
1988 const gchar * subject = g_mime_message_get_subject(message);
1989
1990 if (!subject)
1991 headers->subject = g_strdup(_("(no subject)"));
1992 else
1993 headers->subject = g_mime_utils_header_decode_text(subject);
1994 }
1995 libbalsa_utf8_sanitize(&headers->subject,
1996 balsa_app.convert_unknown_8bit,
1997 NULL);
1998
1999 /* unref the gmime message and return the information */
2000 g_object_unref(message);
2001 return headers;
2002 }
2003
2004
2005 /* add_attachment:
2006 adds given filename (uri format) to the list.
2007 */
2008 gboolean
add_attachment(BalsaSendmsg * bsmsg,const gchar * filename,gboolean is_a_temp_file,const gchar * forced_mime_type)2009 add_attachment(BalsaSendmsg * bsmsg, const gchar *filename,
2010 gboolean is_a_temp_file, const gchar *forced_mime_type)
2011 {
2012 LibbalsaVfs * file_uri;
2013 GtkTreeModel *model;
2014 GtkTreeIter iter;
2015 BalsaAttachInfo *attach_data;
2016 gboolean can_inline, is_fwd_message;
2017 gchar *content_type = NULL;
2018 gchar *utf8name;
2019 GError *err = NULL;
2020 GdkPixbuf *pixbuf;
2021 GtkWidget *menu_item;
2022 gchar *content_desc;
2023
2024 if (balsa_app.debug)
2025 fprintf(stderr, "Trying to attach '%s'\n", filename);
2026 if (!(file_uri = libbalsa_vfs_new_from_uri(filename))) {
2027 balsa_information_parented(GTK_WINDOW(bsmsg->window),
2028 LIBBALSA_INFORMATION_ERROR,
2029 _("Cannot create file URI object for %s"),
2030 filename);
2031 return FALSE;
2032 }
2033 if (!libbalsa_vfs_is_regular_file(file_uri, &err)) {
2034 balsa_information_parented(GTK_WINDOW(bsmsg->window),
2035 LIBBALSA_INFORMATION_ERROR,
2036 "%s: %s", filename,
2037 err && err->message ? err->message : _("unknown error"));
2038 g_error_free(err);
2039 g_object_unref(file_uri);
2040 return FALSE;
2041 }
2042
2043 #if defined(ENABLE_TOUCH_UI)
2044 if(!bsmsg_check_format_compatibility(GTK_WINDOW(bsmsg->window),
2045 filename)) {
2046 g_object_unref(file_uri);
2047 return FALSE;
2048 }
2049 #endif /* ENABLE_TOUCH_UI */
2050
2051 /* get the pixbuf for the attachment's content type */
2052 is_fwd_message = forced_mime_type &&
2053 !g_ascii_strncasecmp(forced_mime_type, "message/", 8) && is_a_temp_file;
2054 if (is_fwd_message)
2055 content_type = g_strdup(forced_mime_type);
2056 pixbuf =
2057 libbalsa_icon_finder(GTK_WIDGET(bsmsg->window), forced_mime_type,
2058 file_uri, &content_type,
2059 GTK_ICON_SIZE_LARGE_TOOLBAR);
2060 if (!content_type)
2061 /* Last ditch. */
2062 content_type = g_strdup("application/octet-stream");
2063
2064 /* create a new attachment info block */
2065 attach_data = balsa_attach_info_new(bsmsg);
2066 attach_data->charset = NULL;
2067 if (!g_ascii_strncasecmp(content_type, "text/", 5)) {
2068 gboolean change_type = FALSE;
2069 if (!sw_set_charset(bsmsg, filename, content_type,
2070 &change_type, &attach_data->charset)) {
2071 g_free(content_type);
2072 g_object_unref(attach_data);
2073 return FALSE;
2074 }
2075 if (change_type) {
2076 forced_mime_type = "application/octet-stream";
2077 g_free(content_type);
2078 content_type = g_strdup(forced_mime_type);
2079 }
2080 }
2081
2082 if (is_fwd_message) {
2083 attach_data->headers = get_fwd_mail_headers(filename);
2084 if (!attach_data->headers)
2085 utf8name = g_strdup(_("forwarded message"));
2086 else {
2087 gchar *tmp =
2088 internet_address_list_to_string(attach_data->headers->from,
2089 FALSE);
2090 utf8name = g_strdup_printf(_("Message from %s, subject: \"%s\""),
2091 tmp,
2092 attach_data->headers->subject);
2093 g_free(tmp);
2094 }
2095 } else {
2096 const gchar *uri_utf8 = libbalsa_vfs_get_uri_utf8(file_uri);
2097 const gchar *home = g_getenv("HOME");
2098
2099 if (home && !strncmp(uri_utf8, "file://", 7) &&
2100 !strncmp(uri_utf8 + 7, home, strlen(home))) {
2101 utf8name = g_strdup_printf("~%s", uri_utf8 + 7 + strlen(home));
2102 } else
2103 utf8name = g_strdup(uri_utf8);
2104 }
2105
2106 model = BALSA_MSG_ATTACH_MODEL(bsmsg);
2107 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
2108
2109 attach_data->file_uri = file_uri;
2110 attach_data->force_mime_type = g_strdup(forced_mime_type);
2111
2112 attach_data->delete_on_destroy = is_a_temp_file;
2113 can_inline = !is_a_temp_file &&
2114 (!g_ascii_strncasecmp(content_type, "text/", 5) ||
2115 !g_ascii_strncasecmp(content_type, "image/", 6));
2116 attach_data->mode = LIBBALSA_ATTACH_AS_ATTACHMENT;
2117
2118 /* build the attachment's popup menu */
2119 attach_data->popup_menu = gtk_menu_new();
2120
2121 /* only real text/... and image/... parts may be inlined */
2122 if (can_inline) {
2123 menu_item =
2124 gtk_menu_item_new_with_label(_(attach_modes
2125 [LIBBALSA_ATTACH_AS_INLINE]));
2126 g_object_set_data(G_OBJECT(menu_item), "new-mode",
2127 GINT_TO_POINTER(LIBBALSA_ATTACH_AS_INLINE));
2128 g_signal_connect(G_OBJECT(menu_item), "activate",
2129 G_CALLBACK(change_attach_mode),
2130 (gpointer)attach_data);
2131 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2132 menu_item);
2133 }
2134
2135 /* all real files can be attachments */
2136 if (can_inline || !is_a_temp_file) {
2137 menu_item =
2138 gtk_menu_item_new_with_label(_(attach_modes
2139 [LIBBALSA_ATTACH_AS_ATTACHMENT]));
2140 gtk_widget_set_sensitive(menu_item, FALSE);
2141 g_object_set_data(G_OBJECT(menu_item), "new-mode",
2142 GINT_TO_POINTER(LIBBALSA_ATTACH_AS_ATTACHMENT));
2143 g_signal_connect(G_OBJECT(menu_item), "activate",
2144 G_CALLBACK(change_attach_mode),
2145 (gpointer)attach_data);
2146 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2147 menu_item);
2148 }
2149
2150 /* real files may be references (external body) */
2151 if (!is_a_temp_file) {
2152 menu_item =
2153 gtk_menu_item_new_with_label(_(attach_modes
2154 [LIBBALSA_ATTACH_AS_EXTBODY]));
2155 g_object_set_data(G_OBJECT(menu_item), "new-mode",
2156 GINT_TO_POINTER(LIBBALSA_ATTACH_AS_EXTBODY));
2157 g_signal_connect(G_OBJECT(menu_item), "activate",
2158 G_CALLBACK(change_attach_mode),
2159 (gpointer)attach_data);
2160 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2161 menu_item);
2162 }
2163
2164 /* an attachment can be removed */
2165 menu_item =
2166 gtk_menu_item_new_with_label(_("Remove"));
2167 g_signal_connect(G_OBJECT (menu_item), "activate",
2168 G_CALLBACK(remove_attachment),
2169 (gpointer)attach_data);
2170 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2171 menu_item);
2172
2173 /* add the usual vfs menu so the user can inspect what (s)he actually
2174 attached... (only for non-message attachments) */
2175 if (!is_fwd_message)
2176 libbalsa_vfs_fill_menu_by_content_type(GTK_MENU(attach_data->popup_menu),
2177 content_type,
2178 G_CALLBACK(attachment_menu_vfs_cb),
2179 (gpointer)attach_data);
2180 gtk_widget_show_all(attach_data->popup_menu);
2181
2182 /* append to the list store */
2183 content_desc =libbalsa_vfs_content_description(content_type);
2184 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
2185 ATTACH_INFO_COLUMN, attach_data,
2186 ATTACH_ICON_COLUMN, pixbuf,
2187 ATTACH_TYPE_COLUMN, content_desc,
2188 ATTACH_MODE_COLUMN, attach_data->mode,
2189 ATTACH_SIZE_COLUMN, libbalsa_vfs_get_size(file_uri),
2190 ATTACH_DESC_COLUMN, utf8name,
2191 -1);
2192 g_object_unref(attach_data);
2193 g_object_unref(pixbuf);
2194 g_free(utf8name);
2195 g_free(content_type);
2196 g_free(content_desc);
2197
2198 show_attachment_widget(bsmsg);
2199
2200 return TRUE;
2201 }
2202
2203 /* add_urlref_attachment:
2204 adds given url as reference to the to the list.
2205 frees url.
2206 */
2207 static gboolean
add_urlref_attachment(BalsaSendmsg * bsmsg,gchar * url)2208 add_urlref_attachment(BalsaSendmsg * bsmsg, gchar *url)
2209 {
2210 GtkTreeModel *model;
2211 GtkTreeIter iter;
2212 BalsaAttachInfo *attach_data;
2213 GdkPixbuf * pixbuf;
2214 GtkWidget *menu_item;
2215
2216 if (balsa_app.debug)
2217 fprintf(stderr, "Trying to attach '%s'\n", url);
2218
2219 /* get the pixbuf for the attachment's content type */
2220 pixbuf =
2221 gtk_widget_render_icon_pixbuf(GTK_WIDGET(balsa_app.main_window),
2222 GTK_STOCK_JUMP_TO,
2223 GTK_ICON_SIZE_MENU);
2224
2225 /* create a new attachment info block */
2226 attach_data = balsa_attach_info_new(bsmsg);
2227 attach_data->charset = NULL;
2228
2229 model = BALSA_MSG_ATTACH_MODEL(bsmsg);
2230 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
2231
2232 attach_data->uri_ref = g_strconcat("URL:", url, NULL);
2233 attach_data->force_mime_type = g_strdup("message/external-body");
2234 attach_data->delete_on_destroy = FALSE;
2235 attach_data->mode = LIBBALSA_ATTACH_AS_EXTBODY;
2236
2237 /* build the attachment's popup menu - may only be removed */
2238 attach_data->popup_menu = gtk_menu_new();
2239 menu_item =
2240 gtk_menu_item_new_with_label(_("Remove"));
2241 g_signal_connect(G_OBJECT (menu_item), "activate",
2242 G_CALLBACK(remove_attachment),
2243 (gpointer)attach_data);
2244 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2245 menu_item);
2246
2247 /* add a separator and the usual vfs menu so the user can inspect what
2248 (s)he actually attached... (only for non-message attachments) */
2249 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2250 gtk_separator_menu_item_new());
2251 menu_item =
2252 gtk_menu_item_new_with_label(_("Open..."));
2253 g_signal_connect(G_OBJECT (menu_item), "activate",
2254 G_CALLBACK(on_open_url_cb),
2255 (gpointer)attach_data);
2256 gtk_menu_shell_append(GTK_MENU_SHELL(attach_data->popup_menu),
2257 menu_item);
2258 gtk_widget_show_all(attach_data->popup_menu);
2259
2260 /* append to the list store */
2261 gtk_list_store_set(GTK_LIST_STORE(model), &iter,
2262 ATTACH_INFO_COLUMN, attach_data,
2263 ATTACH_ICON_COLUMN, pixbuf,
2264 ATTACH_TYPE_COLUMN, _("(URL)"),
2265 ATTACH_MODE_COLUMN, attach_data->mode,
2266 ATTACH_SIZE_COLUMN, 0,
2267 ATTACH_DESC_COLUMN, url,
2268 -1);
2269 g_object_unref(attach_data);
2270 g_object_unref(pixbuf);
2271 g_free(url);
2272
2273 show_attachment_widget(bsmsg);
2274
2275 return TRUE;
2276 }
2277
2278 /* attach_dialog_ok:
2279 processes the attachment file selection. Adds them to the list,
2280 showing the attachment list, if was hidden.
2281 */
2282 static void
attach_dialog_response(GtkWidget * dialog,gint response,BalsaSendmsg * bsmsg)2283 attach_dialog_response(GtkWidget * dialog, gint response,
2284 BalsaSendmsg * bsmsg)
2285 {
2286 GtkFileChooser * fc;
2287 GSList *files, *list;
2288 int res = 0;
2289
2290 g_object_set_data(G_OBJECT(bsmsg->window),
2291 "balsa-sendmsg-window-attach-dialog", NULL);
2292
2293 if (response != GTK_RESPONSE_OK) {
2294 gtk_widget_destroy(dialog);
2295 return;
2296 }
2297
2298 fc = GTK_FILE_CHOOSER(dialog);
2299 files = gtk_file_chooser_get_uris(fc);
2300 for (list = files; list; list = list->next) {
2301 if(!add_attachment(bsmsg, list->data, FALSE, NULL))
2302 res++;
2303 g_free(list->data);
2304 }
2305
2306 g_slist_free(files);
2307
2308 g_free(balsa_app.attach_dir);
2309 balsa_app.attach_dir = gtk_file_chooser_get_current_folder_uri(fc);
2310
2311 if (res == 0)
2312 gtk_widget_destroy(dialog);
2313 }
2314
2315 static GtkFileChooser *
sw_attach_dialog(BalsaSendmsg * bsmsg)2316 sw_attach_dialog(BalsaSendmsg * bsmsg)
2317 {
2318 GtkWidget *fsw;
2319 GtkFileChooser *fc;
2320
2321 fsw =
2322 gtk_file_chooser_dialog_new(_("Attach file"),
2323 GTK_WINDOW(bsmsg->window),
2324 GTK_FILE_CHOOSER_ACTION_OPEN,
2325 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2326 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
2327 #if HAVE_MACOSX_DESKTOP
2328 libbalsa_macosx_menu_for_parent(fsw, GTK_WINDOW(bsmsg->window));
2329 #endif
2330 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(fsw),
2331 libbalsa_vfs_local_only());
2332 gtk_window_set_destroy_with_parent(GTK_WINDOW(fsw), TRUE);
2333
2334 fc = GTK_FILE_CHOOSER(fsw);
2335 gtk_file_chooser_set_select_multiple(fc, TRUE);
2336 if (balsa_app.attach_dir)
2337 gtk_file_chooser_set_current_folder_uri(fc, balsa_app.attach_dir);
2338
2339 g_signal_connect(G_OBJECT(fc), "response",
2340 G_CALLBACK(attach_dialog_response), bsmsg);
2341
2342 gtk_widget_show(fsw);
2343
2344 return fc;
2345 }
2346
2347 /* attach_clicked - menu callback */
2348 static void
attach_clicked(GtkAction * action,gpointer data)2349 attach_clicked(GtkAction * action, gpointer data)
2350 {
2351 sw_attach_dialog((BalsaSendmsg *) data);
2352 }
2353
2354 /* attach_message:
2355 returns TRUE on success, FALSE on failure.
2356 */
2357 static gboolean
attach_message(BalsaSendmsg * bsmsg,LibBalsaMessage * message)2358 attach_message(BalsaSendmsg *bsmsg, LibBalsaMessage *message)
2359 {
2360 gchar *name, *tmp_file_name;
2361
2362 if (libbalsa_mktempdir(&tmp_file_name) == FALSE)
2363 return FALSE;
2364 name = g_strdup_printf("%s/forwarded-message", tmp_file_name);
2365 g_free(tmp_file_name);
2366
2367 if(!libbalsa_message_save(message, name)) {
2368 g_free(name);
2369 return FALSE;
2370 }
2371 tmp_file_name = g_filename_to_uri(name, NULL, NULL);
2372 g_free(name);
2373 add_attachment(bsmsg, tmp_file_name, TRUE, "message/rfc822");
2374 g_free(tmp_file_name);
2375 return TRUE;
2376 }
2377
2378 static void
insert_selected_messages(BalsaSendmsg * bsmsg,QuoteType type)2379 insert_selected_messages(BalsaSendmsg *bsmsg, QuoteType type)
2380 {
2381 GtkTextBuffer *buffer =
2382 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
2383 GtkWidget *index =
2384 balsa_window_find_current_index(balsa_app.main_window);
2385 GList *l;
2386
2387 if (index && (l = balsa_index_selected_list(BALSA_INDEX(index)))) {
2388 GList *node;
2389
2390 for (node = l; node; node = g_list_next(node)) {
2391 LibBalsaMessage *message = node->data;
2392 GString *body = quote_message_body(bsmsg, message, type);
2393 gtk_text_buffer_insert_at_cursor(buffer, body->str, body->len);
2394 g_string_free(body, TRUE);
2395 }
2396 g_list_foreach(l, (GFunc)g_object_unref, NULL);
2397 g_list_free(l);
2398 }
2399 }
2400
2401 static void
include_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)2402 include_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
2403 {
2404 insert_selected_messages(bsmsg, QUOTE_ALL);
2405 }
2406
2407
2408 static void
attach_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)2409 attach_message_cb(GtkAction * action, BalsaSendmsg *bsmsg)
2410 {
2411 GtkWidget *index =
2412 balsa_window_find_current_index(balsa_app.main_window);
2413
2414 if (index) {
2415 GList *node, *l = balsa_index_selected_list(BALSA_INDEX(index));
2416
2417 for (node = l; node; node = g_list_next(node)) {
2418 LibBalsaMessage *message = node->data;
2419
2420 if(!attach_message(bsmsg, message)) {
2421 balsa_information_parented(GTK_WINDOW(bsmsg->window),
2422 LIBBALSA_INFORMATION_WARNING,
2423 _("Attaching message failed.\n"
2424 "Possible reason: not enough temporary space"));
2425 break;
2426 }
2427 }
2428 g_list_foreach(l, (GFunc)g_object_unref, NULL);
2429 g_list_free(l);
2430 }
2431 }
2432
2433
2434 #if 0
2435 static gint include_messages_cb(GtkWidget *widget, BalsaSendmsg *bsmsg)
2436 {
2437 return insert_selected_messages(bsmsg, TRUE);
2438 }
2439 #endif /* 0 */
2440
2441 /* attachments_add - attachments field D&D callback */
2442 static GSList*
uri2gslist(const char * uri_list)2443 uri2gslist(const char *uri_list)
2444 {
2445 GSList *list = NULL;
2446
2447 while (*uri_list) {
2448 char *linebreak = strchr(uri_list, 13);
2449 int length;
2450
2451 if (!linebreak || linebreak[1] != '\n')
2452 return list;
2453
2454 length = linebreak - uri_list;
2455
2456 if (length && uri_list[0] != '#') {
2457 gchar *this_uri = g_strndup(uri_list, length);
2458
2459 if (this_uri)
2460 list = g_slist_append(list, this_uri);
2461 }
2462
2463 uri_list = linebreak + 2;
2464 }
2465 return list;
2466 }
2467
2468 /* Helper: check if the passed parameter contains a valid RFC 2396 URI (leading
2469 * & trailing whitespaces allowed). Return a newly allocated string with the
2470 * spaces stripped on success or NULL on fail. Note that the URI may still be
2471 * malformed. */
2472 static gchar *
rfc2396_uri(const gchar * instr)2473 rfc2396_uri(const gchar *instr)
2474 {
2475 gchar *s1, *uri;
2476 static const gchar *uri_extra = ";/?:@&=+$,-_.!~*'()%";
2477
2478 /* remove leading and trailing whitespaces */
2479 uri = g_strchomp(g_strchug(g_strdup(instr)));
2480
2481 /* check that the string starts with ftp[s]:// or http[s]:// */
2482 if (g_ascii_strncasecmp(uri, "ftp://", 6) &&
2483 g_ascii_strncasecmp(uri, "ftps://", 7) &&
2484 g_ascii_strncasecmp(uri, "http://", 7) &&
2485 g_ascii_strncasecmp(uri, "https://", 8)) {
2486 g_free(uri);
2487 return NULL;
2488 }
2489
2490 /* verify that the string contains only valid chars (see rfc 2396) */
2491 s1 = uri + 6; /* skip verified beginning */
2492 while (*s1 != '\0') {
2493 if (!g_ascii_isalnum(*s1) && !strchr(uri_extra, *s1)) {
2494 g_free(uri);
2495 return NULL;
2496 }
2497 s1++;
2498 }
2499
2500 /* success... */
2501 return uri;
2502 }
2503
2504 static void
attachments_add(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time,BalsaSendmsg * bsmsg)2505 attachments_add(GtkWidget * widget,
2506 GdkDragContext * context,
2507 gint x,
2508 gint y,
2509 GtkSelectionData * selection_data,
2510 guint info, guint32 time, BalsaSendmsg * bsmsg)
2511 {
2512 gboolean drag_result = TRUE;
2513
2514 if (balsa_app.debug)
2515 printf("attachments_add: info %d\n", info);
2516 if (info == TARGET_MESSAGES) {
2517 BalsaIndex *index =
2518 *(BalsaIndex **) gtk_selection_data_get_data(selection_data);
2519 LibBalsaMailbox *mailbox = index->mailbox_node->mailbox;
2520 GArray *selected = balsa_index_selected_msgnos_new(index);
2521 guint i;
2522
2523 for (i = 0; i < selected->len; i++) {
2524 guint msgno = g_array_index(selected, guint, i);
2525 LibBalsaMessage *message =
2526 libbalsa_mailbox_get_message(mailbox, msgno);
2527 if (!message)
2528 continue;
2529
2530 if(!attach_message(bsmsg, message))
2531 balsa_information_parented(GTK_WINDOW(bsmsg->window),
2532 LIBBALSA_INFORMATION_WARNING,
2533 _("Attaching message failed.\n"
2534 "Possible reason: not enough temporary space"));
2535 g_object_unref(message);
2536 }
2537 balsa_index_selected_msgnos_free(index, selected);
2538 } else if (info == TARGET_URI_LIST) {
2539 GSList *uri_list =
2540 uri2gslist((gchar *)
2541 gtk_selection_data_get_data(selection_data));
2542 for (; uri_list; uri_list = g_slist_next(uri_list)) {
2543 add_attachment(bsmsg, uri_list->data, FALSE, NULL);
2544 g_free(uri_list->data);
2545 }
2546 g_slist_free(uri_list);
2547 } else if( info == TARGET_STRING) {
2548 gchar *url =
2549 rfc2396_uri((gchar *)
2550 gtk_selection_data_get_data(selection_data));
2551
2552 if (url)
2553 add_urlref_attachment(bsmsg, url);
2554 else
2555 drag_result = FALSE;
2556 }
2557 gtk_drag_finish(context, drag_result, FALSE, time);
2558 }
2559
2560 /* to_add - e-mail (To, From, Cc, Bcc) field D&D callback */
2561 static void
to_add(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time)2562 to_add(GtkWidget * widget,
2563 GdkDragContext * context,
2564 gint x,
2565 gint y,
2566 GtkSelectionData * selection_data,
2567 guint info, guint32 time)
2568 {
2569 #if 0 /* FIXME */
2570 append_comma_separated(GTK_EDITABLE(widget),
2571 (gchar *) selection_data->data);
2572 #endif
2573 gtk_drag_finish(context, TRUE, FALSE, time);
2574 }
2575
2576 /*
2577 * static void create_email_or_string_entry()
2578 *
2579 * Creates a gtk_label()/entry pair.
2580 *
2581 * Input: GtkWidget* grid - Grid to attach to.
2582 * const gchar* label - Label string.
2583 * int y_pos - position in the grid.
2584 * arr - arr[1] is the entry widget.
2585 *
2586 * Output: GtkWidget* arr[] - arr[0] will be the label widget.
2587 */
2588 static void
create_email_or_string_entry(GtkWidget * grid,const gchar * label,int y_pos,GtkWidget * arr[])2589 create_email_or_string_entry(GtkWidget * grid, const gchar * label,
2590 int y_pos, GtkWidget * arr[])
2591 {
2592 PangoFontDescription *desc;
2593 GtkWidget *mnemonic_widget;
2594
2595 mnemonic_widget = arr[1];
2596 if (GTK_IS_FRAME(mnemonic_widget))
2597 mnemonic_widget = gtk_bin_get_child(GTK_BIN(mnemonic_widget));
2598 arr[0] = gtk_label_new_with_mnemonic(label);
2599 gtk_label_set_mnemonic_widget(GTK_LABEL(arr[0]), mnemonic_widget);
2600 gtk_misc_set_alignment(GTK_MISC(arr[0]), 0.0, 0.5);
2601 gtk_misc_set_padding(GTK_MISC(arr[0]), GNOME_PAD_SMALL,
2602 GNOME_PAD_SMALL);
2603 gtk_grid_attach(GTK_GRID(grid), arr[0], 0, y_pos, 1, 1);
2604
2605 desc = pango_font_description_from_string(balsa_app.message_font);
2606 gtk_widget_override_font(arr[1], desc);
2607 pango_font_description_free(desc);
2608
2609 gtk_widget_set_hexpand(arr[1], TRUE);
2610 gtk_grid_attach(GTK_GRID(grid), arr[1], 1, y_pos, 1, 1);
2611 }
2612
2613
2614 /*
2615 * static void create_string_entry()
2616 *
2617 * Creates a gtk_label()/gtk_entry() pair.
2618 *
2619 * Input: GtkWidget* grid - Grid to attach to.
2620 * const gchar* label - Label string.
2621 * int y_pos - position in the grid.
2622 *
2623 * Output: GtkWidget* arr[] - arr[0] will be the label widget.
2624 * - arr[1] will be the entry widget.
2625 */
2626 static void
create_string_entry(GtkWidget * grid,const gchar * label,int y_pos,GtkWidget * arr[])2627 create_string_entry(GtkWidget * grid, const gchar * label, int y_pos,
2628 GtkWidget * arr[])
2629 {
2630 arr[1] = gtk_entry_new();
2631 gtk_entry_set_max_length(GTK_ENTRY(arr[1]), 2048);
2632 create_email_or_string_entry(grid, label, y_pos, arr);
2633 }
2634
2635 /*
2636 * static void create_email_entry()
2637 *
2638 * Creates a gtk_label()/libbalsa_address_view() and button in a grid for
2639 * e-mail entries, eg. To:. It also sets up some callbacks in gtk.
2640 *
2641 * Input: GtkWidget *grid - grid to insert the widgets into.
2642 * int y_pos - How far down in the grid to put label.
2643 * BalsaSendmsg *bsmsg - The send message window
2644 * On return, bsmsg->address_view and bsmsg->addresses[1] have been set.
2645 */
2646
2647 #if 0
2648 static void
2649 sw_scroll_size_request(GtkWidget * widget, GtkRequisition * requisition)
2650 {
2651 gint focus_width;
2652 gint focus_pad;
2653 gint border_width;
2654 GtkPolicyType type = GTK_POLICY_NEVER;
2655
2656 gtk_widget_get_preferred_size(gtk_bin_get_child(GTK_BIN(widget)),
2657 NULL, requisition);
2658 gtk_widget_style_get(widget, "focus-line-width", &focus_width,
2659 "focus-padding", &focus_pad, NULL);
2660
2661 border_width =
2662 (gtk_container_get_border_width(GTK_CONTAINER(widget)) + focus_width +
2663 focus_pad) * 2;
2664 requisition->width += border_width;
2665 requisition->height += border_width;
2666 if (requisition->width > balsa_app.sw_width * 3 / 4) {
2667 requisition->width = balsa_app.sw_width * 3 / 4;
2668 type = GTK_POLICY_AUTOMATIC;
2669 requisition->height += 50;
2670 }
2671 if (requisition->height > 100) {
2672 requisition->height = 100;
2673 type = GTK_POLICY_AUTOMATIC;
2674 requisition->width += 50;
2675 }
2676 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(widget), type, type);
2677 }
2678 #endif
2679
2680 static void
create_email_entry(GtkWidget * grid,int y_pos,BalsaSendmsg * bsmsg,LibBalsaAddressView ** view,GtkWidget ** widget,const gchar * label,const gchar * const * types,guint n_types)2681 create_email_entry(GtkWidget * grid, int y_pos, BalsaSendmsg * bsmsg,
2682 LibBalsaAddressView ** view, GtkWidget ** widget,
2683 const gchar * label, const gchar * const *types,
2684 guint n_types)
2685 {
2686 GtkWidget *scroll;
2687
2688 *view = libbalsa_address_view_new(types, n_types,
2689 balsa_app.address_book_list,
2690 balsa_app.convert_unknown_8bit);
2691
2692 scroll = gtk_scrolled_window_new(NULL, NULL);
2693 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
2694 GTK_POLICY_AUTOMATIC,
2695 GTK_POLICY_AUTOMATIC);
2696 #if 0
2697 g_signal_connect(scroll, "size-request",
2698 G_CALLBACK(sw_scroll_size_request), NULL);
2699 #endif
2700 gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(*view));
2701
2702 widget[1] = gtk_frame_new(NULL);
2703 gtk_frame_set_shadow_type(GTK_FRAME(widget[1]), GTK_SHADOW_IN);
2704 gtk_container_add(GTK_CONTAINER(widget[1]), scroll);
2705
2706 create_email_or_string_entry(grid, _(label), y_pos, widget);
2707
2708 g_signal_connect(*view, "drag_data_received",
2709 G_CALLBACK(to_add), NULL);
2710 g_signal_connect(*view, "open-address-book",
2711 G_CALLBACK(address_book_cb), bsmsg);
2712 gtk_drag_dest_set(GTK_WIDGET(*view), GTK_DEST_DEFAULT_ALL,
2713 email_field_drop_types,
2714 ELEMENTS(email_field_drop_types),
2715 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2716
2717 libbalsa_address_view_set_domain(*view, bsmsg->ident->domain);
2718 g_signal_connect_swapped(gtk_tree_view_get_model(GTK_TREE_VIEW(*view)),
2719 "row-changed", G_CALLBACK(check_readiness),
2720 bsmsg);
2721 g_signal_connect_swapped(gtk_tree_view_get_model(GTK_TREE_VIEW(*view)),
2722 "row-deleted", G_CALLBACK(check_readiness),
2723 bsmsg);
2724 }
2725
2726 static void
sw_combo_box_changed(GtkComboBox * combo_box,BalsaSendmsg * bsmsg)2727 sw_combo_box_changed(GtkComboBox * combo_box, BalsaSendmsg * bsmsg)
2728 {
2729 GtkTreeIter iter;
2730
2731 if (gtk_combo_box_get_active_iter(combo_box, &iter)) {
2732 LibBalsaIdentity *ident;
2733
2734 gtk_tree_model_get(gtk_combo_box_get_model(combo_box), &iter,
2735 2, &ident, -1);
2736 update_bsmsg_identity(bsmsg, ident);
2737 g_object_unref(ident);
2738 }
2739 }
2740
2741 static void
create_from_entry(GtkWidget * grid,BalsaSendmsg * bsmsg)2742 create_from_entry(GtkWidget * grid, BalsaSendmsg * bsmsg)
2743 {
2744 GList *list;
2745 GtkListStore *store;
2746 GtkCellRenderer *renderer;
2747
2748 /* For each identity, store the address, the identity name, and a
2749 * ref to the identity in a combo-box.
2750 * Note: we can't depend on balsa_app.identities staying in the same
2751 * order while the compose window is open, so we need a ref to the
2752 * actual identity. */
2753 store = gtk_list_store_new(3,
2754 G_TYPE_STRING,
2755 G_TYPE_STRING,
2756 G_TYPE_OBJECT);
2757 for (list = balsa_app.identities; list; list = list->next) {
2758 LibBalsaIdentity *ident;
2759 gchar *from, *name;
2760 GtkTreeIter iter;
2761
2762 ident = list->data;
2763 from = internet_address_to_string(ident->ia, FALSE);
2764 name = g_strconcat("(", ident->identity_name, ")", NULL);
2765
2766 gtk_list_store_append(store, &iter);
2767 gtk_list_store_set(store, &iter,
2768 0, from,
2769 1, name,
2770 2, ident,
2771 -1);
2772
2773 g_free(from);
2774 g_free(name);
2775 }
2776 bsmsg->from[1] = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
2777 renderer = gtk_cell_renderer_text_new();
2778 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(bsmsg->from[1]), renderer,
2779 TRUE);
2780 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(bsmsg->from[1]),
2781 renderer, "text", 0, NULL);
2782 renderer = gtk_cell_renderer_text_new();
2783 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(bsmsg->from[1]), renderer,
2784 FALSE);
2785 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(bsmsg->from[1]),
2786 renderer, "text", 1, NULL);
2787 g_object_unref(store);
2788 g_signal_connect(bsmsg->from[1], "changed",
2789 G_CALLBACK(sw_combo_box_changed), bsmsg);
2790 create_email_or_string_entry(grid, _("F_rom:"), 0, bsmsg->from);
2791 }
2792
2793 static gboolean
attachment_button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)2794 attachment_button_press_cb(GtkWidget * widget, GdkEventButton * event,
2795 gpointer data)
2796 {
2797 GtkTreeView *tree_view = GTK_TREE_VIEW(widget);
2798 GtkTreePath *path;
2799
2800 g_return_val_if_fail(event, FALSE);
2801 if (event->type != GDK_BUTTON_PRESS || event->button != 3
2802 || event->window != gtk_tree_view_get_bin_window(tree_view))
2803 return FALSE;
2804
2805 if (gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y,
2806 &path, NULL, NULL, NULL)) {
2807 GtkTreeIter iter;
2808 GtkTreeSelection * selection =
2809 gtk_tree_view_get_selection(tree_view);
2810 GtkTreeModel * model = gtk_tree_view_get_model(tree_view);
2811
2812 gtk_tree_selection_unselect_all(selection);
2813 gtk_tree_selection_select_path(selection, path);
2814 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree_view), path, NULL,
2815 FALSE);
2816 if (gtk_tree_model_get_iter (model, &iter, path)) {
2817 BalsaAttachInfo *attach_info;
2818
2819 gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &attach_info, -1);
2820 if (attach_info) {
2821 if (attach_info->popup_menu)
2822 gtk_menu_popup(GTK_MENU(attach_info->popup_menu), NULL, NULL,
2823 NULL, NULL, event->button, event->time);
2824 g_object_unref(attach_info);
2825 }
2826 }
2827 gtk_tree_path_free(path);
2828 }
2829
2830 return TRUE;
2831 }
2832
2833
2834 static gboolean
attachment_popup_cb(GtkWidget * widget,gpointer user_data)2835 attachment_popup_cb(GtkWidget *widget, gpointer user_data)
2836 {
2837 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
2838 GtkTreeModel *model;
2839 GtkTreeIter iter;
2840 BalsaAttachInfo *attach_info;
2841
2842 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
2843 return FALSE;
2844
2845 gtk_tree_model_get(model, &iter, ATTACH_INFO_COLUMN, &attach_info, -1);
2846 if (attach_info) {
2847 if (attach_info->popup_menu)
2848 gtk_menu_popup(GTK_MENU(attach_info->popup_menu), NULL, NULL, NULL,
2849 NULL, 0, gtk_get_current_event_time());
2850 g_object_unref(attach_info);
2851 }
2852
2853 return TRUE;
2854 }
2855
2856
2857 static void
render_attach_mode(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2858 render_attach_mode(GtkTreeViewColumn *column, GtkCellRenderer *cell,
2859 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
2860 {
2861 gint mode;
2862
2863 gtk_tree_model_get(model, iter, ATTACH_MODE_COLUMN, &mode, -1);
2864 g_object_set(cell, "text", _(attach_modes[mode]), NULL);
2865 }
2866
2867
2868 static void
render_attach_size(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)2869 render_attach_size(GtkTreeViewColumn *column, GtkCellRenderer *cell,
2870 GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
2871 {
2872 gint mode;
2873 guint64 size;
2874 gchar *sstr;
2875
2876 gtk_tree_model_get(model, iter, ATTACH_MODE_COLUMN, &mode,
2877 ATTACH_SIZE_COLUMN, &size, -1);
2878 if (mode == LIBBALSA_ATTACH_AS_EXTBODY)
2879 sstr = g_strdup("-");
2880 else
2881 #if GLIB_CHECK_VERSION(2, 30, 0)
2882 sstr = g_format_size(size);
2883 #else /* GLIB_CHECK_VERSION(2, 30, 0) */
2884 sstr = g_format_size_for_display((goffset) size);
2885 #endif /* GLIB_CHECK_VERSION(2, 30, 0) */
2886 g_object_set(cell, "text", sstr, NULL);
2887 g_free(sstr);
2888 }
2889
2890
2891 /* create_info_pane
2892 creates upper panel with the message headers: From, To, ... and
2893 returns it.
2894 */
2895 static GtkWidget *
create_info_pane(BalsaSendmsg * bsmsg)2896 create_info_pane(BalsaSendmsg * bsmsg)
2897 {
2898 guint row = 0;
2899 GtkWidget *sw;
2900 GtkWidget *grid;
2901 GtkWidget *frame;
2902 GtkListStore *store;
2903 GtkCellRenderer *renderer;
2904 GtkTreeView *view;
2905 GtkTreeViewColumn *column;
2906
2907 grid = gtk_grid_new();
2908 gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
2909 gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
2910 gtk_container_set_border_width(GTK_CONTAINER(grid), 6);
2911
2912 /* From: */
2913 create_from_entry(grid, bsmsg);
2914
2915 #if !defined(ENABLE_TOUCH_UI)
2916 /* Create the 'Reply To:' entry before the regular recipients, to
2917 * get the initial focus in the regular recipients*/
2918 #define REPLY_TO_ROW 3
2919 create_email_entry(grid, REPLY_TO_ROW, bsmsg, &bsmsg->replyto_view,
2920 bsmsg->replyto, "R_eply To:", NULL, 0);
2921 #endif
2922
2923 /* To:, Cc:, and Bcc: */
2924 create_email_entry(grid, ++row, bsmsg, &bsmsg->recipient_view,
2925 bsmsg->recipients, "Rec_ipients", address_types,
2926 G_N_ELEMENTS(address_types));
2927 g_signal_connect_swapped(gtk_tree_view_get_model
2928 (GTK_TREE_VIEW(bsmsg->recipient_view)),
2929 "row-changed",
2930 G_CALLBACK(sendmsg_window_set_title), bsmsg);
2931 g_signal_connect_swapped(gtk_tree_view_get_model
2932 (GTK_TREE_VIEW(bsmsg->recipient_view)),
2933 "row-deleted",
2934 G_CALLBACK(sendmsg_window_set_title), bsmsg);
2935
2936 /* Subject: */
2937 create_string_entry(grid, _("S_ubject:"), ++row, bsmsg->subject);
2938 g_signal_connect_swapped(G_OBJECT(bsmsg->subject[1]), "changed",
2939 G_CALLBACK(sendmsg_window_set_title), bsmsg);
2940
2941 #if !defined(ENABLE_TOUCH_UI)
2942 /* Reply To: */
2943 /* We already created it, so just increment row: */
2944 g_assert(++row == REPLY_TO_ROW);
2945 #undef REPLY_TO_ROW
2946 #endif
2947
2948 /* fcc: mailbox folder where the message copy will be written to */
2949 bsmsg->fcc[0] = gtk_label_new_with_mnemonic(_("F_cc:"));
2950 gtk_misc_set_alignment(GTK_MISC(bsmsg->fcc[0]), 0.0, 0.5);
2951 gtk_misc_set_padding(GTK_MISC(bsmsg->fcc[0]), GNOME_PAD_SMALL,
2952 GNOME_PAD_SMALL);
2953 ++row;
2954 gtk_grid_attach(GTK_GRID(grid), bsmsg->fcc[0], 0, row, 1, 1);
2955
2956 if (!balsa_app.fcc_mru)
2957 balsa_mblist_mru_add(&balsa_app.fcc_mru, balsa_app.sentbox->url);
2958 balsa_mblist_mru_add(&balsa_app.fcc_mru, "");
2959 if (balsa_app.copy_to_sentbox) {
2960 /* move the NULL option to the bottom */
2961 balsa_app.fcc_mru = g_list_reverse(balsa_app.fcc_mru);
2962 balsa_mblist_mru_add(&balsa_app.fcc_mru, "");
2963 balsa_app.fcc_mru = g_list_reverse(balsa_app.fcc_mru);
2964 }
2965 if (bsmsg->draft_message && bsmsg->draft_message->headers &&
2966 bsmsg->draft_message->headers->fcc_url)
2967 balsa_mblist_mru_add(&balsa_app.fcc_mru,
2968 bsmsg->draft_message->headers->fcc_url);
2969 bsmsg->fcc[1] =
2970 balsa_mblist_mru_option_menu(GTK_WINDOW(bsmsg->window),
2971 &balsa_app.fcc_mru);
2972 gtk_label_set_mnemonic_widget(GTK_LABEL(bsmsg->fcc[0]), bsmsg->fcc[1]);
2973 gtk_grid_attach(GTK_GRID(grid), bsmsg->fcc[1] , 1, row, 1, 1);
2974
2975 /* Attachment list */
2976 bsmsg->attachments[0] = gtk_label_new_with_mnemonic(_("_Attachments:"));
2977 gtk_misc_set_alignment(GTK_MISC(bsmsg->attachments[0]), 0.0, 0.5);
2978 gtk_misc_set_padding(GTK_MISC(bsmsg->attachments[0]), GNOME_PAD_SMALL,
2979 GNOME_PAD_SMALL);
2980 ++row;
2981 gtk_grid_attach(GTK_GRID(grid), bsmsg->attachments[0], 0, row, 1, 1);
2982
2983 sw = gtk_scrolled_window_new(NULL, NULL);
2984 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
2985 GTK_POLICY_AUTOMATIC,
2986 GTK_POLICY_AUTOMATIC);
2987 #if 0
2988 g_signal_connect(sw, "size-request",
2989 G_CALLBACK(sw_scroll_size_request), NULL);
2990 #endif
2991
2992 store = gtk_list_store_new(ATTACH_NUM_COLUMNS,
2993 TYPE_BALSA_ATTACH_INFO,
2994 GDK_TYPE_PIXBUF,
2995 G_TYPE_STRING,
2996 G_TYPE_INT,
2997 G_TYPE_UINT64,
2998 G_TYPE_STRING);
2999
3000 bsmsg->attachments[1] = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
3001 view = GTK_TREE_VIEW(bsmsg->attachments[1]);
3002 gtk_tree_view_set_headers_visible(view, TRUE);
3003 gtk_tree_view_set_rules_hint(view, TRUE);
3004 g_object_unref(store);
3005 bsmsg->attachments[2] = NULL;
3006
3007 /* column for type icon */
3008 renderer = gtk_cell_renderer_pixbuf_new();
3009 g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
3010 gtk_tree_view_insert_column_with_attributes(view,
3011 -1, NULL, renderer,
3012 "pixbuf", ATTACH_ICON_COLUMN,
3013 NULL);
3014
3015 /* column for the mime type */
3016 renderer = gtk_cell_renderer_text_new();
3017 g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
3018 gtk_tree_view_insert_column_with_attributes(view,
3019 -1, _("Type"), renderer,
3020 "text", ATTACH_TYPE_COLUMN,
3021 NULL);
3022
3023 /* column for the attachment mode */
3024 renderer = gtk_cell_renderer_text_new();
3025 g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
3026 column = gtk_tree_view_column_new_with_attributes(_("Mode"), renderer,
3027 "text", ATTACH_MODE_COLUMN,
3028 NULL);
3029 gtk_tree_view_column_set_cell_data_func(column,
3030 renderer, render_attach_mode,
3031 NULL, NULL);
3032 gtk_tree_view_append_column(view, column);
3033
3034 /* column for the attachment size */
3035 renderer = gtk_cell_renderer_text_new();
3036 g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
3037 column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer,
3038 "text", ATTACH_SIZE_COLUMN,
3039 NULL);
3040 gtk_tree_view_column_set_cell_data_func(column,
3041 renderer, render_attach_size,
3042 NULL, NULL);
3043 gtk_tree_view_append_column(view, column);
3044
3045 /* column for the file type/description */
3046 renderer = gtk_cell_renderer_text_new();
3047 g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
3048 gtk_tree_view_insert_column_with_attributes(view,
3049 -1, _("Description"), renderer,
3050 "text", ATTACH_DESC_COLUMN,
3051 NULL);
3052
3053 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view),
3054 GTK_SELECTION_SINGLE);
3055 g_signal_connect(view, "popup-menu",
3056 G_CALLBACK(attachment_popup_cb), NULL);
3057 g_signal_connect(view, "button_press_event",
3058 G_CALLBACK(attachment_button_press_cb), NULL);
3059
3060 g_signal_connect(G_OBJECT(bsmsg->window), "drag_data_received",
3061 G_CALLBACK(attachments_add), bsmsg);
3062 gtk_drag_dest_set(GTK_WIDGET(bsmsg->window), GTK_DEST_DEFAULT_ALL,
3063 drop_types, ELEMENTS(drop_types),
3064 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
3065
3066 gtk_widget_set_size_request(bsmsg->attachments[1], -1, 100);
3067
3068 frame = gtk_frame_new(NULL);
3069 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
3070 gtk_container_add(GTK_CONTAINER(sw), bsmsg->attachments[1]);
3071 gtk_container_add(GTK_CONTAINER(frame), sw);
3072
3073 gtk_widget_set_hexpand(frame, TRUE);
3074 gtk_grid_attach(GTK_GRID(grid), frame, 1, row, 1, 1);
3075
3076 bsmsg->attachments[2] = sw;
3077 bsmsg->attachments[3] = frame;
3078
3079 gtk_widget_show_all(grid);
3080 hide_attachment_widget(bsmsg);
3081 return grid;
3082 }
3083
3084 typedef struct {
3085 gchar * name;
3086 gboolean found;
3087 } has_file_attached_t;
3088
3089 static gboolean
has_file_attached(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)3090 has_file_attached(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
3091 gpointer data)
3092 {
3093 has_file_attached_t *find_file = (has_file_attached_t *)data;
3094 BalsaAttachInfo *info;
3095 const gchar * uri;
3096
3097 gtk_tree_model_get(model, iter, ATTACH_INFO_COLUMN, &info, -1);
3098 if (!info)
3099 return FALSE;
3100 uri = libbalsa_vfs_get_uri(info->file_uri);
3101 if (uri && !strcmp(find_file->name, uri))
3102 find_file->found = TRUE;
3103 g_object_unref(info);
3104
3105 return find_file->found;
3106 }
3107
3108 /* drag_data_quote - text area D&D callback */
3109 static void
drag_data_quote(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time,BalsaSendmsg * bsmsg)3110 drag_data_quote(GtkWidget * widget,
3111 GdkDragContext * context,
3112 gint x,
3113 gint y,
3114 GtkSelectionData * selection_data,
3115 guint info, guint32 time, BalsaSendmsg * bsmsg)
3116 {
3117 GtkTextBuffer *buffer;
3118 BalsaIndex *index;
3119 LibBalsaMailbox *mailbox;
3120 GArray *selected;
3121 guint i;
3122
3123 switch(info) {
3124 case TARGET_MESSAGES:
3125 index =
3126 *(BalsaIndex **) gtk_selection_data_get_data(selection_data);
3127 mailbox = index->mailbox_node->mailbox;
3128 selected = balsa_index_selected_msgnos_new(index);
3129 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
3130
3131 for (i = 0; i < selected->len; i++) {
3132 guint msgno = g_array_index(selected, guint, i);
3133 LibBalsaMessage *message;
3134 GString *body;
3135
3136 message = libbalsa_mailbox_get_message(mailbox, msgno);
3137 if (!message)
3138 continue;
3139
3140 body = quote_message_body(bsmsg, message, QUOTE_ALL);
3141 g_object_unref(message);
3142 gtk_text_buffer_insert_at_cursor(buffer, body->str, body->len);
3143 g_string_free(body, TRUE);
3144 }
3145 balsa_index_selected_msgnos_free(index, selected);
3146 break;
3147 case TARGET_URI_LIST: {
3148 GSList *uri_list =
3149 uri2gslist((gchar *)
3150 gtk_selection_data_get_data(selection_data));
3151 for (; uri_list; uri_list = g_slist_next(uri_list)) {
3152 /* Since current GtkTextView gets this signal twice for
3153 * every action (#150141) we need to check for duplicates,
3154 * which is a good idea anyway. */
3155 has_file_attached_t find_file;
3156
3157 find_file.name = uri_list->data;
3158 find_file.found = FALSE;
3159 gtk_tree_model_foreach(BALSA_MSG_ATTACH_MODEL(bsmsg),
3160 has_file_attached, &find_file);
3161 if (!find_file.found)
3162 add_attachment(bsmsg, uri_list->data, FALSE, NULL);
3163 }
3164 g_slist_foreach(uri_list, (GFunc) g_free, NULL);
3165 g_slist_free(uri_list);
3166 }
3167 break;
3168 case TARGET_EMAIL:
3169 case TARGET_STRING: /* perhaps we should allow dropping in these, too? */
3170 default: return;
3171 }
3172 gtk_drag_finish(context, TRUE, FALSE, time);
3173 }
3174
3175 /* create_text_area
3176 Creates the text entry part of the compose window.
3177 */
3178 #if (HAVE_GTKSOURCEVIEW == 1)
3179
3180 static void
sw_can_undo_cb(GtkSourceBuffer * source_buffer,gboolean can_undo,BalsaSendmsg * bsmsg)3181 sw_can_undo_cb(GtkSourceBuffer * source_buffer, gboolean can_undo,
3182 BalsaSendmsg * bsmsg)
3183 {
3184 sw_set_sensitive(bsmsg, "Undo", can_undo);
3185 }
3186
3187 static void
sw_can_redo_cb(GtkSourceBuffer * source_buffer,gboolean can_redo,BalsaSendmsg * bsmsg)3188 sw_can_redo_cb(GtkSourceBuffer * source_buffer, gboolean can_redo,
3189 BalsaSendmsg * bsmsg)
3190 {
3191 sw_set_sensitive(bsmsg, "Redo", can_redo);
3192 }
3193
3194 #elif (HAVE_GTKSOURCEVIEW >= 2)
3195
3196 static void
sw_can_undo_cb(GtkSourceBuffer * source_buffer,GParamSpec * arg1,BalsaSendmsg * bsmsg)3197 sw_can_undo_cb(GtkSourceBuffer * source_buffer, GParamSpec *arg1,
3198 BalsaSendmsg * bsmsg)
3199 {
3200 gboolean can_undo;
3201
3202 g_object_get(G_OBJECT(source_buffer), "can-undo", &can_undo, NULL);
3203 sw_set_sensitive(bsmsg, "Undo", can_undo);
3204 }
3205
3206 static void
sw_can_redo_cb(GtkSourceBuffer * source_buffer,GParamSpec * arg1,BalsaSendmsg * bsmsg)3207 sw_can_redo_cb(GtkSourceBuffer * source_buffer, GParamSpec *arg1,
3208 BalsaSendmsg * bsmsg)
3209 {
3210 gboolean can_redo;
3211
3212 g_object_get(G_OBJECT(source_buffer), "can-redo", &can_redo, NULL);
3213 sw_set_sensitive(bsmsg, "Redo", can_redo);
3214 }
3215
3216 #endif /* HAVE_GTKSOURCEVIEW */
3217
3218 static GtkWidget *
create_text_area(BalsaSendmsg * bsmsg)3219 create_text_area(BalsaSendmsg * bsmsg)
3220 {
3221 GtkTextView *text_view;
3222 PangoFontDescription *desc;
3223 GtkTextBuffer *buffer;
3224 GtkWidget *scroll;
3225
3226 #if HAVE_GTKSOURCEVIEW
3227 bsmsg->text = libbalsa_source_view_new(TRUE);
3228 #else /* HAVE_GTKSOURCEVIEW */
3229 bsmsg->text = gtk_text_view_new();
3230 #endif /* HAVE_GTKSOURCEVIEW */
3231 text_view = GTK_TEXT_VIEW(bsmsg->text);
3232 gtk_text_view_set_left_margin(text_view, 2);
3233 gtk_text_view_set_right_margin(text_view, 2);
3234
3235 /* set the message font */
3236 desc = pango_font_description_from_string(balsa_app.message_font);
3237 gtk_widget_override_font(bsmsg->text, desc);
3238 pango_font_description_free(desc);
3239
3240 buffer = gtk_text_view_get_buffer(text_view);
3241 #if (HAVE_GTKSOURCEVIEW == 1)
3242 g_signal_connect(buffer, "can-undo",
3243 G_CALLBACK(sw_can_undo_cb), bsmsg);
3244 g_signal_connect(buffer, "can-redo",
3245 G_CALLBACK(sw_can_redo_cb), bsmsg);
3246 #elif (HAVE_GTKSOURCEVIEW)
3247 g_signal_connect(G_OBJECT(buffer), "notify::can-undo",
3248 G_CALLBACK(sw_can_undo_cb), bsmsg);
3249 g_signal_connect(G_OBJECT(buffer), "notify::can-redo",
3250 G_CALLBACK(sw_can_redo_cb), bsmsg);
3251 #else /* HAVE_GTKSOURCEVIEW */
3252 bsmsg->buffer2 =
3253 gtk_text_buffer_new(gtk_text_buffer_get_tag_table(buffer));
3254 #endif /* HAVE_GTKSOURCEVIEW */
3255 gtk_text_buffer_create_tag(buffer, "url", NULL, NULL);
3256 gtk_text_view_set_editable(text_view, TRUE);
3257 gtk_text_view_set_wrap_mode(text_view, GTK_WRAP_WORD_CHAR);
3258
3259 scroll = gtk_scrolled_window_new(NULL, NULL);
3260 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
3261 GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
3262 gtk_container_add(GTK_CONTAINER(scroll), bsmsg->text);
3263 g_signal_connect(G_OBJECT(bsmsg->text), "drag_data_received",
3264 G_CALLBACK(drag_data_quote), bsmsg);
3265 /* GTK_DEST_DEFAULT_ALL in drag_set would trigger bug 150141 */
3266 gtk_drag_dest_set(GTK_WIDGET(bsmsg->text), 0,
3267 drop_types, ELEMENTS(drop_types),
3268 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
3269
3270 gtk_widget_show_all(scroll);
3271
3272 return scroll;
3273 }
3274
3275 /* Check whether the string can be converted. */
3276 static gboolean
sw_can_convert(const gchar * string,gssize len,const gchar * to_codeset,const gchar * from_codeset,gchar ** result)3277 sw_can_convert(const gchar * string, gssize len,
3278 const gchar * to_codeset, const gchar * from_codeset,
3279 gchar ** result)
3280 {
3281 gsize bytes_read, bytes_written;
3282 GError *err = NULL;
3283 gchar *s;
3284
3285 if (!(to_codeset && from_codeset))
3286 return FALSE;
3287
3288 s = g_convert(string, len, to_codeset, from_codeset,
3289 &bytes_read, &bytes_written, &err);
3290 if (err) {
3291 g_error_free(err);
3292 g_free(s);
3293 s = NULL;
3294 }
3295
3296 if (result)
3297 *result = s;
3298 else
3299 g_free(s);
3300
3301 return !err;
3302 }
3303
3304 /* continue_body --------------------------------------------------------
3305 a short-circuit procedure for the 'Continue action'
3306 basically copies the first text/plain part over to the entry field.
3307 Attachments (if any) are saved temporarily in subfolders to preserve
3308 their original names and then attached again.
3309 NOTE that rbdy == NULL if message has no text parts.
3310 */
3311 static void
continue_body(BalsaSendmsg * bsmsg,LibBalsaMessage * message)3312 continue_body(BalsaSendmsg * bsmsg, LibBalsaMessage * message)
3313 {
3314 LibBalsaMessageBody *body;
3315
3316 body = message->body_list;
3317 if (body) {
3318 if (libbalsa_message_body_type(body) == LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART)
3319 body = body->parts;
3320 /* if the first part is of type text/plain with a NULL filename, it
3321 was the message... */
3322 if (body && !body->filename) {
3323 GString *rbdy;
3324 gchar *body_type = libbalsa_message_body_get_mime_type(body);
3325 gint llen = -1;
3326 GtkTextBuffer *buffer =
3327 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
3328
3329 if (bsmsg->flow && libbalsa_message_body_is_flowed(body))
3330 llen = balsa_app.wraplength;
3331 if (!strcmp(body_type, "text/plain") &&
3332 (rbdy = process_mime_part(message, body, NULL, llen, FALSE,
3333 bsmsg->flow))) {
3334 gtk_text_buffer_insert_at_cursor(buffer, rbdy->str, rbdy->len);
3335 g_string_free(rbdy, TRUE);
3336 }
3337 g_free(body_type);
3338 body = body->next;
3339 }
3340 while (body) {
3341 gchar *name, *body_type, *tmp_file_name;
3342 GError *err = NULL;
3343 gboolean res = FALSE;
3344
3345 if (body->filename) {
3346 libbalsa_mktempdir(&tmp_file_name);
3347 name = g_strdup_printf("%s/%s", tmp_file_name, body->filename);
3348 g_free(tmp_file_name);
3349 res = libbalsa_message_body_save(body, name,
3350 LIBBALSA_MESSAGE_BODY_SAFE,
3351 FALSE, &err);
3352 } else {
3353 int fd;
3354
3355 if ((fd = g_file_open_tmp("balsa-continue-XXXXXX", &name, NULL)) > 0) {
3356 GMimeStream * tmp_stream;
3357
3358 if ((tmp_stream = g_mime_stream_fs_new(fd)) != NULL)
3359 res = libbalsa_message_body_save_stream(body, tmp_stream, FALSE, &err);
3360 else
3361 close(fd);
3362 }
3363 }
3364 if(!res) {
3365 balsa_information_parented(GTK_WINDOW(bsmsg->window),
3366 LIBBALSA_INFORMATION_ERROR,
3367 _("Could not save attachment: %s"),
3368 err ? err->message : "Unknown error");
3369 g_clear_error(&err);
3370 /* FIXME: do not try any further? */
3371 }
3372 body_type = libbalsa_message_body_get_mime_type(body);
3373 tmp_file_name = g_filename_to_uri(name, NULL, NULL);
3374 g_free(name);
3375 add_attachment(bsmsg, tmp_file_name, TRUE, body_type);
3376 g_free(body_type);
3377 g_free(tmp_file_name);
3378 body = body->next;
3379 }
3380 }
3381 }
3382
3383 static gchar*
message_part_get_subject(LibBalsaMessageBody * part)3384 message_part_get_subject(LibBalsaMessageBody *part)
3385 {
3386 gchar *subject = NULL;
3387 if(part->embhdrs && part->embhdrs->subject)
3388 subject = g_strdup(part->embhdrs->subject);
3389 else if(part->message && part->message->subj)
3390 subject = g_strdup(part->message->subj);
3391 else subject = g_strdup(_("No subject"));
3392 libbalsa_utf8_sanitize(&subject, balsa_app.convert_unknown_8bit,
3393 NULL);
3394 return subject;
3395 }
3396
3397 /* --- stuff for collecting parts for a reply --- */
3398
3399 enum {
3400 QUOTE_INCLUDE,
3401 QUOTE_DESCRIPTION,
3402 QUOTE_BODY,
3403 QOUTE_NUM_ELEMS
3404 };
3405
3406 static void
tree_add_quote_body(LibBalsaMessageBody * body,GtkTreeStore * store,GtkTreeIter * parent)3407 tree_add_quote_body(LibBalsaMessageBody * body, GtkTreeStore * store, GtkTreeIter * parent)
3408 {
3409 GtkTreeIter iter;
3410 gchar * mime_type = libbalsa_message_body_get_mime_type(body);
3411 const gchar * disp_type;
3412 static gboolean preselect;
3413 gchar * description;
3414
3415 gtk_tree_store_append(store, &iter, parent);
3416 if (body->mime_part)
3417 disp_type = g_mime_object_get_disposition(body->mime_part);
3418 else
3419 disp_type = NULL;
3420 /* cppcheck-suppress nullPointer */
3421 preselect = !disp_type || *disp_type == '\0' ||
3422 !g_ascii_strcasecmp(disp_type, "inline");
3423 if (body->filename && *body->filename) {
3424 if (preselect)
3425 description = g_strdup_printf(_("inlined file \"%s\" (%s)"),
3426 body->filename, mime_type);
3427 else
3428 description = g_strdup_printf(_("attached file \"%s\" (%s)"),
3429 body->filename, mime_type);
3430 } else {
3431 if (preselect)
3432 description = g_strdup_printf(_("inlined %s part"), mime_type);
3433 else
3434 description = g_strdup_printf(_("attached %s part"), mime_type);
3435 }
3436 g_free(mime_type);
3437 gtk_tree_store_set(store, &iter,
3438 QUOTE_INCLUDE, preselect,
3439 QUOTE_DESCRIPTION, description,
3440 QUOTE_BODY, body,
3441 -1);
3442 g_free(description);
3443 }
3444
3445 static gint
scan_bodies(GtkTreeStore * bodies,GtkTreeIter * parent,LibBalsaMessageBody * body,gboolean ignore_html,gboolean container_mp_alt)3446 scan_bodies(GtkTreeStore * bodies, GtkTreeIter * parent, LibBalsaMessageBody * body,
3447 gboolean ignore_html, gboolean container_mp_alt)
3448 {
3449 gchar * mime_type;
3450 gint count = 0;
3451
3452 while (body) {
3453 switch (libbalsa_message_body_type(body)) {
3454 case LIBBALSA_MESSAGE_BODY_TYPE_TEXT:
3455 {
3456 gchar *mime_type;
3457 LibBalsaHTMLType html_type;
3458
3459 mime_type = libbalsa_message_body_get_mime_type(body);
3460 html_type = libbalsa_html_type(mime_type);
3461 g_free(mime_type);
3462
3463 /* On a multipart/alternative, ignore_html defines if html or
3464 * non-html parts will be added. Eject from the container when
3465 * the first part has been found.
3466 * Otherwise, select all text parts. */
3467 if (container_mp_alt) {
3468 if ((ignore_html && html_type == LIBBALSA_HTML_TYPE_NONE) ||
3469 (!ignore_html && html_type != LIBBALSA_HTML_TYPE_NONE)) {
3470 tree_add_quote_body(body, bodies, parent);
3471 return count + 1;
3472 }
3473 } else {
3474 tree_add_quote_body(body, bodies, parent);
3475 count++;
3476 }
3477 break;
3478 }
3479
3480 case LIBBALSA_MESSAGE_BODY_TYPE_MULTIPART:
3481 mime_type = libbalsa_message_body_get_mime_type(body);
3482 count += scan_bodies(bodies, parent, body->parts, ignore_html,
3483 !g_ascii_strcasecmp(mime_type, "multipart/alternative"));
3484 g_free(mime_type);
3485 break;
3486
3487 case LIBBALSA_MESSAGE_BODY_TYPE_MESSAGE:
3488 {
3489 GtkTreeIter iter;
3490 gchar * description = NULL;
3491
3492 mime_type = libbalsa_message_body_get_mime_type(body);
3493 if (g_ascii_strcasecmp(mime_type, "message/rfc822") == 0 &&
3494 body->embhdrs) {
3495 gchar *from = balsa_message_sender_to_gchar(body->embhdrs->from, 0);
3496 gchar *subj = g_strdup(body->embhdrs->subject);
3497
3498
3499 libbalsa_utf8_sanitize(&from, balsa_app.convert_unknown_8bit, NULL);
3500 libbalsa_utf8_sanitize(&subj, balsa_app.convert_unknown_8bit, NULL);
3501 description =
3502 g_strdup_printf(_("message from %s, subject \"%s\""),
3503 from, subj);
3504 g_free(from);
3505 g_free(subj);
3506 } else
3507 description = g_strdup(mime_type);
3508
3509 gtk_tree_store_append(bodies, &iter, parent);
3510 gtk_tree_store_set(bodies, &iter,
3511 QUOTE_INCLUDE, FALSE,
3512 QUOTE_DESCRIPTION, description,
3513 QUOTE_BODY, NULL,
3514 -1);
3515 g_free(mime_type);
3516 g_free(description);
3517 count += scan_bodies(bodies, &iter, body->parts, ignore_html, 0);
3518 }
3519
3520 default:
3521 break;
3522 }
3523
3524 body = body->next;
3525 }
3526
3527 return count;
3528 }
3529
3530 static void
set_all_cells(GtkTreeModel * model,GtkTreeIter * iter,const gboolean value)3531 set_all_cells(GtkTreeModel * model, GtkTreeIter * iter, const gboolean value)
3532 {
3533 do {
3534 GtkTreeIter children;
3535
3536 if (gtk_tree_model_iter_children(model, &children, iter))
3537 set_all_cells(model, &children, value);
3538 gtk_tree_store_set(GTK_TREE_STORE(model), iter, QUOTE_INCLUDE, value, -1);
3539 } while (gtk_tree_model_iter_next(model, iter));
3540 }
3541
3542 static gboolean
calculate_expander_toggles(GtkTreeModel * model,GtkTreeIter * iter)3543 calculate_expander_toggles(GtkTreeModel * model, GtkTreeIter * iter)
3544 {
3545 gint count, on;
3546
3547 count = on = 0;
3548 do {
3549 GtkTreeIter children;
3550 gboolean value;
3551
3552 if (gtk_tree_model_iter_children(model, &children, iter)) {
3553 value = calculate_expander_toggles(model, &children);
3554 gtk_tree_store_set(GTK_TREE_STORE(model), iter, QUOTE_INCLUDE, value, -1);
3555 } else
3556 gtk_tree_model_get(model, iter, QUOTE_INCLUDE, &value, -1);
3557 if (value)
3558 on++;
3559 count++;
3560 } while (gtk_tree_model_iter_next(model, iter));
3561
3562 return count == on;
3563 }
3564
3565 static void
cell_toggled_cb(GtkCellRendererToggle * cell,gchar * path_str,GtkTreeView * treeview)3566 cell_toggled_cb(GtkCellRendererToggle *cell, gchar *path_str, GtkTreeView *treeview)
3567 {
3568 GtkTreeModel *model = NULL;
3569 GtkTreePath *path;
3570 GtkTreeIter iter;
3571 GtkTreeIter children;
3572 gboolean active;
3573
3574 g_return_if_fail (GTK_IS_TREE_VIEW (treeview));
3575 if (!(model = gtk_tree_view_get_model(treeview)))
3576 return;
3577
3578 path = gtk_tree_path_new_from_string(path_str);
3579 if (!gtk_tree_model_get_iter(model, &iter, path))
3580 return;
3581 gtk_tree_path_free(path);
3582
3583 gtk_tree_model_get(model, &iter,
3584 QUOTE_INCLUDE, &active,
3585 -1);
3586 gtk_tree_store_set(GTK_TREE_STORE (model), &iter,
3587 QUOTE_INCLUDE, !active,
3588 -1);
3589 if (gtk_tree_model_iter_children(model, &children, &iter))
3590 set_all_cells(model, &children, !active);
3591 gtk_tree_model_get_iter_first(model, &children);
3592 calculate_expander_toggles(model, &children);
3593 }
3594
3595 static void
append_parts(GString * q_body,LibBalsaMessage * message,GtkTreeModel * model,GtkTreeIter * iter,const gchar * from_msg,gchar * reply_prefix_str,gint llen,gboolean flow)3596 append_parts(GString * q_body, LibBalsaMessage *message, GtkTreeModel * model,
3597 GtkTreeIter * iter, const gchar * from_msg, gchar * reply_prefix_str,
3598 gint llen, gboolean flow)
3599 {
3600 gboolean used_from_msg = FALSE;
3601
3602 do {
3603 GtkTreeIter children;
3604
3605 if (gtk_tree_model_iter_children(model, &children, iter)) {
3606 gchar * description;
3607
3608 gtk_tree_model_get(model, iter, QUOTE_DESCRIPTION, &description, -1);
3609 append_parts(q_body, message, model, &children, description,
3610 reply_prefix_str, llen, flow);
3611 g_free(description);
3612 } else {
3613 gboolean do_include;
3614
3615 gtk_tree_model_get(model, iter, QUOTE_INCLUDE, &do_include, -1);
3616 if (do_include) {
3617 LibBalsaMessageBody *this_body;
3618
3619 gtk_tree_model_get(model, iter, QUOTE_BODY, &this_body, -1);
3620 if (this_body) {
3621 GString * this_part;
3622 this_part= process_mime_part(message, this_body,
3623 reply_prefix_str, llen,
3624 FALSE, flow);
3625
3626 if (q_body->len > 0 && q_body->str[q_body->len - 1] != '\n')
3627 g_string_append_c(q_body, '\n');
3628 if (!used_from_msg && from_msg) {
3629 g_string_append_printf(q_body, "\n======%s %s======\n", _("quoted"), from_msg);
3630 used_from_msg = TRUE;
3631 } else if (q_body->len > 0) {
3632 if (this_body->filename)
3633 g_string_append_printf(q_body, "\n------%s \"%s\"------\n",
3634 _("quoted attachment"), this_body->filename);
3635 else
3636 g_string_append_printf(q_body, "\n------%s------\n",
3637 _("quoted attachment"));
3638 }
3639 g_string_append(q_body, this_part->str);
3640 g_string_free(this_part, TRUE);
3641 }
3642 }
3643 }
3644 } while (gtk_tree_model_iter_next(model, iter));
3645 }
3646
3647 static gboolean
quote_parts_select_dlg(GtkTreeStore * tree_store,GtkWindow * parent)3648 quote_parts_select_dlg(GtkTreeStore *tree_store, GtkWindow * parent)
3649 {
3650 GtkWidget *dialog;
3651 GtkWidget *label;
3652 GtkWidget *image;
3653 GtkWidget *hbox;
3654 GtkWidget *vbox;
3655 GtkWidget *scroll;
3656 GtkWidget *tree_view;
3657 GtkTreeViewColumn *column;
3658 GtkCellRenderer *renderer;
3659 GtkTreeIter iter;
3660 gboolean result;
3661 GtkBox *content_box;
3662
3663 dialog = gtk_dialog_new_with_buttons(_("Select parts for quotation"),
3664 parent,
3665 GTK_DIALOG_DESTROY_WITH_PARENT,
3666 GTK_STOCK_OK, GTK_RESPONSE_OK,
3667 NULL);
3668 #if HAVE_MACOSX_DESKTOP
3669 libbalsa_macosx_menu_for_parent(dialog, parent);
3670 #endif
3671
3672 label = gtk_label_new(_("Select the parts of the message which shall be quoted in the reply"));
3673 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
3674 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
3675 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
3676
3677 image = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
3678 GTK_ICON_SIZE_DIALOG);
3679 gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.0);
3680
3681 /* stolen form gtk/gtkmessagedialog.c */
3682 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
3683 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
3684
3685 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
3686 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
3687 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
3688 content_box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
3689 gtk_box_pack_start(content_box, hbox, TRUE, TRUE, 0);
3690
3691 gtk_container_set_border_width(GTK_CONTAINER(dialog), 5);
3692 gtk_container_set_border_width(GTK_CONTAINER(hbox), 5);
3693 gtk_box_set_spacing(content_box, 14);
3694
3695 /* scrolled window for the tree view */
3696 scroll = gtk_scrolled_window_new(NULL, NULL);
3697 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
3698 GTK_POLICY_AUTOMATIC,
3699 GTK_POLICY_AUTOMATIC);
3700 gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
3701
3702 /* add the tree view */
3703 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_store));
3704 gtk_widget_set_size_request(tree_view, -1, 100);
3705 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), FALSE);
3706 renderer = gtk_cell_renderer_toggle_new();
3707 g_signal_connect(renderer, "toggled", G_CALLBACK(cell_toggled_cb),
3708 tree_view);
3709 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
3710 "active", QUOTE_INCLUDE,
3711 NULL);
3712 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
3713 gtk_tree_view_set_expander_column(GTK_TREE_VIEW(tree_view), column);
3714 column = gtk_tree_view_column_new_with_attributes(NULL, gtk_cell_renderer_text_new(),
3715 "text", QUOTE_DESCRIPTION,
3716 NULL);
3717 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
3718 gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view));
3719 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(tree_store), &iter);
3720 calculate_expander_toggles(GTK_TREE_MODEL(tree_store), &iter);
3721
3722 /* add, show & run */
3723 gtk_container_add(GTK_CONTAINER(scroll), tree_view);
3724 gtk_widget_show_all(hbox);
3725 result = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK;
3726 gtk_widget_destroy(dialog);
3727 return result;
3728 }
3729
3730 static gboolean
tree_find_single_part(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)3731 tree_find_single_part(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
3732 gpointer data)
3733 {
3734 LibBalsaMessageBody ** this_body = (LibBalsaMessageBody **) data;
3735
3736 gtk_tree_model_get(model, iter, QUOTE_BODY, this_body, -1);
3737 if (*this_body)
3738 return TRUE;
3739 else
3740 return FALSE;
3741 }
3742
3743 static GString *
collect_for_quote(LibBalsaMessageBody * root,gchar * reply_prefix_str,gint llen,gboolean ignore_html,gboolean flow)3744 collect_for_quote(LibBalsaMessageBody *root, gchar * reply_prefix_str,
3745 gint llen, gboolean ignore_html, gboolean flow)
3746 {
3747 GtkTreeStore * tree_store;
3748 gint text_bodies;
3749 LibBalsaMessage *message = root->message;
3750 GString *q_body = NULL;
3751
3752 libbalsa_message_body_ref(message, FALSE, FALSE);
3753
3754 /* scan the message and collect text parts which might be included
3755 * in the reply, and if there is only one return this part */
3756 tree_store = gtk_tree_store_new(QOUTE_NUM_ELEMS,
3757 G_TYPE_BOOLEAN, G_TYPE_STRING,
3758 G_TYPE_POINTER);
3759 text_bodies = scan_bodies(tree_store, NULL, root, ignore_html, FALSE);
3760 if (text_bodies == 1) {
3761 /* note: the only text body may be buried in an attached message, so
3762 * we have to search the tree store... */
3763 LibBalsaMessageBody *this_body;
3764
3765 gtk_tree_model_foreach(GTK_TREE_MODEL(tree_store), tree_find_single_part,
3766 &this_body);
3767 if (this_body)
3768 q_body = process_mime_part(message, this_body, reply_prefix_str,
3769 llen, FALSE, flow);
3770 } else if (text_bodies > 1) {
3771 if (quote_parts_select_dlg(tree_store, NULL)) {
3772 GtkTreeIter iter;
3773
3774 q_body = g_string_new("");
3775 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(tree_store), &iter);
3776 append_parts(q_body, message, GTK_TREE_MODEL(tree_store), &iter, NULL,
3777 reply_prefix_str, llen, flow);
3778 }
3779 }
3780
3781 /* clean up */
3782 g_object_unref(G_OBJECT(tree_store));
3783 libbalsa_message_body_unref(message);
3784 return q_body;
3785 }
3786
3787
3788 /* quote_body -----------------------------------------------------------
3789 quotes properly the body of the message.
3790 Use GString to optimize memory usage.
3791 Specifying type explicitly allows for later message quoting when
3792 eg. a new message is composed.
3793 */
3794 static GString *
quote_body(BalsaSendmsg * bsmsg,LibBalsaMessageHeaders * headers,const gchar * message_id,GList * references,LibBalsaMessageBody * root,QuoteType qtype)3795 quote_body(BalsaSendmsg * bsmsg, LibBalsaMessageHeaders *headers,
3796 const gchar *message_id, GList *references,
3797 LibBalsaMessageBody *root, QuoteType qtype)
3798 {
3799 GString *body;
3800 gchar *str, *date = NULL;
3801 gchar *personStr;
3802 const gchar *orig_address;
3803
3804 g_return_val_if_fail(headers, NULL);
3805
3806 if (headers->from &&
3807 (orig_address =
3808 libbalsa_address_get_name_from_list(headers->from))) {
3809 personStr = g_strdup(orig_address);
3810 libbalsa_utf8_sanitize(&personStr,
3811 balsa_app.convert_unknown_8bit,
3812 NULL);
3813 } else
3814 personStr = g_strdup(_("you"));
3815
3816 if (headers->date)
3817 date = libbalsa_message_headers_date_to_utf8(headers,
3818 balsa_app.date_string);
3819
3820 if (qtype == QUOTE_HEADERS) {
3821 gchar *subject;
3822
3823 str = g_strdup_printf(_("------forwarded message from %s------\n"),
3824 personStr);
3825 body = g_string_new(str);
3826 g_free(str);
3827
3828 if (date)
3829 g_string_append_printf(body, "%s %s\n", _("Date:"), date);
3830
3831 subject = message_part_get_subject(root);
3832 if (subject)
3833 g_string_append_printf(body, "%s %s\n", _("Subject:"), subject);
3834 g_free(subject);
3835
3836 if (headers->from) {
3837 gchar *from =
3838 internet_address_list_to_string(headers->from,
3839 FALSE);
3840 g_string_append_printf(body, "%s %s\n", _("From:"), from);
3841 g_free(from);
3842 }
3843
3844 if (internet_address_list_length(headers->to_list) > 0) {
3845 gchar *to_list =
3846 internet_address_list_to_string(headers->to_list,
3847 FALSE);
3848 g_string_append_printf(body, "%s %s\n", _("To:"), to_list);
3849 g_free(to_list);
3850 }
3851
3852 if (internet_address_list_length(headers->cc_list) > 0) {
3853 gchar *cc_list =
3854 internet_address_list_to_string(headers->cc_list,
3855 FALSE);
3856 g_string_append_printf(body, "%s %s\n", _("Cc:"), cc_list);
3857 g_free(cc_list);
3858 }
3859
3860 g_string_append_printf(body, _("Message-ID: %s\n"),
3861 message_id);
3862
3863 if (references) {
3864 GList *ref_list;
3865
3866 g_string_append(body, _("References:"));
3867
3868 for (ref_list = references; ref_list;
3869 ref_list = g_list_next(ref_list))
3870 g_string_append_printf(body, " <%s>",
3871 (gchar *) ref_list->data);
3872
3873 g_string_append_c(body, '\n');
3874 }
3875 } else {
3876 if (date)
3877 str = g_strdup_printf(_("On %s, %s wrote:\n"), date, personStr);
3878 else
3879 str = g_strdup_printf(_("%s wrote:\n"), personStr);
3880
3881 /* scan the message and collect text parts which might be included
3882 * in the reply */
3883 body = collect_for_quote(root,
3884 qtype == QUOTE_ALL ? balsa_app.quote_str : NULL,
3885 bsmsg->flow ? -1 : balsa_app.wraplength,
3886 balsa_app.reply_strip_html, bsmsg->flow);
3887 if (body) {
3888 gchar *buf;
3889
3890 buf = g_string_free(body, FALSE);
3891 libbalsa_utf8_sanitize(&buf, balsa_app.convert_unknown_8bit,
3892 NULL);
3893 body = g_string_new(buf);
3894 g_free(buf);
3895 g_string_prepend(body, str);
3896 } else
3897 body = g_string_new(str);
3898 g_free(str);
3899 }
3900
3901 g_free(date);
3902 g_free(personStr);
3903
3904 return body;
3905 }
3906
3907 /* fill_body -------------------------------------------------------------
3908 fills the body of the message to be composed based on the given message.
3909 First quotes the original one, if autoquote is set,
3910 and then adds the signature.
3911 Optionally prepends the signature to quoted text.
3912 */
3913 static void
fill_body_from_part(BalsaSendmsg * bsmsg,LibBalsaMessageHeaders * headers,const gchar * message_id,GList * references,LibBalsaMessageBody * root,QuoteType qtype)3914 fill_body_from_part(BalsaSendmsg * bsmsg, LibBalsaMessageHeaders *headers,
3915 const gchar *message_id, GList *references,
3916 LibBalsaMessageBody *root, QuoteType qtype)
3917 {
3918 GString *body;
3919 GtkTextBuffer *buffer =
3920 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
3921 GtkTextIter start;
3922
3923 g_assert(headers);
3924
3925 body = quote_body(bsmsg, headers, message_id, references,
3926 root, qtype);
3927
3928 g_return_if_fail(body != NULL);
3929
3930 if(body->len && body->str[body->len] != '\n')
3931 g_string_append_c(body, '\n');
3932 gtk_text_buffer_insert_at_cursor(buffer, body->str, body->len);
3933
3934 if(qtype == QUOTE_HEADERS)
3935 gtk_text_buffer_get_end_iter(buffer, &start);
3936 else
3937 gtk_text_buffer_get_start_iter(buffer, &start);
3938
3939 gtk_text_buffer_place_cursor(buffer, &start);
3940 g_string_free(body, TRUE);
3941 }
3942
3943 static GString*
quote_message_body(BalsaSendmsg * bsmsg,LibBalsaMessage * message,QuoteType qtype)3944 quote_message_body(BalsaSendmsg * bsmsg,
3945 LibBalsaMessage * message,
3946 QuoteType qtype)
3947 {
3948 GString *res;
3949 if(libbalsa_message_body_ref(message, FALSE, FALSE)) {
3950 res = quote_body(bsmsg, message->headers, message->message_id,
3951 message->references, message->body_list, qtype);
3952 libbalsa_message_body_unref(message);
3953 } else res = g_string_new("");
3954 return res;
3955 }
3956
3957 static void
fill_body_from_message(BalsaSendmsg * bsmsg,LibBalsaMessage * message,QuoteType qtype)3958 fill_body_from_message(BalsaSendmsg *bsmsg, LibBalsaMessage *message,
3959 QuoteType qtype)
3960 {
3961 fill_body_from_part(bsmsg, message->headers, message->message_id,
3962 message->references, message->body_list, qtype);
3963 }
3964
3965
3966 static void
insert_signature_cb(GtkAction * action,BalsaSendmsg * bsmsg)3967 insert_signature_cb(GtkAction * action, BalsaSendmsg *bsmsg)
3968 {
3969 gchar *signature;
3970
3971 if(!bsmsg->ident->signature_path || !bsmsg->ident->signature_path[0])
3972 return;
3973 signature = libbalsa_identity_get_signature(bsmsg->ident,
3974 GTK_WINDOW(bsmsg->window));
3975 if (signature != NULL) {
3976 GtkTextBuffer *buffer =
3977 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
3978 #if !HAVE_GTKSOURCEVIEW
3979 sw_buffer_save(bsmsg);
3980 #endif /* HAVE_GTKSOURCEVIEW */
3981 sw_buffer_signals_block(bsmsg, buffer);
3982 gtk_text_buffer_insert_at_cursor(buffer, signature, -1);
3983 sw_buffer_signals_unblock(bsmsg, buffer);
3984
3985 g_free(signature);
3986 } else
3987 balsa_information_parented(GTK_WINDOW(bsmsg->window),
3988 LIBBALSA_INFORMATION_ERROR,
3989 _("No signature found!"));
3990 }
3991
3992
3993 static void
quote_messages_cb(GtkAction * action,BalsaSendmsg * bsmsg)3994 quote_messages_cb(GtkAction * action, BalsaSendmsg *bsmsg)
3995 {
3996 insert_selected_messages(bsmsg, QUOTE_ALL);
3997 }
3998
3999
4000 /** Generates a new subject for forwarded messages based on a message
4001 being responded to and identity.
4002 */
4003 static char*
generate_forwarded_subject(const char * orig_subject,LibBalsaMessageHeaders * headers,LibBalsaIdentity * ident)4004 generate_forwarded_subject(const char *orig_subject,
4005 LibBalsaMessageHeaders *headers,
4006 LibBalsaIdentity *ident)
4007 {
4008 char *newsubject;
4009
4010 if (!orig_subject) {
4011 if (headers && headers->from)
4012 newsubject = g_strdup_printf("%s from %s",
4013 ident->forward_string,
4014 libbalsa_address_get_mailbox_from_list
4015 (headers->from));
4016 else
4017 newsubject = g_strdup(ident->forward_string);
4018 } else {
4019 const char *tmp = orig_subject;
4020 if (g_ascii_strncasecmp(tmp, "fwd:", 4) == 0) {
4021 tmp += 4;
4022 } else if (g_ascii_strncasecmp(tmp, _("Fwd:"),
4023 strlen(_("Fwd:"))) == 0) {
4024 tmp += strlen(_("Fwd:"));
4025 } else {
4026 size_t i = strlen(ident->forward_string);
4027 if (g_ascii_strncasecmp(tmp, ident->forward_string, i) == 0) {
4028 tmp += i;
4029 }
4030 }
4031 while( *tmp && isspace((int)*tmp) ) tmp++;
4032 if (headers && headers->from)
4033 newsubject =
4034 g_strdup_printf("%s %s [%s]",
4035 ident->forward_string,
4036 tmp,
4037 libbalsa_address_get_mailbox_from_list
4038 (headers->from));
4039 else {
4040 newsubject =
4041 g_strdup_printf("%s %s",
4042 ident->forward_string,
4043 tmp);
4044 g_strchomp(newsubject);
4045 }
4046 }
4047 return newsubject;
4048 }
4049 /* bsmsg_set_subject_from_body:
4050 set subject entry based on given replied/forwarded/continued message
4051 and the compose type.
4052 */
4053 static void
bsmsg_set_subject_from_body(BalsaSendmsg * bsmsg,LibBalsaMessageBody * part,LibBalsaIdentity * ident)4054 bsmsg_set_subject_from_body(BalsaSendmsg * bsmsg,
4055 LibBalsaMessageBody * part,
4056 LibBalsaIdentity * ident)
4057 {
4058 gchar *subject;
4059
4060 if (!part)
4061 return;
4062 subject = message_part_get_subject(part);
4063
4064 if (!bsmsg->is_continue) {
4065 gchar *newsubject = NULL;
4066 const gchar *tmp;
4067 LibBalsaMessageHeaders *headers;
4068
4069 switch (bsmsg->type) {
4070 case SEND_REPLY:
4071 case SEND_REPLY_ALL:
4072 case SEND_REPLY_GROUP:
4073 if (!subject) {
4074 subject = g_strdup(ident->reply_string);
4075 break;
4076 }
4077
4078 tmp = subject;
4079 if (g_ascii_strncasecmp(tmp, "re:", 3) == 0 ||
4080 g_ascii_strncasecmp(tmp, "aw:", 3) == 0)
4081 tmp += 3;
4082 else if (g_ascii_strncasecmp(tmp, _("Re:"), strlen(_("Re:")))
4083 == 0)
4084 tmp += strlen(_("Re:"));
4085 else {
4086 gint len = strlen(ident->reply_string);
4087 if (g_ascii_strncasecmp(tmp, ident->reply_string, len) == 0)
4088 tmp += len;
4089 }
4090 while (*tmp && isspace((int) *tmp))
4091 tmp++;
4092 newsubject = g_strdup_printf("%s %s", ident->reply_string, tmp);
4093 g_strchomp(newsubject);
4094 g_strdelimit(newsubject, "\r\n", ' ');
4095 break;
4096
4097 case SEND_FORWARD_ATTACH:
4098 case SEND_FORWARD_INLINE:
4099 headers =
4100 part->embhdrs ? part->embhdrs : part->message->headers;
4101 newsubject =
4102 generate_forwarded_subject(subject, headers, ident);
4103 break;
4104 default:
4105 break;
4106 }
4107
4108 if (newsubject) {
4109 g_free(subject);
4110 subject = newsubject;
4111 }
4112 }
4113
4114 gtk_entry_set_text(GTK_ENTRY(bsmsg->subject[1]), subject);
4115 g_free(subject);
4116 }
4117
4118 static gboolean
sw_save_draft(BalsaSendmsg * bsmsg)4119 sw_save_draft(BalsaSendmsg * bsmsg)
4120 {
4121 GError *err = NULL;
4122
4123 if (!message_postpone(bsmsg)) {
4124 balsa_information_parented(GTK_WINDOW(bsmsg->window),
4125 LIBBALSA_INFORMATION_MESSAGE,
4126 _("Could not save message."));
4127 return FALSE;
4128 }
4129
4130 if(!libbalsa_mailbox_open(balsa_app.draftbox, &err)) {
4131 balsa_information_parented(GTK_WINDOW(bsmsg->window),
4132 LIBBALSA_INFORMATION_WARNING,
4133 _("Could not open draftbox: %s"),
4134 err ? err->message : _("Unknown error"));
4135 g_clear_error(&err);
4136 return FALSE;
4137 }
4138
4139 if (bsmsg->draft_message) {
4140 g_object_set_data(G_OBJECT(bsmsg->draft_message),
4141 BALSA_SENDMSG_WINDOW_KEY, NULL);
4142 if (bsmsg->draft_message->mailbox)
4143 libbalsa_mailbox_close(bsmsg->draft_message->mailbox,
4144 /* Respect pref setting: */
4145 balsa_app.expunge_on_close);
4146 g_object_unref(G_OBJECT(bsmsg->draft_message));
4147 }
4148 bsmsg->state = SENDMSG_STATE_CLEAN;
4149
4150 bsmsg->draft_message =
4151 libbalsa_mailbox_get_message(balsa_app.draftbox,
4152 libbalsa_mailbox_total_messages
4153 (balsa_app.draftbox));
4154 g_object_set_data(G_OBJECT(bsmsg->draft_message),
4155 BALSA_SENDMSG_WINDOW_KEY, bsmsg);
4156 balsa_information_parented(GTK_WINDOW(bsmsg->window),
4157 LIBBALSA_INFORMATION_MESSAGE,
4158 _("Message saved."));
4159
4160 return TRUE;
4161 }
4162
4163 static gboolean
sw_autosave_timeout_cb(BalsaSendmsg * bsmsg)4164 sw_autosave_timeout_cb(BalsaSendmsg * bsmsg)
4165 {
4166 gdk_threads_enter();
4167
4168 if (bsmsg->state == SENDMSG_STATE_MODIFIED) {
4169 if (sw_save_draft(bsmsg))
4170 bsmsg->state = SENDMSG_STATE_AUTO_SAVED;
4171 }
4172
4173 gdk_threads_leave();
4174
4175 return TRUE; /* do repeat it */
4176 }
4177
4178 static void
setup_headers_from_message(BalsaSendmsg * bsmsg,LibBalsaMessage * message)4179 setup_headers_from_message(BalsaSendmsg* bsmsg, LibBalsaMessage *message)
4180 {
4181 g_return_if_fail(message->headers);
4182
4183 /* Try to make the blank line in the address view useful;
4184 * - never make it a Bcc: line;
4185 * - if Cc: is non-empty, make it a Cc: line;
4186 * - if Cc: is empty, make it a To: line
4187 * Note that if set-from-list is given an empty list, the blank line
4188 * will be a To: line */
4189 libbalsa_address_view_set_from_list(bsmsg->recipient_view,
4190 "Bcc:",
4191 message->headers->bcc_list);
4192 libbalsa_address_view_set_from_list(bsmsg->recipient_view,
4193 "To:",
4194 message->headers->to_list);
4195 libbalsa_address_view_set_from_list(bsmsg->recipient_view,
4196 "Cc:",
4197 message->headers->cc_list);
4198 }
4199
4200
4201 /*
4202 * set_identity_from_mailbox
4203 *
4204 * Attempt to determine the default identity from the mailbox containing
4205 * the message.
4206 **/
4207 static gboolean
set_identity_from_mailbox(BalsaSendmsg * bsmsg,LibBalsaMessage * message)4208 set_identity_from_mailbox(BalsaSendmsg* bsmsg, LibBalsaMessage * message)
4209 {
4210 const gchar *identity;
4211
4212 LibBalsaIdentity* ident;
4213 GList *ilist;
4214
4215 if( message && message->mailbox && balsa_app.identities) {
4216 identity = libbalsa_mailbox_get_identity_name(message->mailbox);
4217 if(!identity) return FALSE;
4218 for (ilist = balsa_app.identities;
4219 ilist != NULL;
4220 ilist = g_list_next(ilist)) {
4221 ident = LIBBALSA_IDENTITY(ilist->data);
4222 if (!g_ascii_strcasecmp(identity, ident->identity_name)) {
4223 bsmsg->ident = ident;
4224 return TRUE;
4225 }
4226 }
4227 }
4228
4229 return FALSE; /* use default */
4230 }
4231
4232 /*
4233 * guess_identity
4234 *
4235 * Attempt to determine if a message should be associated with a
4236 * particular identity, other than the default. The to_list of the
4237 * original message needs to be set in order for it to work.
4238 **/
4239 /* First a helper; groups cannot be nested, and are not allowed in the
4240 * From: list. */
4241 static gboolean
guess_identity_from_list(BalsaSendmsg * bsmsg,InternetAddressList * list,gboolean allow_group)4242 guess_identity_from_list(BalsaSendmsg * bsmsg, InternetAddressList * list,
4243 gboolean allow_group)
4244 {
4245 gint i;
4246
4247 if (!list)
4248 return FALSE;
4249
4250 for (i = 0; i < internet_address_list_length(list); i++) {
4251 InternetAddress *ia = internet_address_list_get_address(list, i);
4252
4253 if (INTERNET_ADDRESS_IS_GROUP(ia)) {
4254 InternetAddressList *members =
4255 INTERNET_ADDRESS_GROUP(ia)->members;
4256 if (allow_group
4257 && guess_identity_from_list(bsmsg, members, FALSE))
4258 return TRUE;
4259 } else {
4260 GList *l;
4261
4262 for (l = balsa_app.identities; l; l = l->next) {
4263 LibBalsaIdentity *ident = LIBBALSA_IDENTITY(l->data);
4264 if (libbalsa_ia_rfc2821_equal(ia, ident->ia)) {
4265 bsmsg->ident = ident;
4266 return TRUE;
4267 }
4268 }
4269 }
4270 }
4271
4272 return FALSE;
4273 }
4274
4275 static gboolean
guess_identity(BalsaSendmsg * bsmsg,LibBalsaMessage * message)4276 guess_identity(BalsaSendmsg* bsmsg, LibBalsaMessage * message)
4277 {
4278 if (!message || !message->headers || !balsa_app.identities)
4279 return FALSE; /* use default */
4280
4281 if (bsmsg->is_continue)
4282 return guess_identity_from_list(bsmsg, message->headers->from,
4283 FALSE);
4284
4285 if (bsmsg->type != SEND_NORMAL)
4286 /* bsmsg->type == SEND_REPLY || bsmsg->type == SEND_REPLY_ALL ||
4287 * bsmsg->type == SEND_REPLY_GROUP || bsmsg->type == SEND_FORWARD_ATTACH ||
4288 * bsmsg->type == SEND_FORWARD_INLINE */
4289 return guess_identity_from_list(bsmsg, message->headers->to_list,
4290 TRUE)
4291 || guess_identity_from_list(bsmsg, message->headers->cc_list,
4292 TRUE);
4293
4294 return FALSE;
4295 }
4296
4297 static void
setup_headers_from_identity(BalsaSendmsg * bsmsg,LibBalsaIdentity * ident)4298 setup_headers_from_identity(BalsaSendmsg* bsmsg, LibBalsaIdentity *ident)
4299 {
4300 gtk_combo_box_set_active(GTK_COMBO_BOX(bsmsg->from[1]),
4301 g_list_index(balsa_app.identities, ident));
4302 #if !defined(ENABLE_TOUCH_UI)
4303 if(ident->replyto)
4304 libbalsa_address_view_set_from_string(bsmsg->replyto_view,
4305 "Reply To:",
4306 ident->replyto);
4307 #endif
4308 if(ident->bcc)
4309 libbalsa_address_view_set_from_string(bsmsg->recipient_view,
4310 "Bcc:",
4311 ident->bcc);
4312
4313 /* Make sure the blank line is "To:" */
4314 libbalsa_address_view_add_from_string(bsmsg->recipient_view,
4315 "To:", NULL);
4316 }
4317
4318 static int
comp_send_locales(const void * a,const void * b)4319 comp_send_locales(const void* a, const void* b)
4320 {
4321 return g_utf8_collate(((struct SendLocales*)a)->lang_name,
4322 ((struct SendLocales*)b)->lang_name);
4323 }
4324
4325 /* create_lang_menu:
4326 create language menu for the compose window. The order cannot be
4327 hardcoded because it depends on the current locale.
4328 */
4329 #define BALSA_LANGUAGE_MENU_POS "balsa-language-menu-pos"
4330 static void
create_lang_menu(GtkWidget * parent,BalsaSendmsg * bsmsg)4331 create_lang_menu(GtkWidget * parent, BalsaSendmsg * bsmsg)
4332 {
4333 unsigned i;
4334 gint selected_pos;
4335 GtkWidget *langs = gtk_menu_new();
4336 static gboolean locales_sorted = FALSE;
4337 GSList *group = NULL;
4338 EnchantBroker *broker;
4339
4340 if (!locales_sorted) {
4341 for (i = 0; i < ELEMENTS(locales); i++)
4342 locales[i].lang_name = _(locales[i].lang_name);
4343 qsort(locales, ELEMENTS(locales), sizeof(struct SendLocales),
4344 comp_send_locales);
4345 locales_sorted = TRUE;
4346 }
4347
4348 /* find the preferred charset... */
4349 #if HAVE_GTKSPELL
4350 selected_pos =
4351 find_locale_index_by_locale(balsa_app.spell_check_lang
4352 ? balsa_app.spell_check_lang
4353 : setlocale(LC_CTYPE, NULL));
4354 #else /* HAVE_GTKSPELL */
4355 selected_pos = find_locale_index_by_locale(setlocale(LC_CTYPE, NULL));
4356 #endif /* HAVE_GTKSPELL */
4357
4358 broker = enchant_broker_init();
4359
4360 for (i = 0; i < ELEMENTS(locales); i++) {
4361 if (locales[i].locale == NULL || locales[i].locale[0] == '\0')
4362 /* GtkSpell handles NULL lang, but complains about empty
4363 * lang; in either case, it does not go in the langs menu. */
4364 continue;
4365
4366 if (enchant_broker_dict_exists(broker, locales[i].locale)) {
4367 GtkWidget *w;
4368
4369 if (selected_pos < 0)
4370 /* We did not find balsa_app.spell_check_lang. */
4371 selected_pos = i;
4372
4373 w = gtk_radio_menu_item_new_with_mnemonic(group,
4374 locales[i].
4375 lang_name);
4376 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(w));
4377 if (i == (unsigned) selected_pos)
4378 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),
4379 TRUE);
4380
4381 g_signal_connect(G_OBJECT(w), "activate",
4382 G_CALLBACK(lang_set_cb), bsmsg);
4383 g_object_set_data(G_OBJECT(w), BALSA_LANGUAGE_MENU_POS,
4384 GINT_TO_POINTER(i));
4385 gtk_widget_show(w);
4386 gtk_menu_shell_append(GTK_MENU_SHELL(langs), w);
4387 }
4388 }
4389 enchant_broker_free(broker);
4390
4391 if (selected_pos >= 0)
4392 set_locale(bsmsg, selected_pos);
4393 gtk_menu_item_set_submenu(GTK_MENU_ITEM(parent), langs);
4394 gtk_widget_show(parent);
4395 }
4396
4397 /* Standard buttons; "" means a separator. */
4398 static const gchar* compose_toolbar[] = {
4399 #if defined(ENABLE_TOUCH_UI)
4400 GTK_STOCK_UNDO,
4401 GTK_STOCK_REDO,
4402 GTK_STOCK_SPELL_CHECK,
4403 "",
4404 BALSA_PIXMAP_ATTACHMENT,
4405 "",
4406 GTK_STOCK_SAVE,
4407 "",
4408 BALSA_PIXMAP_SEND,
4409 "",
4410 GTK_STOCK_CLOSE,
4411 "",
4412 BALSA_PIXMAP_IDENTITY,
4413 #else /* ENABLE_TOUCH_UI */
4414 BALSA_PIXMAP_SEND,
4415 "",
4416 BALSA_PIXMAP_ATTACHMENT,
4417 "",
4418 GTK_STOCK_SAVE,
4419 "",
4420 GTK_STOCK_UNDO,
4421 GTK_STOCK_REDO,
4422 "",
4423 BALSA_PIXMAP_IDENTITY,
4424 "",
4425 GTK_STOCK_SPELL_CHECK,
4426 "",
4427 GTK_STOCK_PRINT,
4428 "",
4429 GTK_STOCK_CLOSE,
4430 #endif /* ENABLE_TOUCH_UI */
4431 };
4432
4433 /* Create the toolbar model for the compose window's toolbar.
4434 */
4435 BalsaToolbarModel *
sendmsg_window_get_toolbar_model(void)4436 sendmsg_window_get_toolbar_model(void)
4437 {
4438 static BalsaToolbarModel *model = NULL;
4439 GSList *standard;
4440 guint i;
4441
4442 if (model)
4443 return model;
4444
4445 standard = NULL;
4446 for (i = 0; i < ELEMENTS(compose_toolbar); i++)
4447 standard = g_slist_append(standard, g_strdup(compose_toolbar[i]));
4448
4449 model =
4450 balsa_toolbar_model_new(BALSA_TOOLBAR_TYPE_COMPOSE_WINDOW,
4451 standard);
4452 balsa_toolbar_model_add_actions(model, entries,
4453 G_N_ELEMENTS(entries));
4454 balsa_toolbar_model_add_actions(model, ready_entries,
4455 G_N_ELEMENTS(ready_entries));
4456 balsa_toolbar_model_add_toggle_actions(model, toggle_entries,
4457 G_N_ELEMENTS(toggle_entries));
4458
4459 return model;
4460 }
4461
4462 static void
bsmsg_identities_changed_cb(BalsaSendmsg * bsmsg)4463 bsmsg_identities_changed_cb(BalsaSendmsg * bsmsg)
4464 {
4465 sw_set_sensitive(bsmsg, "SelectIdentity",
4466 balsa_app.identities->next != NULL);
4467 }
4468
4469 static void
sw_cc_add_list(InternetAddressList ** new_cc,InternetAddressList * list)4470 sw_cc_add_list(InternetAddressList **new_cc, InternetAddressList * list)
4471 {
4472 int i;
4473
4474 if (!list)
4475 return;
4476
4477 for (i = 0; i < internet_address_list_length(list); i++) {
4478 InternetAddress *ia = internet_address_list_get_address (list, i);
4479 GList *ident;
4480
4481 /* do not insert any of my identities into the cc: list */
4482 for (ident = balsa_app.identities; ident; ident = ident->next)
4483 if (libbalsa_ia_rfc2821_equal
4484 (ia, LIBBALSA_IDENTITY(ident->data)->ia))
4485 break;
4486 if (!ident) {
4487 if (*new_cc == NULL)
4488 *new_cc = internet_address_list_new();
4489 internet_address_list_add(*new_cc, ia);
4490 }
4491 }
4492 }
4493
4494 static BalsaSendmsg*
sendmsg_window_new()4495 sendmsg_window_new()
4496 {
4497 BalsaToolbarModel *model;
4498 GtkWidget *window;
4499 GtkWidget *main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
4500 BalsaSendmsg *bsmsg = NULL;
4501 #if HAVE_GTKSOURCEVIEW
4502 GtkSourceBuffer *source_buffer;
4503 #endif /* HAVE_GTKSOURCEVIEW */
4504 GtkUIManager *ui_manager;
4505 GtkAccelGroup *accel_group;
4506 GError *error = NULL;
4507 GtkWidget *menubar;
4508
4509 bsmsg = g_malloc(sizeof(BalsaSendmsg));
4510 bsmsg->in_reply_to = NULL;
4511 bsmsg->references = NULL;
4512 bsmsg->spell_check_lang = NULL;
4513 bsmsg->fcc_url = NULL;
4514 bsmsg->insert_mark = NULL;
4515 bsmsg->ident = balsa_app.current_ident;
4516 bsmsg->update_config = FALSE;
4517 bsmsg->quit_on_close = FALSE;
4518 bsmsg->state = SENDMSG_STATE_CLEAN;
4519
4520 bsmsg->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4521
4522 /*
4523 * restore the SendMsg window size
4524 */
4525 gtk_window_set_default_size(GTK_WINDOW(window),
4526 balsa_app.sw_width,
4527 balsa_app.sw_height);
4528 if (balsa_app.sw_maximized)
4529 gtk_window_maximize(GTK_WINDOW(window));
4530
4531 gtk_window_set_wmclass(GTK_WINDOW(window), "compose", "Balsa");
4532
4533 gtk_container_add(GTK_CONTAINER(window), main_box);
4534 gtk_widget_show_all(window);
4535
4536 bsmsg->type = SEND_NORMAL;
4537 bsmsg->is_continue = FALSE;
4538 #if !HAVE_GTKSPELL
4539 bsmsg->spell_checker = NULL;
4540 #endif /* HAVE_GTKSPELL */
4541 #ifdef HAVE_GPGME
4542 bsmsg->gpg_mode = LIBBALSA_PROTECT_RFC3156;
4543 #endif
4544 bsmsg->autosave_timeout_id = /* autosave every 5 minutes */
4545 g_timeout_add_seconds(60*5, (GSourceFunc)sw_autosave_timeout_cb, bsmsg);
4546
4547 bsmsg->draft_message = NULL;
4548 bsmsg->parent_message = NULL;
4549 g_signal_connect(G_OBJECT(window), "delete-event",
4550 G_CALLBACK(delete_event_cb), bsmsg);
4551 g_signal_connect(G_OBJECT(window), "destroy",
4552 G_CALLBACK(destroy_event_cb), bsmsg);
4553 g_signal_connect(G_OBJECT(window), "size_allocate",
4554 G_CALLBACK(sw_size_alloc_cb), bsmsg);
4555
4556 model = sendmsg_window_get_toolbar_model();
4557 ui_manager = sendmsg_window_ui_manager_new(bsmsg);
4558
4559 accel_group = gtk_ui_manager_get_accel_group(ui_manager);
4560 gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
4561 g_object_unref(accel_group);
4562
4563 if (!gtk_ui_manager_add_ui_from_string
4564 (ui_manager, ui_description, -1, &error)) {
4565 g_message("building menus failed: %s", error->message);
4566 g_error_free(error);
4567 g_object_unref(ui_manager);
4568 g_object_unref(window);
4569 g_free(bsmsg);
4570 return NULL;
4571 }
4572
4573 menubar = gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
4574 #if HAVE_MACOSX_DESKTOP
4575 libbalsa_macosx_menu(window, GTK_MENU_SHELL(menubar));
4576 #else
4577 gtk_box_pack_start(GTK_BOX(main_box), menubar, FALSE, FALSE, 0);
4578 #endif
4579
4580 bsmsg->toolbar = balsa_toolbar_new(model, ui_manager);
4581 gtk_box_pack_start(GTK_BOX(main_box), bsmsg->toolbar,
4582 FALSE, FALSE, 0);
4583
4584 /* Now that we have installed the menubar and toolbar, we no longer
4585 * need the UIManager. */
4586 g_object_unref(ui_manager);
4587
4588 bsmsg->flow = !balsa_app.wordwrap;
4589 sw_set_sensitive(bsmsg, "Reflow", bsmsg->flow);
4590 bsmsg->send_mp_alt = FALSE;
4591
4592 sw_set_sensitive(bsmsg, "SelectIdentity",
4593 balsa_app.identities->next != NULL);
4594 bsmsg->identities_changed_id =
4595 g_signal_connect_swapped(balsa_app.main_window, "identities-changed",
4596 (GCallback)bsmsg_identities_changed_cb,
4597 bsmsg);
4598 #if !HAVE_GTKSOURCEVIEW
4599 sw_buffer_set_undo(bsmsg, TRUE, FALSE);
4600 #endif /* HAVE_GTKSOURCEVIEW */
4601
4602 /* set options */
4603 bsmsg->req_dispnotify = FALSE;
4604
4605 sw_set_active(bsmsg, "Flowed", bsmsg->flow);
4606 sw_set_active(bsmsg, "SendMPAlt", bsmsg->ident->send_mp_alternative);
4607 sw_set_active(bsmsg, "ShowToolbar", balsa_app.show_compose_toolbar);
4608
4609 #ifdef HAVE_GPGME
4610 bsmsg_setup_gpg_ui(bsmsg);
4611 #endif
4612
4613 /* create the top portion with the to, from, etc in it */
4614 gtk_box_pack_start(GTK_BOX(main_box), create_info_pane(bsmsg),
4615 FALSE, FALSE, 0);
4616
4617 /* create text area for the message */
4618 gtk_box_pack_start(GTK_BOX(main_box), create_text_area(bsmsg),
4619 TRUE, TRUE, 0);
4620
4621 /* set the menus - and language index */
4622 init_menus(bsmsg);
4623
4624 /* Connect to "text-changed" here, so that we catch the initial text
4625 * and wrap it... */
4626 sw_buffer_signals_connect(bsmsg);
4627
4628 #if HAVE_GTKSOURCEVIEW
4629 source_buffer = GTK_SOURCE_BUFFER(gtk_text_view_get_buffer
4630 (GTK_TEXT_VIEW(bsmsg->text)));
4631 gtk_source_buffer_begin_not_undoable_action(source_buffer);
4632 gtk_source_buffer_end_not_undoable_action(source_buffer);
4633 sw_set_sensitive(bsmsg, "Undo", FALSE);
4634 sw_set_sensitive(bsmsg, "Redo", FALSE);
4635 #else /* HAVE_GTKSOURCEVIEW */
4636 sw_buffer_set_undo(bsmsg, FALSE, FALSE);
4637 #endif /* HAVE_GTKSOURCEVIEW */
4638
4639 bsmsg->update_config = TRUE;
4640
4641 bsmsg->delete_sig_id =
4642 g_signal_connect(G_OBJECT(balsa_app.main_window), "delete-event",
4643 G_CALLBACK(delete_event_cb), bsmsg);
4644
4645 bsmsg->current_language_menu =
4646 #if !defined(ENABLE_TOUCH_UI)
4647 gtk_ui_manager_get_widget(ui_manager, "/MainMenu/LanguageMenu");
4648 #else /* ENABLE_TOUCH_UI */
4649 gtk_ui_manager_get_widget(ui_manager,
4650 "/MainMenu/ToolsMenu/LanguageMenu");
4651 #endif /* ENABLE_TOUCH_UI */
4652 create_lang_menu(bsmsg->current_language_menu, bsmsg);
4653
4654 #if HAVE_GTKSPELL
4655 sw_set_active(bsmsg, "CheckSpelling", balsa_app.spell_check_active);
4656 #endif
4657 setup_headers_from_identity(bsmsg, bsmsg->ident);
4658
4659 return bsmsg;
4660 }
4661
4662 static void
insert_initial_sig(BalsaSendmsg * bsmsg)4663 insert_initial_sig(BalsaSendmsg *bsmsg)
4664 {
4665 GtkTextIter sig_pos;
4666 GtkTextBuffer *buffer =
4667 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
4668
4669 if(bsmsg->ident->sig_prepend)
4670 gtk_text_buffer_get_start_iter(buffer, &sig_pos);
4671 else
4672 gtk_text_buffer_get_end_iter(buffer, &sig_pos);
4673 gtk_text_buffer_insert(buffer, &sig_pos, "\n", 1);
4674 insert_signature_cb(NULL, bsmsg);
4675 gtk_text_buffer_get_start_iter(buffer, &sig_pos);
4676 gtk_text_buffer_place_cursor(buffer, &sig_pos);
4677 }
4678
4679 BalsaSendmsg*
sendmsg_window_compose(void)4680 sendmsg_window_compose(void)
4681 {
4682 BalsaSendmsg *bsmsg = sendmsg_window_new();
4683
4684 /* set the initial window title */
4685 bsmsg->type = SEND_NORMAL;
4686 sendmsg_window_set_title(bsmsg);
4687 if(bsmsg->ident->sig_sending)
4688 insert_initial_sig(bsmsg);
4689 bsmsg->state = SENDMSG_STATE_CLEAN;
4690 return bsmsg;
4691 }
4692
4693 BalsaSendmsg*
sendmsg_window_compose_with_address(const gchar * address)4694 sendmsg_window_compose_with_address(const gchar * address)
4695 {
4696 BalsaSendmsg *bsmsg = sendmsg_window_compose();
4697 libbalsa_address_view_add_from_string(bsmsg->recipient_view,
4698 "To:", address);
4699 return bsmsg;
4700 }
4701
4702 static void
bsm_prepare_for_setup(LibBalsaMessage * message)4703 bsm_prepare_for_setup(LibBalsaMessage *message)
4704 {
4705 if (message->mailbox)
4706 libbalsa_mailbox_open(message->mailbox, NULL);
4707 /* fill in that info:
4708 * ref the message so that we have all needed headers */
4709 libbalsa_message_body_ref(message, TRUE, TRUE);
4710 #ifdef HAVE_GPGME
4711 /* scan the message for encrypted parts - this is only possible if
4712 there is *no* other ref to it */
4713 balsa_message_perform_crypto(message, LB_MAILBOX_CHK_CRYPT_NEVER,
4714 TRUE, 1);
4715 #endif
4716 }
4717
4718 /* libbalsa_message_body_unref() may destroy the @param part - this is
4719 why body_unref() is done at the end. */
4720 static void
bsm_finish_setup(BalsaSendmsg * bsmsg,LibBalsaMessageBody * part)4721 bsm_finish_setup(BalsaSendmsg *bsmsg, LibBalsaMessageBody *part)
4722 {
4723 g_return_if_fail(part != NULL);
4724 g_return_if_fail(part->message != NULL);
4725
4726 if (part->message->mailbox &&
4727 !bsmsg->parent_message && !bsmsg->draft_message)
4728 libbalsa_mailbox_close(part->message->mailbox, FALSE);
4729 /* ...but mark it as unmodified. */
4730 bsmsg->state = SENDMSG_STATE_CLEAN;
4731 bsmsg_set_subject_from_body(bsmsg, part, bsmsg->ident);
4732 libbalsa_message_body_unref(part->message);
4733 }
4734
4735 static void
set_cc_from_all_recipients(BalsaSendmsg * bsmsg,LibBalsaMessageHeaders * headers)4736 set_cc_from_all_recipients(BalsaSendmsg* bsmsg,
4737 LibBalsaMessageHeaders *headers)
4738 {
4739 InternetAddressList *new_cc = NULL;
4740
4741 sw_cc_add_list(&new_cc, headers->to_list);
4742 sw_cc_add_list(&new_cc, headers->cc_list);
4743
4744 libbalsa_address_view_set_from_list(bsmsg->recipient_view,
4745 "Cc:",
4746 new_cc);
4747 if (new_cc)
4748 g_object_unref(new_cc);
4749 }
4750
4751 static void
set_in_reply_to(BalsaSendmsg * bsmsg,const gchar * message_id,LibBalsaMessageHeaders * headers)4752 set_in_reply_to(BalsaSendmsg *bsmsg, const gchar *message_id,
4753 LibBalsaMessageHeaders *headers)
4754 {
4755 gchar *tmp;
4756
4757 g_assert(message_id);
4758 if(message_id[0] == '<')
4759 tmp = g_strdup(message_id);
4760 else
4761 tmp = g_strconcat("<", message_id, ">", NULL);
4762 if (headers && headers->from) {
4763 gchar recvtime[50];
4764
4765 ctime_r(&headers->date, recvtime);
4766 if (recvtime[0]) /* safety check; remove trailing '\n' */
4767 recvtime[strlen(recvtime)-1] = '\0';
4768 bsmsg->in_reply_to =
4769 g_strconcat(tmp, " (from ",
4770 libbalsa_address_get_mailbox_from_list
4771 (headers->from),
4772 " on ", recvtime, ")", NULL);
4773 g_free(tmp);
4774 } else
4775 bsmsg->in_reply_to = tmp;
4776 }
4777
4778 static void
set_to(BalsaSendmsg * bsmsg,LibBalsaMessageHeaders * headers)4779 set_to(BalsaSendmsg *bsmsg, LibBalsaMessageHeaders *headers)
4780 {
4781 if (bsmsg->type == SEND_REPLY_GROUP) {
4782 set_list_post_address(bsmsg);
4783 } else {
4784 InternetAddressList *addr = headers->reply_to ?
4785 headers->reply_to : headers->from;
4786
4787 libbalsa_address_view_set_from_list(bsmsg->recipient_view,
4788 "To:", addr);
4789 }
4790 }
4791
4792 static void
set_references_reply(BalsaSendmsg * bsmsg,GList * references,const gchar * in_reply_to,const gchar * message_id)4793 set_references_reply(BalsaSendmsg *bsmsg, GList *references,
4794 const gchar *in_reply_to, const gchar *message_id)
4795 {
4796 GList *refs = NULL, *list;
4797
4798 for (list = references; list; list = list->next)
4799 refs = g_list_prepend(refs, g_strdup(list->data));
4800
4801 /* We're replying to parent_message, so construct the
4802 * references according to RFC 2822. */
4803 if (!references
4804 /* Parent message has no References header... */
4805 && in_reply_to)
4806 /* ...but it has an In-Reply-To header with a single
4807 * message identifier. */
4808 refs = g_list_prepend(refs, g_strdup(in_reply_to));
4809 if (message_id)
4810 refs = g_list_prepend(refs, g_strdup(message_id));
4811
4812 bsmsg->references = g_list_reverse(refs);
4813 }
4814
4815 static void
set_identity(BalsaSendmsg * bsmsg,LibBalsaMessage * message)4816 set_identity(BalsaSendmsg * bsmsg, LibBalsaMessage * message)
4817 {
4818 /* Set up the default identity */
4819 if(!set_identity_from_mailbox(bsmsg, message))
4820 /* Get the identity from the To: field of the original message */
4821 guess_identity(bsmsg, message);
4822 /* From: */
4823 setup_headers_from_identity(bsmsg, bsmsg->ident);
4824 }
4825
4826 static gboolean
sw_grab_focus_to_text(GtkWidget * text)4827 sw_grab_focus_to_text(GtkWidget * text)
4828 {
4829 gdk_threads_enter();
4830 gtk_widget_grab_focus(text);
4831 g_object_unref(text);
4832 gdk_threads_leave();
4833 return FALSE;
4834 }
4835
4836 BalsaSendmsg *
sendmsg_window_reply(LibBalsaMailbox * mailbox,guint msgno,SendType reply_type)4837 sendmsg_window_reply(LibBalsaMailbox * mailbox, guint msgno,
4838 SendType reply_type)
4839 {
4840 LibBalsaMessage *message =
4841 libbalsa_mailbox_get_message(mailbox, msgno);
4842 BalsaSendmsg *bsmsg = sendmsg_window_new();
4843
4844 g_assert(message);
4845 switch(reply_type) {
4846 case SEND_REPLY:
4847 case SEND_REPLY_ALL:
4848 case SEND_REPLY_GROUP:
4849 bsmsg->type = reply_type; break;
4850 default: printf("reply_type: %d\n", reply_type); g_assert_not_reached();
4851 }
4852 bsmsg->parent_message = message;
4853 set_identity(bsmsg, message);
4854
4855 bsm_prepare_for_setup(message);
4856
4857 set_to(bsmsg, message->headers);
4858
4859 if (message->message_id)
4860 set_in_reply_to(bsmsg, message->message_id, message->headers);
4861 if (reply_type == SEND_REPLY_ALL)
4862 set_cc_from_all_recipients(bsmsg, message->headers);
4863 set_references_reply(bsmsg, message->references,
4864 message->in_reply_to
4865 ? message->in_reply_to->data : NULL,
4866 message->message_id);
4867 if(balsa_app.autoquote)
4868 fill_body_from_message(bsmsg, message, QUOTE_ALL);
4869 if(bsmsg->ident->sig_whenreply)
4870 insert_initial_sig(bsmsg);
4871 bsm_finish_setup(bsmsg, message->body_list);
4872 g_idle_add((GSourceFunc) sw_grab_focus_to_text,
4873 g_object_ref(bsmsg->text));
4874 return bsmsg;
4875 }
4876
4877 BalsaSendmsg*
sendmsg_window_reply_embedded(LibBalsaMessageBody * part,SendType reply_type)4878 sendmsg_window_reply_embedded(LibBalsaMessageBody *part,
4879 SendType reply_type)
4880 {
4881 BalsaSendmsg *bsmsg = sendmsg_window_new();
4882 LibBalsaMessageHeaders *headers;
4883
4884 g_assert(part);
4885 g_return_val_if_fail(part->embhdrs, bsmsg);
4886
4887 switch(reply_type) {
4888 case SEND_REPLY:
4889 case SEND_REPLY_ALL:
4890 case SEND_REPLY_GROUP:
4891 bsmsg->type = reply_type; break;
4892 default: printf("reply_type: %d\n", reply_type); g_assert_not_reached();
4893 }
4894 bsm_prepare_for_setup(g_object_ref(part->message));
4895 headers = part->embhdrs;
4896 /* To: */
4897 set_to(bsmsg, headers);
4898
4899 if(part->embhdrs) {
4900 const gchar *message_id =
4901 libbalsa_message_header_get_one(part->embhdrs, "Message-Id");
4902 const gchar *in_reply_to =
4903 libbalsa_message_header_get_one(part->embhdrs, "In-Reply-To");
4904 GList *references =
4905 libbalsa_message_header_get_all(part->embhdrs, "References");
4906 if (message_id)
4907 set_in_reply_to(bsmsg, message_id, headers);
4908 set_references_reply(bsmsg, references,
4909 in_reply_to, message_id);
4910 fill_body_from_part(bsmsg, part->embhdrs, message_id, references,
4911 part->parts, QUOTE_ALL);
4912 g_list_foreach(references, (GFunc) g_free, NULL);
4913 g_list_free(references);
4914 }
4915
4916 if (reply_type == SEND_REPLY_ALL)
4917 set_cc_from_all_recipients(bsmsg, part->embhdrs);
4918
4919 bsm_finish_setup(bsmsg, part);
4920 if(bsmsg->ident->sig_whenreply)
4921 insert_initial_sig(bsmsg);
4922 g_idle_add((GSourceFunc) sw_grab_focus_to_text,
4923 g_object_ref(bsmsg->text));
4924 return bsmsg;
4925 }
4926
4927 BalsaSendmsg*
sendmsg_window_forward(LibBalsaMailbox * mailbox,guint msgno,gboolean attach)4928 sendmsg_window_forward(LibBalsaMailbox *mailbox, guint msgno,
4929 gboolean attach)
4930 {
4931 LibBalsaMessage *message =
4932 libbalsa_mailbox_get_message(mailbox, msgno);
4933 BalsaSendmsg *bsmsg = sendmsg_window_new();
4934 g_assert(message);
4935
4936 bsmsg->type = attach ? SEND_FORWARD_ATTACH : SEND_FORWARD_INLINE;
4937 if (attach) {
4938 if(!attach_message(bsmsg, message))
4939 balsa_information_parented(GTK_WINDOW(bsmsg->window),
4940 LIBBALSA_INFORMATION_WARNING,
4941 _("Attaching message failed.\n"
4942 "Possible reason: not enough temporary space"));
4943 bsmsg->state = SENDMSG_STATE_CLEAN;
4944 bsmsg_set_subject_from_body(bsmsg, message->body_list, bsmsg->ident);
4945 } else {
4946 bsm_prepare_for_setup(message);
4947 fill_body_from_message(bsmsg, message, QUOTE_NOPREFIX);
4948 bsm_finish_setup(bsmsg, message->body_list);
4949 }
4950 if(bsmsg->ident->sig_whenforward)
4951 insert_initial_sig(bsmsg);
4952 if(!attach) {
4953 GtkTextBuffer *buffer =
4954 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
4955 GtkTextIter pos;
4956 gtk_text_buffer_get_start_iter(buffer, &pos);
4957 gtk_text_buffer_place_cursor(buffer, &pos);
4958 gtk_text_buffer_insert_at_cursor(buffer, "\n", 1);
4959 gtk_text_buffer_get_start_iter(buffer, &pos);
4960 gtk_text_buffer_place_cursor(buffer, &pos);
4961 }
4962 return bsmsg;
4963 }
4964
4965 BalsaSendmsg*
sendmsg_window_continue(LibBalsaMailbox * mailbox,guint msgno)4966 sendmsg_window_continue(LibBalsaMailbox * mailbox, guint msgno)
4967 {
4968 LibBalsaMessage *message =
4969 libbalsa_mailbox_get_message(mailbox, msgno);
4970 BalsaSendmsg *bsmsg;
4971 const gchar *postpone_hdr;
4972 GList *list, *refs = NULL;
4973
4974 g_assert(message);
4975
4976 if ((bsmsg = g_object_get_data(G_OBJECT(message),
4977 BALSA_SENDMSG_WINDOW_KEY))) {
4978 gtk_window_present(GTK_WINDOW(bsmsg->window));
4979 return NULL;
4980 }
4981
4982 bsmsg = sendmsg_window_new();
4983 bsmsg->is_continue = TRUE;
4984 bsm_prepare_for_setup(message);
4985 bsmsg->draft_message = message;
4986 g_object_set_data(G_OBJECT(bsmsg->draft_message),
4987 BALSA_SENDMSG_WINDOW_KEY, bsmsg);
4988 set_identity(bsmsg, message);
4989 setup_headers_from_message(bsmsg, message);
4990
4991 #if !defined(ENABLE_TOUCH_UI)
4992 libbalsa_address_view_set_from_list(bsmsg->replyto_view,
4993 "Reply To:",
4994 message->headers->reply_to);
4995 #endif
4996 if (message->in_reply_to)
4997 bsmsg->in_reply_to =
4998 g_strconcat("<", message->in_reply_to->data, ">", NULL);
4999
5000 #ifdef HAVE_GPGME
5001 if ((postpone_hdr =
5002 libbalsa_message_get_user_header(message, "X-Balsa-Crypto")))
5003 bsmsg_setup_gpg_ui_by_mode(bsmsg, atoi(postpone_hdr));
5004 #endif
5005 if ((postpone_hdr =
5006 libbalsa_message_get_user_header(message, "X-Balsa-MDN")))
5007 sw_set_active(bsmsg, "RequestMDN", atoi(postpone_hdr) != 0);
5008 if ((postpone_hdr =
5009 libbalsa_message_get_user_header(message, "X-Balsa-Lang"))) {
5010 GtkWidget *langs =
5011 gtk_menu_item_get_submenu(GTK_MENU_ITEM
5012 (bsmsg->current_language_menu));
5013 GList *list, *children =
5014 gtk_container_get_children(GTK_CONTAINER(langs));
5015 gint selected_pos = find_locale_index_by_locale(postpone_hdr);
5016 set_locale(bsmsg, selected_pos);
5017 for (list = children; list; list = list->next) {
5018 GtkCheckMenuItem *menu_item = list->data;
5019 if (GPOINTER_TO_INT
5020 (g_object_get_data(G_OBJECT(menu_item),
5021 BALSA_LANGUAGE_MENU_POS)) ==
5022 selected_pos)
5023 gtk_check_menu_item_set_active(menu_item, TRUE);
5024 }
5025 g_list_free(children);
5026 }
5027 if ((postpone_hdr =
5028 libbalsa_message_get_user_header(message, "X-Balsa-Format")))
5029 sw_set_active(bsmsg, "Flowed", strcmp(postpone_hdr, "Fixed"));
5030 if ((postpone_hdr =
5031 libbalsa_message_get_user_header(message, "X-Balsa-MP-Alt")))
5032 sw_set_active(bsmsg, "SendMPAlt", !strcmp(postpone_hdr, "yes"));
5033 if ((postpone_hdr =
5034 libbalsa_message_get_user_header(message, "X-Balsa-Send-Type")))
5035 bsmsg->type = atoi(postpone_hdr);
5036
5037 for (list = message->references; list; list = list->next)
5038 refs = g_list_prepend(refs, g_strdup(list->data));
5039 bsmsg->references = g_list_reverse(refs);
5040
5041 continue_body(bsmsg, message);
5042 bsm_finish_setup(bsmsg, message->body_list);
5043 g_idle_add((GSourceFunc) sw_grab_focus_to_text,
5044 g_object_ref(bsmsg->text));
5045 return bsmsg;
5046 }
5047
5048 /* decode_and_strdup:
5049 decodes given URL string up to the delimiter and places the
5050 eos pointer in newstr if supplied (eos==NULL if end of string was reached)
5051 */
5052 static gchar*
decode_and_strdup(const gchar * str,int delim,gchar ** newstr)5053 decode_and_strdup(const gchar*str, int delim, gchar** newstr)
5054 {
5055 gchar num[3];
5056 GString *s = g_string_new(NULL);
5057 /* eos points to the character after the last to parse */
5058 gchar *eos = strchr(str, delim);
5059
5060 if(!eos) eos = (gchar*)str + strlen(str);
5061 while(str<eos) {
5062 switch(*str) {
5063 case '+':
5064 g_string_append_c(s, ' ');
5065 str++;
5066 break;
5067 case '%':
5068 if(str+2<eos) {
5069 strncpy(num, str+1, 2); num[2] = 0;
5070 g_string_append_c(s, strtol(num,NULL,16));
5071 }
5072 str+=3;
5073 break;
5074 default:
5075 g_string_append_c(s, *str++);
5076 }
5077 }
5078 if(newstr) *newstr = *eos ? eos+1 : NULL;
5079 eos = s->str;
5080 g_string_free(s,FALSE);
5081 return eos;
5082 }
5083
5084 /* process_url:
5085 extracts all characters until NUL or question mark; parse later fields
5086 of format 'key'='value' with ampersands as separators.
5087 */
5088 void
sendmsg_window_process_url(const char * url,field_setter func,void * data)5089 sendmsg_window_process_url(const char *url, field_setter func, void *data)
5090 {
5091 gchar * ptr, *to, *key, *val;
5092
5093 to = decode_and_strdup(url,'?', &ptr);
5094 func(data, "to", to);
5095 g_free(to);
5096 while(ptr) {
5097 key = decode_and_strdup(ptr,'=', &ptr);
5098 if(ptr) {
5099 val = decode_and_strdup(ptr,'&', &ptr);
5100 func(data, key, val);
5101 g_free(val);
5102 }
5103 g_free(key);
5104 }
5105 }
5106
5107 /* sendmsg_window_set_field:
5108 sets given field of the compose window to the specified value.
5109 */
5110
5111 #define NO_SECURITY_ISSUES_WITH_ATTACHMENTS TRUE
5112 #if defined(NO_SECURITY_ISSUES_WITH_ATTACHMENTS)
5113 static void
sw_attach_file(BalsaSendmsg * bsmsg,const gchar * val)5114 sw_attach_file(BalsaSendmsg * bsmsg, const gchar * val)
5115 {
5116 GtkFileChooser *attach;
5117
5118 if (!g_path_is_absolute(val)) {
5119 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5120 LIBBALSA_INFORMATION_WARNING,
5121 _("Could not attach the file %s: %s."), val,
5122 _("not an absolute path"));
5123 return;
5124 }
5125 if (!(g_str_has_prefix(val, g_get_home_dir())
5126 || g_str_has_prefix(val, g_get_tmp_dir()))) {
5127 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5128 LIBBALSA_INFORMATION_WARNING,
5129 _("Could not attach the file %s: %s."), val,
5130 _("not in your directory"));
5131 return;
5132 }
5133 if (!g_file_test(val, G_FILE_TEST_EXISTS)) {
5134 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5135 LIBBALSA_INFORMATION_WARNING,
5136 _("Could not attach the file %s: %s."), val,
5137 _("does not exist"));
5138 return;
5139 }
5140 if (!g_file_test(val, G_FILE_TEST_IS_REGULAR)) {
5141 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5142 LIBBALSA_INFORMATION_WARNING,
5143 _("Could not attach the file %s: %s."), val,
5144 _("not a regular file"));
5145 return;
5146 }
5147 attach = g_object_get_data(G_OBJECT(bsmsg->window),
5148 "balsa-sendmsg-window-attach-dialog");
5149 if (!attach) {
5150 attach = sw_attach_dialog(bsmsg);
5151 g_object_set_data(G_OBJECT(bsmsg->window),
5152 "balsa-sendmsg-window-attach-dialog", attach);
5153 g_object_set_data_full(G_OBJECT(attach),
5154 "balsa-sendmsg-window-attach-dir",
5155 g_path_get_dirname(val), g_free);
5156 } else {
5157 gchar *dirname = g_object_get_data(G_OBJECT(attach),
5158 "balsa-sendmsg-window-attach-dir");
5159 gchar *valdir = g_path_get_dirname(val);
5160 gboolean good = (strcmp(dirname, valdir) == 0);
5161
5162 g_free(valdir);
5163 if (!good) {
5164 /* gtk_file_chooser_select_filename will crash */
5165 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5166 LIBBALSA_INFORMATION_WARNING,
5167 _("Could not attach the file %s: %s."), val,
5168 _("not in current directory"));
5169 return;
5170 }
5171 }
5172 gtk_file_chooser_select_filename(attach, val);
5173 }
5174 #endif
5175
5176 void
sendmsg_window_set_field(BalsaSendmsg * bsmsg,const gchar * key,const gchar * val)5177 sendmsg_window_set_field(BalsaSendmsg * bsmsg, const gchar * key,
5178 const gchar * val)
5179 {
5180 const gchar *type;
5181 g_return_if_fail(bsmsg);
5182
5183 if (g_ascii_strcasecmp(key, "body") == 0) {
5184 GtkTextBuffer *buffer =
5185 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
5186
5187 gtk_text_buffer_insert_at_cursor(buffer, val, -1);
5188
5189 return;
5190 }
5191 #if defined(NO_SECURITY_ISSUES_WITH_ATTACHMENTS)
5192 if (g_ascii_strcasecmp(key, "attach") == 0) {
5193 sw_attach_file(bsmsg, val);
5194 return;
5195 }
5196 #endif
5197 if(g_ascii_strcasecmp(key, "subject") == 0) {
5198 append_comma_separated(GTK_EDITABLE(bsmsg->subject[1]), val);
5199 gtk_widget_show_all(bsmsg->subject[0]);
5200 gtk_widget_show_all(bsmsg->subject[1]);
5201 return;
5202 }
5203
5204 if (g_ascii_strcasecmp(key, "to") == 0)
5205 type = "To:";
5206 else if(g_ascii_strcasecmp(key, "cc") == 0)
5207 type = "Cc:";
5208 else if(g_ascii_strcasecmp(key, "bcc") == 0) {
5209 type = "Bcc:";
5210 if (!g_object_get_data(G_OBJECT(bsmsg->window),
5211 "balsa-sendmsg-window-url-bcc")) {
5212 GtkWidget *dialog =
5213 gtk_message_dialog_new
5214 (GTK_WINDOW(bsmsg->window),
5215 GTK_DIALOG_DESTROY_WITH_PARENT,
5216 GTK_MESSAGE_INFO,
5217 GTK_BUTTONS_OK,
5218 _("The link that you selected created\n"
5219 "a \"Blind copy\" (Bcc) address.\n"
5220 "Please check that the address\n"
5221 "is appropriate."));
5222 #if HAVE_MACOSX_DESKTOP
5223 libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(bsmsg->window));
5224 #endif
5225 g_object_set_data(G_OBJECT(bsmsg->window),
5226 "balsa-sendmsg-window-url-bcc", dialog);
5227 g_signal_connect(G_OBJECT(dialog), "response",
5228 G_CALLBACK(gtk_widget_destroy), NULL);
5229 gtk_widget_show_all(dialog);
5230 }
5231 }
5232 #if !defined(ENABLE_TOUCH_UI)
5233 else if(g_ascii_strcasecmp(key, "replyto") == 0) {
5234 libbalsa_address_view_add_from_string(bsmsg->replyto_view,
5235 "Reply To:",
5236 val);
5237 return;
5238 }
5239 #endif
5240 else return;
5241
5242 libbalsa_address_view_add_from_string(bsmsg->recipient_view, type, val);
5243 }
5244
5245
5246 /* opens the load file dialog box, allows selection of the file and includes
5247 it at current point */
5248
5249 static void
do_insert_string_select_ch(BalsaSendmsg * bsmsg,GtkTextBuffer * buffer,const gchar * string,size_t len,const gchar * fname)5250 do_insert_string_select_ch(BalsaSendmsg* bsmsg, GtkTextBuffer *buffer,
5251 const gchar* string, size_t len,
5252 const gchar* fname)
5253 {
5254 const gchar *charset = NULL;
5255 LibBalsaTextAttribute attr = libbalsa_text_attr_string(string);
5256
5257 do {
5258 LibBalsaCodeset codeset;
5259 LibBalsaCodesetInfo *info;
5260 gchar* s;
5261
5262 if ((codeset = sw_get_user_codeset(bsmsg, NULL, NULL, fname))
5263 == (LibBalsaCodeset) (-1))
5264 break;
5265 info = &libbalsa_codeset_info[codeset];
5266
5267 charset = info->std;
5268 if (info->win && (attr & LIBBALSA_TEXT_HI_CTRL))
5269 charset = info->win;
5270
5271 g_print("Trying charset: %s\n", charset);
5272 if (sw_can_convert(string, len, "UTF-8", charset, &s)) {
5273 gtk_text_buffer_insert_at_cursor(buffer, s, -1);
5274 g_free(s);
5275 break;
5276 }
5277 } while(1);
5278 }
5279
5280 static void
insert_file_response(GtkWidget * selector,gint response,BalsaSendmsg * bsmsg)5281 insert_file_response(GtkWidget * selector, gint response,
5282 BalsaSendmsg * bsmsg)
5283 {
5284 GtkFileChooser *fc;
5285 gchar *fname;
5286 FILE *fl;
5287 GtkTextBuffer *buffer;
5288 gchar * string;
5289 size_t len;
5290
5291 if (response != GTK_RESPONSE_OK) {
5292 gtk_widget_destroy(selector);
5293 return;
5294 }
5295
5296 fc = GTK_FILE_CHOOSER(selector);
5297 fname = gtk_file_chooser_get_filename(fc);
5298
5299 if ((fl = fopen(fname, "rt")) ==NULL) {
5300 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5301 LIBBALSA_INFORMATION_WARNING,
5302 _("Could not open the file %s.\n"), fname);
5303 g_free(fname);
5304 return;
5305 }
5306
5307 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
5308 string = NULL;
5309 len = libbalsa_readfile(fl, &string);
5310 fclose(fl);
5311
5312 if (string) {
5313 LibBalsaTextAttribute attr;
5314
5315 attr = libbalsa_text_attr_string(string);
5316 if (!attr || attr & LIBBALSA_TEXT_HI_UTF8)
5317 /* Ascii or utf-8 */
5318 gtk_text_buffer_insert_at_cursor(buffer, string, -1);
5319 else {
5320 /* Neither ascii nor utf-8... */
5321 gchar *s = NULL;
5322 const gchar *charset = sw_preferred_charset(bsmsg);
5323
5324 if (sw_can_convert(string, -1, "UTF-8", charset, &s)) {
5325 /* ...but seems to be in current charset. */
5326 gtk_text_buffer_insert_at_cursor(buffer, s, -1);
5327 g_free(s);
5328 } else
5329 /* ...and can't be decoded from current charset. */
5330 do_insert_string_select_ch(bsmsg, buffer, string, len,
5331 fname);
5332 }
5333 g_free(string);
5334 }
5335
5336 /* Use the same folder as for attachments. */
5337 g_free(balsa_app.attach_dir);
5338 balsa_app.attach_dir = gtk_file_chooser_get_current_folder(fc);
5339
5340 gtk_widget_destroy(selector);
5341 g_free(fname);
5342 }
5343
5344 static void
include_file_cb(GtkAction * action,BalsaSendmsg * bsmsg)5345 include_file_cb(GtkAction * action, BalsaSendmsg * bsmsg)
5346 {
5347 GtkWidget *file_selector;
5348
5349 file_selector =
5350 gtk_file_chooser_dialog_new(_("Include file"),
5351 GTK_WINDOW(bsmsg->window),
5352 GTK_FILE_CHOOSER_ACTION_OPEN,
5353 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
5354 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
5355 #if HAVE_MACOSX_DESKTOP
5356 libbalsa_macosx_menu_for_parent(file_selector, GTK_WINDOW(bsmsg->window));
5357 #endif
5358 gtk_window_set_destroy_with_parent(GTK_WINDOW(file_selector), TRUE);
5359 /* Use the same folder as for attachments. */
5360 if (balsa_app.attach_dir)
5361 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER
5362 (file_selector),
5363 balsa_app.attach_dir);
5364 g_signal_connect(G_OBJECT(file_selector), "response",
5365 G_CALLBACK(insert_file_response), bsmsg);
5366
5367 /* Display that dialog */
5368 gtk_widget_show(file_selector);
5369 }
5370
5371 static void
strip_chars(gchar * str,const gchar * char2strip)5372 strip_chars(gchar * str, const gchar * char2strip)
5373 {
5374 gchar *ins = str;
5375 while (*str) {
5376 if (strchr(char2strip, *str) == NULL)
5377 *ins++ = *str;
5378 str++;
5379 }
5380 *ins = '\0';
5381 }
5382
5383 static void
sw_wrap_body(BalsaSendmsg * bsmsg)5384 sw_wrap_body(BalsaSendmsg * bsmsg)
5385 {
5386 GtkTextView *text_view = GTK_TEXT_VIEW(bsmsg->text);
5387 GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
5388 GtkTextIter start, end;
5389
5390 gtk_text_buffer_get_bounds(buffer, &start, &end);
5391
5392 if (bsmsg->flow) {
5393 sw_buffer_signals_block(bsmsg, buffer);
5394 libbalsa_unwrap_buffer(buffer, &start, -1);
5395 sw_buffer_signals_unblock(bsmsg, buffer);
5396 } else {
5397 GtkTextIter now;
5398 gint pos;
5399 gchar *the_text;
5400
5401 gtk_text_buffer_get_iter_at_mark(buffer, &now,
5402 gtk_text_buffer_get_insert(buffer));
5403 pos = gtk_text_iter_get_offset(&now);
5404
5405 the_text = gtk_text_iter_get_text(&start, &end);
5406 libbalsa_wrap_string(the_text, balsa_app.wraplength);
5407 gtk_text_buffer_set_text(buffer, "", 0);
5408 gtk_text_buffer_insert_at_cursor(buffer, the_text, -1);
5409 g_free(the_text);
5410
5411 gtk_text_buffer_get_iter_at_offset(buffer, &now, pos);
5412 gtk_text_buffer_place_cursor(buffer, &now);
5413 }
5414 bsmsg->state = SENDMSG_STATE_MODIFIED;
5415 gtk_text_view_scroll_to_mark(text_view,
5416 gtk_text_buffer_get_insert(buffer),
5417 0, FALSE, 0, 0);
5418 }
5419
5420
5421 static gboolean
attachment2message(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)5422 attachment2message(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
5423 gpointer data)
5424 {
5425 LibBalsaMessage *message = LIBBALSA_MESSAGE(data);
5426 BalsaAttachInfo *attachment;
5427 LibBalsaMessageBody *body;
5428
5429 /* get the attachment information */
5430 gtk_tree_model_get(model, iter, ATTACH_INFO_COLUMN, &attachment, -1);
5431
5432 /* create the attachment */
5433 body = libbalsa_message_body_new(message);
5434 body->file_uri = attachment->file_uri;
5435 if (attachment->file_uri)
5436 g_object_ref(attachment->file_uri);
5437 else
5438 body->filename = g_strdup(attachment->uri_ref);
5439 body->content_type = g_strdup(attachment->force_mime_type);
5440 body->charset = g_strdup(attachment->charset);
5441 body->attach_mode = attachment->mode;
5442 libbalsa_message_append_part(message, body);
5443
5444 /* clean up */
5445 g_object_unref(attachment);
5446 return FALSE;
5447 }
5448
5449
5450 /* bsmsg2message:
5451 creates Message struct based on given BalsaMessage
5452 stripping EOL chars is necessary - the GtkEntry fields can in principle
5453 contain them. Such characters might screw up message formatting
5454 (consider moving this code to mutt part).
5455 */
5456
5457 static void
sw_set_header_from_path(LibBalsaMessage * message,const gchar * header,const gchar * path,const gchar * error_format)5458 sw_set_header_from_path(LibBalsaMessage * message, const gchar * header,
5459 const gchar * path, const gchar * error_format)
5460 {
5461 gchar *content = NULL;
5462 GError *err = NULL;
5463
5464 if (path && !(content =
5465 libbalsa_get_header_from_path(header, path, NULL,
5466 &err))) {
5467 balsa_information(LIBBALSA_INFORMATION_WARNING,
5468 error_format, path, err->message);
5469 g_error_free(err);
5470 }
5471
5472 libbalsa_message_set_user_header(message, header, content);
5473 g_free(content);
5474 }
5475
5476 static const gchar *
sw_required_charset(BalsaSendmsg * bsmsg,const gchar * text)5477 sw_required_charset(BalsaSendmsg * bsmsg, const gchar * text)
5478 {
5479 const gchar *charset = "us-ascii";
5480
5481 if (libbalsa_text_attr_string(text)) {
5482 charset = sw_preferred_charset(bsmsg);
5483 if (!sw_can_convert(text, -1, charset, "UTF-8", NULL))
5484 charset = "UTF-8";
5485 }
5486
5487 return charset;
5488 }
5489
5490 static LibBalsaMessage *
bsmsg2message(BalsaSendmsg * bsmsg)5491 bsmsg2message(BalsaSendmsg * bsmsg)
5492 {
5493 LibBalsaMessage *message;
5494 LibBalsaMessageBody *body;
5495 gchar *tmp;
5496 GtkTextIter start, end;
5497 LibBalsaIdentity *ident = bsmsg->ident;
5498 GtkTextBuffer *buffer;
5499 GtkTextBuffer *new_buffer = NULL;
5500
5501 message = libbalsa_message_new();
5502
5503 message->headers->from = internet_address_list_new ();
5504 internet_address_list_add(message->headers->from, ident->ia);
5505
5506 tmp = gtk_editable_get_chars(GTK_EDITABLE(bsmsg->subject[1]), 0, -1);
5507 strip_chars(tmp, "\r\n");
5508 libbalsa_message_set_subject(message, tmp);
5509 g_free(tmp);
5510
5511 message->headers->to_list =
5512 libbalsa_address_view_get_list(bsmsg->recipient_view, "To:");
5513
5514 message->headers->cc_list =
5515 libbalsa_address_view_get_list(bsmsg->recipient_view, "Cc:");
5516
5517 message->headers->bcc_list =
5518 libbalsa_address_view_get_list(bsmsg->recipient_view, "Bcc:");
5519
5520
5521 /* get the fcc-box from the option menu widget */
5522 bsmsg->fcc_url =
5523 g_strdup(balsa_mblist_mru_option_menu_get(bsmsg->fcc[1]));
5524
5525 #if !defined(ENABLE_TOUCH_UI)
5526 message->headers->reply_to =
5527 libbalsa_address_view_get_list(bsmsg->replyto_view, "Reply To:");
5528 #endif
5529
5530 if (bsmsg->req_dispnotify)
5531 libbalsa_message_set_dispnotify(message, ident->ia);
5532
5533 sw_set_header_from_path(message, "Face", ident->face,
5534 /* Translators: please do not translate Face. */
5535 _("Could not load Face header file %s: %s"));
5536 sw_set_header_from_path(message, "X-Face", ident->x_face,
5537 /* Translators: please do not translate Face. */
5538 _("Could not load X-Face header file %s: %s"));
5539
5540 message->references = bsmsg->references;
5541 bsmsg->references = NULL; /* steal it */
5542
5543 if (bsmsg->in_reply_to)
5544 message->in_reply_to =
5545 g_list_prepend(NULL, g_strdup(bsmsg->in_reply_to));
5546
5547 body = libbalsa_message_body_new(message);
5548
5549 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
5550 gtk_text_buffer_get_bounds(buffer, &start, &end);
5551
5552 if (bsmsg->flow) {
5553 /* Copy the message text to a new buffer: */
5554 GtkTextTagTable *table;
5555
5556 table = gtk_text_buffer_get_tag_table(buffer);
5557 new_buffer = gtk_text_buffer_new(table);
5558
5559 tmp = gtk_text_iter_get_text(&start, &end);
5560 gtk_text_buffer_set_text(new_buffer, tmp, -1);
5561 g_free(tmp);
5562
5563 /* Remove spaces before a newline: */
5564 gtk_text_buffer_get_bounds(new_buffer, &start, &end);
5565 libbalsa_unwrap_buffer(new_buffer, &start, -1);
5566 gtk_text_buffer_get_bounds(new_buffer, &start, &end);
5567 }
5568
5569 /* Copy the buffer text to the message: */
5570 body->buffer = gtk_text_iter_get_text(&start, &end);
5571 if (new_buffer)
5572 g_object_unref(new_buffer);
5573
5574 if (bsmsg->send_mp_alt)
5575 body->html_buffer =
5576 libbalsa_text_to_html(message->subj, body->buffer,
5577 bsmsg->spell_check_lang);
5578 if (bsmsg->flow)
5579 body->buffer =
5580 libbalsa_wrap_rfc2646(body->buffer, balsa_app.wraplength,
5581 TRUE, FALSE, TRUE);
5582
5583 /* Ildar reports that, when a message contains both text/plain and
5584 * text/html parts, some broken MUAs use the charset from the
5585 * text/plain part to display the text/html part; the latter is
5586 * encoded as UTF-8 by add_mime_body_plain (send.c), so we'll use
5587 * the same encoding for the text/plain part.
5588 * http://bugzilla.gnome.org/show_bug.cgi?id=580704 */
5589 body->charset =
5590 g_strdup(bsmsg->send_mp_alt ?
5591 "UTF-8" : sw_required_charset(bsmsg, body->buffer));
5592 libbalsa_message_append_part(message, body);
5593
5594 /* add attachments */
5595 gtk_tree_model_foreach(BALSA_MSG_ATTACH_MODEL(bsmsg),
5596 attachment2message, message);
5597
5598 message->headers->date = time(NULL);
5599 #ifdef HAVE_GPGME
5600 if (balsa_app.has_openpgp || balsa_app.has_smime)
5601 message->gpg_mode =
5602 (bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) != 0 ? bsmsg->gpg_mode : 0;
5603 else
5604 message->gpg_mode = 0;
5605 if (ident->force_key_id && *ident->force_key_id)
5606 message->force_key_id = g_strdup(ident->force_key_id);
5607 #endif
5608
5609 /* remember the parent window */
5610 g_object_set_data(G_OBJECT(message), "parent-window",
5611 GTK_WINDOW(bsmsg->window));
5612
5613 return message;
5614 }
5615
5616 /* ask the user for a subject */
5617 static gboolean
subject_not_empty(BalsaSendmsg * bsmsg)5618 subject_not_empty(BalsaSendmsg * bsmsg)
5619 {
5620 const gchar *subj;
5621 GtkWidget *no_subj_dialog;
5622 GtkWidget *dialog_vbox;
5623 GtkWidget *hbox;
5624 GtkWidget *image;
5625 GtkWidget *vbox;
5626 gchar *text_str;
5627 GtkWidget *label;
5628 GtkWidget *subj_entry;
5629 GtkWidget *dialog_action_area;
5630 GtkWidget *cnclbutton;
5631 GtkWidget *okbutton;
5632 GtkWidget *alignment;
5633 gint response;
5634
5635 /* read the subject widget and verify that it is contains something else
5636 than spaces */
5637 subj = gtk_entry_get_text(GTK_ENTRY(bsmsg->subject[1]));
5638 if (subj) {
5639 const gchar *p = subj;
5640
5641 while (*p && g_unichar_isspace(g_utf8_get_char(p)))
5642 p = g_utf8_next_char(p);
5643 if (*p != '\0')
5644 return TRUE;
5645 }
5646
5647 /* build the dialog */
5648 no_subj_dialog = gtk_dialog_new ();
5649 gtk_container_set_border_width (GTK_CONTAINER (no_subj_dialog), 6);
5650 gtk_window_set_modal (GTK_WINDOW (no_subj_dialog), TRUE);
5651 gtk_window_set_resizable (GTK_WINDOW (no_subj_dialog), FALSE);
5652 gtk_window_set_type_hint (GTK_WINDOW (no_subj_dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
5653
5654 dialog_vbox = gtk_dialog_get_content_area(GTK_DIALOG(no_subj_dialog));
5655
5656 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
5657 gtk_box_pack_start (GTK_BOX (dialog_vbox), hbox, TRUE, TRUE, 0);
5658 gtk_container_set_border_width (GTK_CONTAINER (hbox), 6);
5659
5660 image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
5661 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
5662 gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
5663
5664 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
5665 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
5666
5667 text_str = g_strdup_printf("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s",
5668 _("You did not specify a subject for this message"),
5669 _("If you would like to provide one, enter it below."));
5670 label = gtk_label_new (text_str);
5671 g_free(text_str);
5672 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
5673 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
5674 gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
5675 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
5676
5677 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
5678 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
5679
5680 label = gtk_label_new (_("Subject:"));
5681 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
5682
5683 subj_entry = gtk_entry_new ();
5684 gtk_entry_set_text(GTK_ENTRY(subj_entry), _("(no subject)"));
5685 gtk_box_pack_start (GTK_BOX (hbox), subj_entry, TRUE, TRUE, 0);
5686 gtk_entry_set_activates_default (GTK_ENTRY (subj_entry), TRUE);
5687
5688 dialog_action_area =
5689 gtk_dialog_get_action_area(GTK_DIALOG(no_subj_dialog));
5690 gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area), GTK_BUTTONBOX_END);
5691
5692 cnclbutton = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
5693 gtk_dialog_add_action_widget (GTK_DIALOG (no_subj_dialog), cnclbutton, GTK_RESPONSE_CANCEL);
5694 gtk_widget_set_can_default(cnclbutton, TRUE);
5695
5696 okbutton = gtk_button_new ();
5697 gtk_dialog_add_action_widget (GTK_DIALOG (no_subj_dialog), okbutton, GTK_RESPONSE_OK);
5698 gtk_widget_set_can_default(okbutton, TRUE);
5699 gtk_dialog_set_default_response(GTK_DIALOG (no_subj_dialog),
5700 GTK_RESPONSE_OK);
5701
5702 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
5703 gtk_container_add (GTK_CONTAINER (okbutton), alignment);
5704
5705 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
5706 gtk_container_add (GTK_CONTAINER (alignment), hbox);
5707
5708 image = gtk_image_new_from_stock (BALSA_PIXMAP_SEND, GTK_ICON_SIZE_BUTTON);
5709 gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
5710
5711 label = gtk_label_new_with_mnemonic (_("_Send"));
5712 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
5713 gtk_widget_set_can_focus(label, TRUE);
5714 gtk_widget_set_can_default(label, TRUE);
5715
5716 gtk_widget_grab_focus (subj_entry);
5717 gtk_editable_select_region(GTK_EDITABLE(subj_entry), 0, -1);
5718 gtk_widget_show_all(dialog_vbox);
5719
5720 response = gtk_dialog_run(GTK_DIALOG(no_subj_dialog));
5721
5722 /* always set the current string in the subject entry */
5723 gtk_entry_set_text(GTK_ENTRY(bsmsg->subject[1]),
5724 gtk_entry_get_text(GTK_ENTRY(subj_entry)));
5725 gtk_widget_destroy(no_subj_dialog);
5726
5727 return response == GTK_RESPONSE_OK;
5728 }
5729
5730 #ifdef HAVE_GPGME
5731 static gboolean
check_suggest_encryption(BalsaSendmsg * bsmsg)5732 check_suggest_encryption(BalsaSendmsg * bsmsg)
5733 {
5734 InternetAddressList * ia_list;
5735 gboolean can_encrypt;
5736 gpgme_protocol_t protocol;
5737 gint len;
5738
5739 /* check if the user wants to see the message */
5740 if (!bsmsg->ident->warn_send_plain)
5741 return TRUE;
5742
5743 /* nothing to do if encryption is already enabled */
5744 if ((bsmsg->gpg_mode & LIBBALSA_PROTECT_ENCRYPT) != 0)
5745 return TRUE;
5746
5747 /* we can not encrypt if we have bcc recipients */
5748 ia_list = libbalsa_address_view_get_list(bsmsg->recipient_view, "Bcc:");
5749 len = internet_address_list_length(ia_list);
5750 g_object_unref(ia_list);
5751 if (len > 0)
5752 return TRUE;
5753
5754 /* collect all to and cc recipients */
5755 protocol = bsmsg->gpg_mode & LIBBALSA_PROTECT_SMIMEV3 ?
5756 GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP;
5757
5758 ia_list = libbalsa_address_view_get_list(bsmsg->recipient_view, "To:");
5759 can_encrypt = libbalsa_can_encrypt_for_all(ia_list, protocol);
5760 g_object_unref(ia_list);
5761 if (can_encrypt) {
5762 ia_list = libbalsa_address_view_get_list(bsmsg->recipient_view, "Cc:");
5763 can_encrypt = libbalsa_can_encrypt_for_all(ia_list, protocol);
5764 g_object_unref(ia_list);
5765 }
5766 if (can_encrypt) {
5767 ia_list = internet_address_list_new();
5768 internet_address_list_add(ia_list, bsmsg->ident->ia);
5769 can_encrypt = libbalsa_can_encrypt_for_all(ia_list, protocol);
5770 g_object_unref(ia_list);
5771 }
5772
5773 /* ask the user if we could encrypt this message */
5774 if (can_encrypt) {
5775 GtkWidget *dialog;
5776 gint choice;
5777 GtkWidget *dialog_action_area;
5778 GtkWidget *button;
5779 GtkWidget *alignment;
5780 GtkWidget *hbox;
5781 GtkWidget *image;
5782 GtkWidget *label;
5783
5784 dialog = gtk_message_dialog_new
5785 (GTK_WINDOW(bsmsg->window),
5786 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
5787 GTK_MESSAGE_QUESTION,
5788 GTK_BUTTONS_NONE,
5789 _("You did not select encryption for this message, although "
5790 "%s public keys are available for all recipients. In order "
5791 "to protect your privacy, the message could be %s encrypted."),
5792 gpgme_get_protocol_name(protocol),
5793 gpgme_get_protocol_name(protocol));
5794 #if HAVE_MACOSX_DESKTOP
5795 libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(bsmsg->window));
5796 #endif
5797
5798 dialog_action_area = gtk_dialog_get_action_area(GTK_DIALOG(dialog));
5799 gtk_button_box_set_layout(GTK_BUTTON_BOX(dialog_action_area), GTK_BUTTONBOX_END);
5800
5801 button = gtk_button_new();
5802 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_YES);
5803 gtk_widget_set_can_default(button, TRUE);
5804 gtk_widget_grab_focus(button);
5805 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
5806 gtk_container_add(GTK_CONTAINER(button), alignment);
5807
5808 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
5809 gtk_container_add(GTK_CONTAINER(alignment), hbox);
5810 image = gtk_image_new_from_stock(BALSA_PIXMAP_GPG_ENCRYPT, GTK_ICON_SIZE_BUTTON);
5811 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
5812 label = gtk_label_new_with_mnemonic(_("Send _encrypted"));
5813 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
5814 gtk_widget_show_all(button);
5815
5816 button = gtk_button_new();
5817 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_NO);
5818 gtk_widget_set_can_default(button, TRUE);
5819 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
5820 gtk_container_add(GTK_CONTAINER(button), alignment);
5821
5822 hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
5823 gtk_container_add(GTK_CONTAINER(alignment), hbox);
5824 image = gtk_image_new_from_stock(BALSA_PIXMAP_SEND, GTK_ICON_SIZE_BUTTON);
5825 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
5826 label = gtk_label_new_with_mnemonic(_("Send _unencrypted"));
5827 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
5828 gtk_widget_show_all(button);
5829
5830 button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
5831 gtk_widget_show(button);
5832 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_CANCEL);
5833 gtk_widget_set_can_default(button, TRUE);
5834
5835 choice = gtk_dialog_run(GTK_DIALOG(dialog));
5836 gtk_widget_destroy(dialog);
5837 if (choice == GTK_RESPONSE_YES)
5838 bsmsg_setup_gpg_ui_by_mode(bsmsg, bsmsg->gpg_mode | LIBBALSA_PROTECT_ENCRYPT);
5839 else if (choice == GTK_RESPONSE_CANCEL || choice == GTK_RESPONSE_DELETE_EVENT)
5840 return FALSE;
5841 }
5842
5843 return TRUE;
5844 }
5845 #endif
5846
5847 /* "send message" menu and toolbar callback.
5848 */
5849 static gint
send_message_handler(BalsaSendmsg * bsmsg,gboolean queue_only)5850 send_message_handler(BalsaSendmsg * bsmsg, gboolean queue_only)
5851 {
5852 LibBalsaMsgCreateResult result;
5853 LibBalsaMessage *message;
5854 LibBalsaMailbox *fcc;
5855 #ifdef HAVE_GPGME
5856 GtkTreeIter iter;
5857 #endif
5858 GError * error = NULL;
5859
5860 if (!gtk_action_group_get_sensitive(bsmsg->ready_action_group))
5861 return FALSE;
5862
5863 if(!subject_not_empty(bsmsg))
5864 return FALSE;
5865
5866 #ifdef HAVE_GPGME
5867 if (!check_suggest_encryption(bsmsg))
5868 return FALSE;
5869
5870 if ((bsmsg->gpg_mode & LIBBALSA_PROTECT_OPENPGP) != 0) {
5871 gboolean warn_mp;
5872 gboolean warn_html_sign;
5873
5874 warn_mp = (bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) != 0 &&
5875 gtk_tree_model_get_iter_first(BALSA_MSG_ATTACH_MODEL(bsmsg), &iter);
5876 warn_html_sign = (bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) == LIBBALSA_PROTECT_SIGN &&
5877 bsmsg->send_mp_alt;
5878
5879 if (warn_mp || warn_html_sign) {
5880 /* we are going to RFC2440 sign/encrypt a multipart, or to
5881 * RFC2440 sign a multipart/alternative... */
5882 GtkWidget *dialog;
5883 gint choice;
5884 GString * message =
5885 g_string_new(_("You selected OpenPGP security for this message.\n"));
5886
5887 if (warn_html_sign)
5888 message =
5889 g_string_append(message,
5890 _("The message text will be sent as plain text and as "
5891 "HTML, but only the plain part can be signed.\n"));
5892 if (warn_mp)
5893 message =
5894 g_string_append(message,
5895 _("The message contains attachments, which cannot be "
5896 "signed or encrypted.\n"));
5897 message =
5898 g_string_append(message,
5899 _("You should select MIME mode if the complete "
5900 "message shall be protected. Do you really want to proceed?"));
5901 dialog = gtk_message_dialog_new
5902 (GTK_WINDOW(bsmsg->window),
5903 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
5904 GTK_MESSAGE_QUESTION,
5905 GTK_BUTTONS_OK_CANCEL, "%s", message->str);
5906 #if HAVE_MACOSX_DESKTOP
5907 libbalsa_macosx_menu_for_parent(dialog, GTK_WINDOW(bsmsg->window));
5908 #endif
5909 g_string_free(message, TRUE);
5910 choice = gtk_dialog_run(GTK_DIALOG(dialog));
5911 gtk_widget_destroy(dialog);
5912 if (choice != GTK_RESPONSE_OK)
5913 return FALSE;
5914 }
5915 }
5916 #endif
5917
5918 message = bsmsg2message(bsmsg);
5919 fcc = balsa_find_mailbox_by_url(bsmsg->fcc_url);
5920
5921 #ifdef HAVE_GPGME
5922 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5923 LIBBALSA_INFORMATION_DEBUG,
5924 _("sending message with gpg mode %d"),
5925 message->gpg_mode);
5926 #endif
5927
5928 #if ENABLE_ESMTP
5929 if(queue_only)
5930 result = libbalsa_message_queue(message, balsa_app.outbox, fcc,
5931 bsmsg->ident->smtp_server,
5932 bsmsg->flow, &error);
5933 else
5934 result = libbalsa_message_send(message, balsa_app.outbox, fcc,
5935 balsa_find_sentbox_by_url,
5936 bsmsg->ident->smtp_server,
5937 bsmsg->flow, balsa_app.debug, &error);
5938 #else
5939 if(queue_only)
5940 result = libbalsa_message_queue(message, balsa_app.outbox, fcc,
5941 bsmsg->flow, &error);
5942 else
5943 result = libbalsa_message_send(message, balsa_app.outbox, fcc,
5944 balsa_find_sentbox_by_url,
5945 bsmsg->flow, balsa_app.debug, &error);
5946 #endif
5947 if (result == LIBBALSA_MESSAGE_CREATE_OK) {
5948 if (bsmsg->parent_message && bsmsg->parent_message->mailbox
5949 && !bsmsg->parent_message->mailbox->readonly)
5950 libbalsa_message_reply(bsmsg->parent_message);
5951 sw_delete_draft(bsmsg);
5952 }
5953
5954 g_object_unref(G_OBJECT(message));
5955
5956 if (result != LIBBALSA_MESSAGE_CREATE_OK) {
5957 const char *msg;
5958 switch(result) {
5959 default:
5960 case LIBBALSA_MESSAGE_CREATE_ERROR:
5961 msg = _("Message could not be created"); break;
5962 case LIBBALSA_MESSAGE_QUEUE_ERROR:
5963 msg = _("Message could not be queued in outbox"); break;
5964 case LIBBALSA_MESSAGE_SAVE_ERROR:
5965 msg = _("Message could not be saved in sentbox"); break;
5966 case LIBBALSA_MESSAGE_SEND_ERROR:
5967 msg = _("Message could not be sent"); break;
5968 #ifdef HAVE_GPGME
5969 case LIBBALSA_MESSAGE_SIGN_ERROR:
5970 msg = _("Message could not be signed"); break;
5971 case LIBBALSA_MESSAGE_ENCRYPT_ERROR:
5972 msg = _("Message could not be encrypted"); break;
5973 #endif
5974 }
5975 if (error)
5976 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5977 LIBBALSA_INFORMATION_ERROR,
5978 _("Send failed: %s\n%s"), msg,
5979 error->message);
5980 else
5981 balsa_information_parented(GTK_WINDOW(bsmsg->window),
5982 LIBBALSA_INFORMATION_ERROR,
5983 _("Send failed: %s"), msg);
5984 return FALSE;
5985 }
5986 g_clear_error(&error);
5987
5988 gtk_widget_destroy(bsmsg->window);
5989
5990 return TRUE;
5991 }
5992
5993
5994 /* "send message" menu callback */
5995 static void
toolbar_send_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)5996 toolbar_send_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
5997 {
5998 send_message_handler(bsmsg, balsa_app.always_queue_sent_mail);
5999 }
6000
6001 static void
send_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)6002 send_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6003 {
6004 send_message_handler(bsmsg, FALSE);
6005 }
6006
6007
6008 static void
queue_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)6009 queue_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6010 {
6011 send_message_handler(bsmsg, TRUE);
6012 }
6013
6014 static gboolean
message_postpone(BalsaSendmsg * bsmsg)6015 message_postpone(BalsaSendmsg * bsmsg)
6016 {
6017 gboolean successp;
6018 LibBalsaMessage *message;
6019 GPtrArray *headers;
6020 GError *error = NULL;
6021
6022 /* Silent fallback to UTF-8 */
6023 message = bsmsg2message(bsmsg);
6024
6025 /* sufficiently long for fcc, mdn, gpg */
6026 headers = g_ptr_array_new();
6027 if (bsmsg->fcc_url) {
6028 g_ptr_array_add(headers, g_strdup("X-Balsa-Fcc"));
6029 g_ptr_array_add(headers, g_strdup(bsmsg->fcc_url));
6030 }
6031 g_ptr_array_add(headers, g_strdup("X-Balsa-MDN"));
6032 g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->req_dispnotify));
6033 #ifdef HAVE_GPGME
6034 g_ptr_array_add(headers, g_strdup("X-Balsa-Crypto"));
6035 g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->gpg_mode));
6036 #endif
6037
6038 #if HAVE_GTKSPELL
6039 if (sw_get_active(bsmsg, "CheckSpelling")) {
6040 g_ptr_array_add(headers, g_strdup("X-Balsa-Lang"));
6041 g_ptr_array_add(headers, g_strdup(bsmsg->spell_check_lang));
6042 }
6043 #else /* HAVE_GTKSPELL */
6044 g_ptr_array_add(headers, g_strdup("X-Balsa-Lang"));
6045 g_ptr_array_add(headers, g_strdup(bsmsg->spell_check_lang));
6046 #endif /* HAVE_GTKSPELL */
6047 g_ptr_array_add(headers, g_strdup("X-Balsa-Format"));
6048 g_ptr_array_add(headers, g_strdup(bsmsg->flow ? "Flowed" : "Fixed"));
6049 g_ptr_array_add(headers, g_strdup("X-Balsa-MP-Alt"));
6050 g_ptr_array_add(headers, g_strdup(bsmsg->send_mp_alt ? "yes" : "no"));
6051 g_ptr_array_add(headers, g_strdup("X-Balsa-Send-Type"));
6052 g_ptr_array_add(headers, g_strdup_printf("%d", bsmsg->type));
6053 g_ptr_array_add(headers, NULL);
6054
6055 if ((bsmsg->type == SEND_REPLY || bsmsg->type == SEND_REPLY_ALL ||
6056 bsmsg->type == SEND_REPLY_GROUP))
6057 successp = libbalsa_message_postpone(message, balsa_app.draftbox,
6058 bsmsg->parent_message,
6059 (gchar **) headers->pdata,
6060 bsmsg->flow, &error);
6061 else
6062 successp = libbalsa_message_postpone(message, balsa_app.draftbox,
6063 NULL,
6064 (gchar **) headers->pdata,
6065 bsmsg->flow, &error);
6066 g_ptr_array_foreach(headers, (GFunc) g_free, NULL);
6067 g_ptr_array_free(headers, TRUE);
6068
6069 if(successp)
6070 sw_delete_draft(bsmsg);
6071 else {
6072 balsa_information_parented(GTK_WINDOW(bsmsg->window),
6073 LIBBALSA_INFORMATION_ERROR,
6074 _("Could not postpone message: %s"),
6075 error ? error->message : "");
6076 g_clear_error(&error);
6077 }
6078
6079 g_object_unref(G_OBJECT(message));
6080 return successp;
6081 }
6082
6083 /* "postpone message" menu callback */
6084 static void
postpone_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)6085 postpone_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6086 {
6087 if (gtk_action_group_get_sensitive(bsmsg->ready_action_group)) {
6088 if(message_postpone(bsmsg)) {
6089 balsa_information_parented(GTK_WINDOW(bsmsg->window),
6090 LIBBALSA_INFORMATION_MESSAGE,
6091 _("Message postponed."));
6092 gtk_widget_destroy(bsmsg->window);
6093 } else {
6094 balsa_information_parented(GTK_WINDOW(bsmsg->window),
6095 LIBBALSA_INFORMATION_MESSAGE,
6096 _("Could not postpone message."));
6097 }
6098 }
6099 }
6100
6101 static void
save_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)6102 save_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6103 {
6104 if (sw_save_draft(bsmsg))
6105 bsmsg->state = SENDMSG_STATE_CLEAN;
6106 }
6107
6108 #if !defined(ENABLE_TOUCH_UI)
6109 static void
page_setup_cb(GtkAction * action,BalsaSendmsg * bsmsg)6110 page_setup_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6111 {
6112 LibBalsaMessage *message;
6113
6114 message = bsmsg2message(bsmsg);
6115 message_print_page_setup(GTK_WINDOW(bsmsg->window));
6116 g_object_unref(message);
6117 }
6118 #endif /* ENABLE_TOUCH_UI */
6119
6120 static void
print_message_cb(GtkAction * action,BalsaSendmsg * bsmsg)6121 print_message_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6122 {
6123 LibBalsaMessage *message;
6124
6125 message = bsmsg2message(bsmsg);
6126 message_print(message, GTK_WINDOW(bsmsg->window));
6127 g_object_unref(message);
6128 }
6129
6130 /*
6131 * Signal handlers for updating the cursor when text is inserted.
6132 * The "insert-text" signal is emitted before the insertion, so we
6133 * create a mark at the insertion point.
6134 * The "changed" signal is emitted after the insertion, and we move the
6135 * cursor to the end of the inserted text.
6136 * This achieves nothing if the text was typed, as the cursor is moved
6137 * there anyway; if the text is copied by drag and drop or center-click,
6138 * this deselects any selected text and places the cursor at the end of
6139 * the insertion.
6140 */
6141 static void
sw_buffer_insert_text(GtkTextBuffer * buffer,GtkTextIter * iter,const gchar * text,gint len,BalsaSendmsg * bsmsg)6142 sw_buffer_insert_text(GtkTextBuffer * buffer, GtkTextIter * iter,
6143 const gchar * text, gint len, BalsaSendmsg * bsmsg)
6144 {
6145 bsmsg->insert_mark =
6146 gtk_text_buffer_create_mark(buffer, "balsa-insert-mark", iter,
6147 FALSE);
6148 #if !HAVE_GTKSOURCEVIEW
6149 /* If this insertion is not from the keyboard, or if we just undid
6150 * something, save the current buffer for undo. */
6151 if (len > 1 /* Not keyboard? */
6152 || !sw_get_sensitive(bsmsg, "Undo"))
6153 sw_buffer_save(bsmsg);
6154 #endif /* HAVE_GTKSOURCEVIEW */
6155 }
6156
6157 static void
sw_buffer_changed(GtkTextBuffer * buffer,BalsaSendmsg * bsmsg)6158 sw_buffer_changed(GtkTextBuffer * buffer, BalsaSendmsg * bsmsg)
6159 {
6160 if (bsmsg->insert_mark) {
6161 GtkTextIter iter;
6162
6163 gtk_text_buffer_get_iter_at_mark(buffer, &iter,
6164 bsmsg->insert_mark);
6165 gtk_text_buffer_place_cursor(buffer, &iter);
6166 bsmsg->insert_mark = NULL;
6167 }
6168
6169 bsmsg->state = SENDMSG_STATE_MODIFIED;
6170 }
6171
6172 #if !HAVE_GTKSOURCEVIEW
6173 static void
sw_buffer_delete_range(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,BalsaSendmsg * bsmsg)6174 sw_buffer_delete_range(GtkTextBuffer * buffer, GtkTextIter * start,
6175 GtkTextIter * end, BalsaSendmsg * bsmsg)
6176 {
6177 if (gtk_text_iter_get_offset(end) >
6178 gtk_text_iter_get_offset(start) + 1)
6179 sw_buffer_save(bsmsg);
6180 }
6181 #endif /* HAVE_GTKSOURCEVIEW */
6182
6183 /*
6184 * Helpers for the undo and redo buffers.
6185 */
6186 static void
sw_buffer_signals_connect(BalsaSendmsg * bsmsg)6187 sw_buffer_signals_connect(BalsaSendmsg * bsmsg)
6188 {
6189 GtkTextBuffer *buffer =
6190 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
6191
6192 bsmsg->insert_text_sig_id =
6193 g_signal_connect(buffer, "insert-text",
6194 G_CALLBACK(sw_buffer_insert_text), bsmsg);
6195 bsmsg->changed_sig_id =
6196 g_signal_connect(buffer, "changed",
6197 G_CALLBACK(sw_buffer_changed), bsmsg);
6198 #if !HAVE_GTKSOURCEVIEW
6199 bsmsg->delete_range_sig_id =
6200 g_signal_connect(buffer, "delete-range",
6201 G_CALLBACK(sw_buffer_delete_range), bsmsg);
6202 #endif /* HAVE_GTKSOURCEVIEW */
6203 }
6204
6205 #if !HAVE_GTKSOURCEVIEW || !HAVE_GTKSPELL
6206 static void
sw_buffer_signals_disconnect(BalsaSendmsg * bsmsg)6207 sw_buffer_signals_disconnect(BalsaSendmsg * bsmsg)
6208 {
6209 GtkTextBuffer *buffer =
6210 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
6211
6212 g_signal_handler_disconnect(buffer, bsmsg->changed_sig_id);
6213 #if !HAVE_GTKSOURCEVIEW
6214 g_signal_handler_disconnect(buffer, bsmsg->delete_range_sig_id);
6215 #endif /* HAVE_GTKSOURCEVIEW */
6216 g_signal_handler_disconnect(buffer, bsmsg->insert_text_sig_id);
6217 }
6218 #endif /* !HAVE_GTKSOURCEVIEW || !HAVE_GTKSPELL */
6219
6220 #if !HAVE_GTKSOURCEVIEW
6221 static void
sw_buffer_set_undo(BalsaSendmsg * bsmsg,gboolean undo,gboolean redo)6222 sw_buffer_set_undo(BalsaSendmsg * bsmsg, gboolean undo, gboolean redo)
6223 {
6224 sw_set_sensitive(bsmsg, "Undo", undo);
6225 sw_set_sensitive(bsmsg, "Redo", redo);
6226 }
6227 #endif /* HAVE_GTKSOURCEVIEW */
6228
6229 #ifdef HAVE_GTKSPELL
6230 static void
sw_spell_attach(BalsaSendmsg * bsmsg)6231 sw_spell_attach(BalsaSendmsg * bsmsg)
6232 {
6233 GtkSpellChecker *spell;
6234 GError *err = NULL;
6235
6236 spell = gtk_spell_checker_new();
6237 gtk_spell_checker_set_language(spell, bsmsg->spell_check_lang, &err);
6238 if (err) {
6239 /* Should not happen, since we now check the language. */
6240 balsa_information_parented(GTK_WINDOW(bsmsg->window),
6241 LIBBALSA_INFORMATION_WARNING,
6242 _("Error starting spell checker: %s"),
6243 err->message);
6244 g_error_free(err);
6245
6246 /* No spell checker, so deactivate the button. */
6247 sw_set_active(bsmsg, "CheckSpelling", FALSE);
6248 } else
6249 gtk_spell_checker_attach(spell, GTK_TEXT_VIEW(bsmsg->text));
6250 }
6251
6252 static gboolean
sw_spell_detach(BalsaSendmsg * bsmsg)6253 sw_spell_detach(BalsaSendmsg * bsmsg)
6254 {
6255 GtkSpellChecker *spell;
6256
6257 spell = gtk_spell_checker_get_from_text_view(GTK_TEXT_VIEW(bsmsg->text));
6258 if (spell)
6259 gtk_spell_checker_detach(spell);
6260
6261 return spell != NULL;
6262 }
6263 #endif /* HAVE_GTKSPELL */
6264
6265 #if !HAVE_GTKSOURCEVIEW
6266 static void
sw_buffer_swap(BalsaSendmsg * bsmsg,gboolean undo)6267 sw_buffer_swap(BalsaSendmsg * bsmsg, gboolean undo)
6268 {
6269 GtkTextBuffer *buffer =
6270 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
6271 #if HAVE_GTKSPELL
6272 gboolean had_spell;
6273
6274 /* GtkSpell doesn't seem to handle setting a new buffer... */
6275 had_spell = sw_spell_detach(bsmsg);
6276 #endif /* HAVE_GTKSPELL */
6277
6278 sw_buffer_signals_disconnect(bsmsg);
6279 g_object_ref(G_OBJECT(buffer));
6280 gtk_text_view_set_buffer(GTK_TEXT_VIEW(bsmsg->text), bsmsg->buffer2);
6281 #if HAVE_GTKSPELL
6282 if (had_spell)
6283 sw_spell_attach(bsmsg);
6284 #endif /* HAVE_GTKSPELL */
6285 g_object_unref(bsmsg->buffer2);
6286 bsmsg->buffer2 = buffer;
6287 sw_buffer_signals_connect(bsmsg);
6288 sw_buffer_set_undo(bsmsg, !undo, undo);
6289 }
6290
6291 static void
sw_buffer_save(BalsaSendmsg * bsmsg)6292 sw_buffer_save(BalsaSendmsg * bsmsg)
6293 {
6294 GtkTextBuffer *buffer =
6295 gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
6296 GtkTextIter start, end, iter;
6297
6298 gtk_text_buffer_get_bounds(buffer, &start, &end);
6299 gtk_text_buffer_set_text(bsmsg->buffer2, "", 0);
6300 gtk_text_buffer_get_start_iter(bsmsg->buffer2, &iter);
6301 gtk_text_buffer_insert_range(bsmsg->buffer2, &iter, &start, &end);
6302
6303 sw_buffer_set_undo(bsmsg, TRUE, FALSE);
6304 }
6305 #endif /* HAVE_GTKSOURCEVIEW */
6306
6307 /*
6308 * Menu and toolbar callbacks.
6309 */
6310
6311 #if HAVE_GTKSOURCEVIEW
6312 static void
sw_undo_cb(GtkAction * action,BalsaSendmsg * bsmsg)6313 sw_undo_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6314 {
6315 g_signal_emit_by_name(bsmsg->text, "undo");
6316 }
6317
6318 static void
sw_redo_cb(GtkAction * action,BalsaSendmsg * bsmsg)6319 sw_redo_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6320 {
6321 g_signal_emit_by_name(bsmsg->text, "redo");
6322 }
6323 #else /* HAVE_GTKSOURCEVIEW */
6324 static void
sw_undo_cb(GtkAction * action,BalsaSendmsg * bsmsg)6325 sw_undo_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6326 {
6327 sw_buffer_swap(bsmsg, TRUE);
6328 }
6329
6330 static void
sw_redo_cb(GtkAction * action,BalsaSendmsg * bsmsg)6331 sw_redo_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6332 {
6333 sw_buffer_swap(bsmsg, FALSE);
6334 }
6335 #endif /* HAVE_GTKSOURCEVIEW */
6336
6337 /*
6338 * Cut, copy, and paste callbacks, and a helper.
6339 */
6340 static void
clipboard_helper(BalsaSendmsg * bsmsg,gchar * signal)6341 clipboard_helper(BalsaSendmsg * bsmsg, gchar * signal)
6342 {
6343 guint signal_id;
6344 GtkWidget *focus_widget =
6345 gtk_window_get_focus(GTK_WINDOW(bsmsg->window));
6346
6347 signal_id =
6348 g_signal_lookup(signal, G_TYPE_FROM_INSTANCE(focus_widget));
6349 if (signal_id)
6350 g_signal_emit(focus_widget, signal_id, (GQuark) 0);
6351 }
6352
6353 static void
cut_cb(GtkAction * action,BalsaSendmsg * bsmsg)6354 cut_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6355 {
6356 clipboard_helper(bsmsg, "cut-clipboard");
6357 }
6358
6359 static void
copy_cb(GtkAction * action,BalsaSendmsg * bsmsg)6360 copy_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6361 {
6362 clipboard_helper(bsmsg, "copy-clipboard");
6363 }
6364
6365 static void
paste_cb(GtkAction * action,BalsaSendmsg * bsmsg)6366 paste_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6367 {
6368 clipboard_helper(bsmsg, "paste-clipboard");
6369 }
6370
6371 /*
6372 * More menu callbacks.
6373 */
6374 static void
select_all_cb(GtkAction * action,BalsaSendmsg * bsmsg)6375 select_all_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6376 {
6377 balsa_window_select_all(GTK_WINDOW(bsmsg->window));
6378 }
6379
6380 static void
wrap_body_cb(GtkAction * action,BalsaSendmsg * bsmsg)6381 wrap_body_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6382 {
6383 #if !HAVE_GTKSOURCEVIEW
6384 sw_buffer_save(bsmsg);
6385 #endif /* HAVE_GTKSOURCEVIEW */
6386 sw_wrap_body(bsmsg);
6387 }
6388
6389 static void
reflow_selected_cb(GtkAction * action,BalsaSendmsg * bsmsg)6390 reflow_selected_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6391 {
6392 GtkTextView *text_view;
6393 GtkTextBuffer *buffer;
6394 #if USE_GREGEX
6395 GRegex *rex;
6396 #else /* USE_GREGEX */
6397 regex_t rex;
6398 #endif /* USE_GREGEX */
6399
6400 if (!bsmsg->flow)
6401 return;
6402
6403 #if USE_GREGEX
6404 if (!(rex = balsa_quote_regex_new()))
6405 return;
6406 #else /* USE_GREGEX */
6407 if (regcomp(&rex, balsa_app.quote_regex, REG_EXTENDED)) {
6408 balsa_information_parented(GTK_WINDOW(bsmsg->window),
6409 LIBBALSA_INFORMATION_WARNING,
6410 _("Could not compile %s"),
6411 _("Quoted Text Regular Expression"));
6412 return;
6413 }
6414 #endif /* USE_GREGEX */
6415
6416 #if !HAVE_GTKSOURCEVIEW
6417 sw_buffer_save(bsmsg);
6418 #endif /* HAVE_GTKSOURCEVIEW */
6419
6420 text_view = GTK_TEXT_VIEW(bsmsg->text);
6421 buffer = gtk_text_view_get_buffer(text_view);
6422 sw_buffer_signals_block(bsmsg, buffer);
6423 #if USE_GREGEX
6424 libbalsa_unwrap_selection(buffer, rex);
6425 #else /* USE_GREGEX */
6426 libbalsa_unwrap_selection(buffer, &rex);
6427 #endif /* USE_GREGEX */
6428 sw_buffer_signals_unblock(bsmsg, buffer);
6429
6430 bsmsg->state = SENDMSG_STATE_MODIFIED;
6431 gtk_text_view_scroll_to_mark(text_view,
6432 gtk_text_buffer_get_insert(buffer),
6433 0, FALSE, 0, 0);
6434
6435 #if USE_GREGEX
6436 g_regex_unref(rex);
6437 #else /* USE_GREGEX */
6438 regfree(&rex);
6439 #endif /* USE_GREGEX */
6440 }
6441
6442 /* To field "changed" signal callback. */
6443 static void
check_readiness(BalsaSendmsg * bsmsg)6444 check_readiness(BalsaSendmsg * bsmsg)
6445 {
6446 gboolean ready =
6447 libbalsa_address_view_n_addresses(bsmsg->recipient_view) > 0;
6448 #if !defined(ENABLE_TOUCH_UI)
6449 if (ready
6450 && libbalsa_address_view_n_addresses(bsmsg->replyto_view) < 0)
6451 ready = FALSE;
6452 #endif
6453
6454 gtk_action_group_set_sensitive(bsmsg->ready_action_group, ready);
6455 }
6456
6457 static const gchar * const header_action_names[] = {
6458 "From",
6459 "Recipients",
6460 #if !defined(ENABLE_TOUCH_UI)
6461 "ReplyTo",
6462 #endif /* ENABLE_TOUCH_UI */
6463 "Fcc"
6464 };
6465
6466 /* toggle_entry:
6467 auxiliary function for "header show/hide" toggle menu entries.
6468 saves the show header configuration.
6469 */
6470 static void
toggle_entry(GtkToggleAction * toggle_action,BalsaSendmsg * bsmsg,GtkWidget * entry[])6471 toggle_entry(GtkToggleAction * toggle_action, BalsaSendmsg * bsmsg,
6472 GtkWidget * entry[])
6473 {
6474 if (gtk_toggle_action_get_active(toggle_action)) {
6475 gtk_widget_show_all(entry[0]);
6476 gtk_widget_show_all(entry[1]);
6477 gtk_widget_grab_focus(entry[1]);
6478 } else {
6479 gtk_widget_hide(entry[0]);
6480 gtk_widget_hide(entry[1]);
6481 }
6482
6483 if (bsmsg->update_config) { /* then save the config */
6484 GString *str = g_string_new(NULL);
6485 unsigned i;
6486
6487 for (i = 0; i < G_N_ELEMENTS(header_action_names); i++) {
6488 if (sw_get_active(bsmsg, header_action_names[i])) {
6489 if (str->len > 0)
6490 g_string_append_c(str, ' ');
6491 g_string_append(str, header_action_names[i]);
6492 }
6493 }
6494 g_free(balsa_app.compose_headers);
6495 balsa_app.compose_headers = g_string_free(str, FALSE);
6496 }
6497 }
6498
6499 static void
toggle_from_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6500 toggle_from_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6501 {
6502 toggle_entry(action, bsmsg, bsmsg->from);
6503 }
6504
6505 static void
toggle_recipients_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6506 toggle_recipients_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6507 {
6508 toggle_entry(action, bsmsg, bsmsg->recipients);
6509 }
6510
6511 #if !defined(ENABLE_TOUCH_UI)
6512 static void
toggle_replyto_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6513 toggle_replyto_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6514 {
6515 toggle_entry(action, bsmsg, bsmsg->replyto);
6516 }
6517 #endif /* ENABLE_TOUCH_UI */
6518
6519 static void
toggle_fcc_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6520 toggle_fcc_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6521 {
6522 toggle_entry(action, bsmsg, bsmsg->fcc);
6523 }
6524
6525 static void
toggle_reqdispnotify_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6526 toggle_reqdispnotify_cb(GtkToggleAction * action,
6527 BalsaSendmsg * bsmsg)
6528 {
6529 bsmsg->req_dispnotify = gtk_toggle_action_get_active(action);
6530 }
6531
6532 static void
sw_show_toolbar_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6533 sw_show_toolbar_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6534 {
6535 balsa_app.show_compose_toolbar = gtk_toggle_action_get_active(action);
6536 if (balsa_app.show_compose_toolbar)
6537 gtk_widget_show(bsmsg->toolbar);
6538 else
6539 gtk_widget_hide(bsmsg->toolbar);
6540 }
6541
6542 static void
toggle_format_cb(GtkToggleAction * toggle_action,BalsaSendmsg * bsmsg)6543 toggle_format_cb(GtkToggleAction * toggle_action, BalsaSendmsg * bsmsg)
6544 {
6545 bsmsg->flow = gtk_toggle_action_get_active(toggle_action);
6546 sw_set_sensitive(bsmsg, "Reflow", bsmsg->flow);
6547 }
6548
6549 static void
toggle_mp_alt_cb(GtkToggleAction * toggle_action,BalsaSendmsg * bsmsg)6550 toggle_mp_alt_cb(GtkToggleAction * toggle_action, BalsaSendmsg * bsmsg)
6551 {
6552 bsmsg->send_mp_alt = gtk_toggle_action_get_active(toggle_action);
6553 }
6554
6555 #ifdef HAVE_GPGME
6556 static void
toggle_gpg_helper(GtkToggleAction * action,BalsaSendmsg * bsmsg,guint mask)6557 toggle_gpg_helper(GtkToggleAction * action, BalsaSendmsg * bsmsg,
6558 guint mask)
6559 {
6560 gboolean butval, radio_on;
6561
6562 butval = gtk_toggle_action_get_active(action);
6563 if (butval)
6564 bsmsg->gpg_mode |= mask;
6565 else
6566 bsmsg->gpg_mode &= ~mask;
6567
6568 radio_on = (bsmsg->gpg_mode & LIBBALSA_PROTECT_MODE) > 0;
6569 #if !defined(ENABLE_TOUCH_UI)
6570 gtk_action_group_set_sensitive(bsmsg->gpg_action_group, radio_on);
6571 #endif /* ENABLE_TOUCH_UI */
6572 }
6573
6574 static void
toggle_sign_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6575 toggle_sign_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6576 {
6577 toggle_gpg_helper(action, bsmsg, LIBBALSA_PROTECT_SIGN);
6578 }
6579
6580 static void
toggle_encrypt_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6581 toggle_encrypt_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6582 {
6583 toggle_gpg_helper(action, bsmsg, LIBBALSA_PROTECT_ENCRYPT);
6584 }
6585
6586 #if !defined(ENABLE_TOUCH_UI)
6587 static void
gpg_mode_radio_cb(GtkRadioAction * action,GtkRadioAction * current,BalsaSendmsg * bsmsg)6588 gpg_mode_radio_cb(GtkRadioAction * action, GtkRadioAction * current,
6589 BalsaSendmsg * bsmsg)
6590 {
6591 guint rfc_flag = gtk_radio_action_get_current_value(action);
6592
6593 bsmsg->gpg_mode =
6594 (bsmsg->gpg_mode & ~LIBBALSA_PROTECT_PROTOCOL) | rfc_flag;
6595 }
6596 #endif /* ENABLE_TOUCH_UI */
6597 #endif /* HAVE_GPGME */
6598
6599
6600 /* init_menus:
6601 performs the initial menu setup: shown headers as well as correct
6602 message charset.
6603 */
6604 static void
init_menus(BalsaSendmsg * bsmsg)6605 init_menus(BalsaSendmsg * bsmsg)
6606 {
6607 unsigned i;
6608
6609 for (i = 0; i < G_N_ELEMENTS(header_action_names); i++) {
6610 gboolean found =
6611 libbalsa_find_word(header_action_names[i],
6612 balsa_app.compose_headers);
6613 /* This action is initially active, so if found is FALSE,
6614 * setting it inactive will trigger the callback, which will
6615 * hide the address line. */
6616 sw_set_active(bsmsg, header_action_names[i], found);
6617 }
6618
6619 /* gray 'send' and 'postpone' */
6620 check_readiness(bsmsg);
6621 }
6622
6623 /* set_locale:
6624 bsmsg is the compose window,
6625 idx - corresponding entry index in locales.
6626 */
6627
6628 static void
set_locale(BalsaSendmsg * bsmsg,gint idx)6629 set_locale(BalsaSendmsg * bsmsg, gint idx)
6630 {
6631 if (locales[idx].locale && *locales[idx].locale)
6632 bsmsg->spell_check_lang = locales[idx].locale;
6633 #if HAVE_GTKSPELL
6634
6635 if (sw_spell_detach(bsmsg))
6636 sw_spell_attach(bsmsg);
6637 #endif /* HAVE_GTKSPELL */
6638 }
6639
6640 #if HAVE_GTKSPELL
6641 /* spell_check_menu_cb
6642 *
6643 * Toggle the spell checker
6644 */
6645 static void
spell_check_menu_cb(GtkToggleAction * action,BalsaSendmsg * bsmsg)6646 spell_check_menu_cb(GtkToggleAction * action, BalsaSendmsg * bsmsg)
6647 {
6648 if ((balsa_app.spell_check_active =
6649 gtk_toggle_action_get_active(action)))
6650 sw_spell_attach(bsmsg);
6651 else
6652 sw_spell_detach(bsmsg);
6653 }
6654
6655 #else /* HAVE_GTKSPELL */
6656 /* spell_check_cb
6657 *
6658 * Start the spell check
6659 * */
6660 static void
spell_check_cb(GtkAction * action,BalsaSendmsg * bsmsg)6661 spell_check_cb(GtkAction * action, BalsaSendmsg * bsmsg)
6662 {
6663 GtkTextView *text_view = GTK_TEXT_VIEW(bsmsg->text);
6664 BalsaSpellCheck *sc;
6665
6666 if (bsmsg->spell_checker) {
6667 if (gtk_widget_get_window(bsmsg->spell_checker)) {
6668 gtk_window_present(GTK_WINDOW(bsmsg->spell_checker));
6669 return;
6670 } else
6671 /* A spell checker was created, but not shown because of
6672 * errors; we'll destroy it, and create a new one. */
6673 gtk_widget_destroy(bsmsg->spell_checker);
6674 }
6675
6676 sw_buffer_signals_disconnect(bsmsg);
6677
6678 bsmsg->spell_checker = balsa_spell_check_new(GTK_WINDOW(bsmsg->window));
6679 sc = BALSA_SPELL_CHECK(bsmsg->spell_checker);
6680 g_object_add_weak_pointer(G_OBJECT(sc), (gpointer) &bsmsg->spell_checker);
6681
6682 /* configure the spell checker */
6683 balsa_spell_check_set_text(sc, text_view);
6684 balsa_spell_check_set_language(sc, bsmsg->spell_check_lang);
6685
6686 g_signal_connect(G_OBJECT(sc), "response",
6687 G_CALLBACK(sw_spell_check_response), bsmsg);
6688 gtk_text_view_set_editable(text_view, FALSE);
6689
6690 balsa_spell_check_start(sc);
6691 }
6692
6693 static void
sw_spell_check_response(BalsaSpellCheck * spell_check,gint response,BalsaSendmsg * bsmsg)6694 sw_spell_check_response(BalsaSpellCheck * spell_check, gint response,
6695 BalsaSendmsg * bsmsg)
6696 {
6697 gtk_widget_destroy(GTK_WIDGET(spell_check));
6698 bsmsg->spell_checker = NULL;
6699 gtk_text_view_set_editable(GTK_TEXT_VIEW(bsmsg->text), TRUE);
6700 sw_buffer_signals_connect(bsmsg);
6701 }
6702 #endif /* HAVE_GTKSPELL */
6703
6704 static void
lang_set_cb(GtkWidget * w,BalsaSendmsg * bsmsg)6705 lang_set_cb(GtkWidget * w, BalsaSendmsg * bsmsg)
6706 {
6707 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w))) {
6708 gint i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),
6709 BALSA_LANGUAGE_MENU_POS));
6710 set_locale(bsmsg, i);
6711 #if HAVE_GTKSPELL
6712 g_free(balsa_app.spell_check_lang);
6713 balsa_app.spell_check_lang = g_strdup(bsmsg->spell_check_lang);
6714 sw_set_active(bsmsg, "CheckSpelling", TRUE);
6715 #endif /* HAVE_GTKSPELL */
6716 }
6717 }
6718
6719 /* sendmsg_window_new_from_list:
6720 * like sendmsg_window_new, but takes a GList of messages, instead of a
6721 * single message;
6722 * called by compose_from_list (balsa-index.c)
6723 */
6724 BalsaSendmsg *
sendmsg_window_new_from_list(LibBalsaMailbox * mailbox,GArray * selected,SendType type)6725 sendmsg_window_new_from_list(LibBalsaMailbox * mailbox,
6726 GArray * selected, SendType type)
6727 {
6728 BalsaSendmsg *bsmsg;
6729 LibBalsaMessage *message;
6730 GtkTextBuffer *buffer;
6731 guint i;
6732 guint msgno = g_array_index(selected, guint, 0);
6733
6734 g_return_val_if_fail(selected->len > 0, NULL);
6735
6736 message = libbalsa_mailbox_get_message(mailbox, msgno);
6737 if (!message)
6738 return NULL;
6739
6740 switch(type) {
6741 case SEND_FORWARD_ATTACH:
6742 case SEND_FORWARD_INLINE:
6743 bsmsg = sendmsg_window_forward(mailbox, msgno,
6744 type == SEND_FORWARD_ATTACH);
6745 break;
6746 default:
6747 g_assert_not_reached(); /* since it hardly makes sense... */
6748 bsmsg = NULL; /** silence invalid warnings */
6749
6750 }
6751 g_object_unref(message);
6752
6753 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(bsmsg->text));
6754
6755 for (i = 1; i < selected->len; i++) {
6756 LibBalsaMessage *message;
6757
6758 msgno = g_array_index(selected, guint, i);
6759 message = libbalsa_mailbox_get_message(mailbox, msgno);
6760 if (!message)
6761 continue;
6762
6763 if (type == SEND_FORWARD_ATTACH)
6764 attach_message(bsmsg, message);
6765 else if (type == SEND_FORWARD_INLINE) {
6766 GString *body =
6767 quote_message_body(bsmsg, message, QUOTE_NOPREFIX);
6768 gtk_text_buffer_insert_at_cursor(buffer, body->str, body->len);
6769 g_string_free(body, TRUE);
6770 }
6771 g_object_unref(message);
6772 }
6773
6774 bsmsg->state = SENDMSG_STATE_CLEAN;
6775
6776 return bsmsg;
6777 }
6778
6779 /* set_list_post_address:
6780 * look for the address for posting messages to a list */
6781 static void
set_list_post_address(BalsaSendmsg * bsmsg)6782 set_list_post_address(BalsaSendmsg * bsmsg)
6783 {
6784 LibBalsaMessage *message =
6785 bsmsg->parent_message ?
6786 bsmsg->parent_message : bsmsg->draft_message;
6787 InternetAddressList *mailing_list_address;
6788 const gchar *header;
6789
6790 mailing_list_address =
6791 libbalsa_mailbox_get_mailing_list_address(message->mailbox);
6792 if (mailing_list_address) {
6793 libbalsa_address_view_set_from_list(bsmsg->recipient_view, "To:",
6794 mailing_list_address);
6795 g_object_unref(mailing_list_address);
6796 return;
6797 }
6798
6799 if ((header = libbalsa_message_get_user_header(message, "list-post"))
6800 && set_list_post_rfc2369(bsmsg, header))
6801 return;
6802
6803 /* we didn't find "list-post", so try some nonstandard
6804 * alternatives: */
6805
6806 if ((header = libbalsa_message_get_user_header(message, "x-beenthere"))
6807 || (header =
6808 libbalsa_message_get_user_header(message, "x-mailing-list")))
6809 libbalsa_address_view_set_from_string(bsmsg->recipient_view, "To:",
6810 header);
6811 }
6812
6813 /* set_list_post_rfc2369:
6814 * look for "List-Post:" header, and get the address */
6815 static gboolean
set_list_post_rfc2369(BalsaSendmsg * bsmsg,const gchar * url)6816 set_list_post_rfc2369(BalsaSendmsg * bsmsg, const gchar * url)
6817 {
6818 /* RFC 2369: To allow for future extension, client
6819 * applications MUST follow the following guidelines for
6820 * handling the contents of the header fields described in
6821 * this document:
6822 * 1) Except where noted for specific fields, if the content
6823 * of the field (following any leading whitespace,
6824 * including comments) begins with any character other
6825 * than the opening angle bracket '<', the field SHOULD
6826 * be ignored.
6827 * 2) Any characters following an angle bracket enclosed URL
6828 * SHOULD be ignored, unless a comma is the first
6829 * non-whitespace/comment character after the closing
6830 * angle bracket.
6831 * 3) If a sub-item (comma-separated item) within the field
6832 * is not an angle-bracket enclosed URL, the remainder of
6833 * the field (the current, and all subsequent sub-items)
6834 * SHOULD be ignored. */
6835 /* RFC 2369: The client application should use the
6836 * left most protocol that it supports, or knows how to
6837 * access by a separate application. */
6838 while (*(url = rfc2822_skip_comments(url)) == '<') {
6839 const gchar *close = strchr(++url, '>');
6840 if (!close)
6841 /* broken syntax--break and return FALSE */
6842 break;
6843 if (g_ascii_strncasecmp(url, "mailto:", 7) == 0) {
6844 /* we support mailto! */
6845 gchar *field = g_strndup(&url[7], close - &url[7]);
6846 sendmsg_window_process_url(field, sendmsg_window_set_field,
6847 bsmsg);
6848 g_free(field);
6849 return TRUE;
6850 }
6851 if (!(*++close && *(close = rfc2822_skip_comments(close)) == ','))
6852 break;
6853 url = ++close;
6854 }
6855 return FALSE;
6856 }
6857
6858 /* rfc2822_skip_comments:
6859 * skip CFWS (comments and folding white space)
6860 *
6861 * CRLFs have already been stripped, so we need to look only for
6862 * comments and white space
6863 *
6864 * returns a pointer to the first character following the CFWS,
6865 * which may point to a '\0' character but is never a NULL pointer */
6866 static const gchar *
rfc2822_skip_comments(const gchar * str)6867 rfc2822_skip_comments(const gchar * str)
6868 {
6869 gint level = 0;
6870
6871 while (*str) {
6872 if (*str == '(')
6873 /* start of a comment--they nest */
6874 ++level;
6875 else if (level > 0) {
6876 if (*str == ')')
6877 /* end of a comment */
6878 --level;
6879 else if (*str == '\\' && *++str == '\0')
6880 /* quoted-pair: we must test for the end of the string,
6881 * which would be an error; in this case, return a
6882 * pointer to the '\0' character following the '\\' */
6883 break;
6884 } else if (!(*str == ' ' || *str == '\t'))
6885 break;
6886 ++str;
6887 }
6888 return str;
6889 }
6890
6891 /* Set the title for the compose window;
6892 *
6893 * handler for the "changed" signals of the "To:" address and the
6894 * "Subject:" field;
6895 *
6896 * also called directly from sendmsg_window_new.
6897 */
6898 static void
sendmsg_window_set_title(BalsaSendmsg * bsmsg)6899 sendmsg_window_set_title(BalsaSendmsg * bsmsg)
6900 {
6901 gchar *title_format;
6902 InternetAddressList *list;
6903 gchar *to_string;
6904 gchar *title;
6905
6906 switch (bsmsg->type) {
6907 case SEND_REPLY:
6908 case SEND_REPLY_ALL:
6909 case SEND_REPLY_GROUP:
6910 title_format = _("Reply to %s: %s");
6911 break;
6912
6913 case SEND_FORWARD_ATTACH:
6914 case SEND_FORWARD_INLINE:
6915 title_format = _("Forward message to %s: %s");
6916 break;
6917
6918 default:
6919 title_format = _("New message to %s: %s");
6920 break;
6921 }
6922
6923 list = libbalsa_address_view_get_list(bsmsg->recipient_view, "To:");
6924 to_string = internet_address_list_to_string(list, FALSE);
6925 g_object_unref(list);
6926
6927 title = g_strdup_printf(title_format, to_string ? to_string : "",
6928 gtk_entry_get_text(GTK_ENTRY(bsmsg->subject[1])));
6929 g_free(to_string);
6930 gtk_window_set_title(GTK_WINDOW(bsmsg->window), title);
6931 g_free(title);
6932 }
6933
6934 #ifdef HAVE_GPGME
6935 static void
bsmsg_update_gpg_ui_on_ident_change(BalsaSendmsg * bsmsg,LibBalsaIdentity * ident)6936 bsmsg_update_gpg_ui_on_ident_change(BalsaSendmsg * bsmsg,
6937 LibBalsaIdentity * ident)
6938 {
6939 /* do nothing if we don't support crypto */
6940 if (!balsa_app.has_openpgp && !balsa_app.has_smime)
6941 return;
6942
6943 /* preset according to identity */
6944 bsmsg->gpg_mode = 0;
6945 if (ident->always_trust)
6946 bsmsg->gpg_mode |= LIBBALSA_PROTECT_ALWAYS_TRUST;
6947
6948 sw_set_active(bsmsg, "SignMessage", ident->gpg_sign);
6949 if (ident->gpg_sign)
6950 bsmsg->gpg_mode |= LIBBALSA_PROTECT_SIGN;
6951
6952 sw_set_active(bsmsg, "EncryptMessage", ident->gpg_encrypt);
6953 if (ident->gpg_encrypt)
6954 bsmsg->gpg_mode |= LIBBALSA_PROTECT_ENCRYPT;
6955
6956 #if !defined(ENABLE_TOUCH_UI)
6957 switch (ident->crypt_protocol) {
6958 case LIBBALSA_PROTECT_OPENPGP:
6959 bsmsg->gpg_mode |= LIBBALSA_PROTECT_OPENPGP;
6960 sw_set_active(bsmsg, "OldOpenPgpMode", TRUE);
6961 break;
6962 #ifdef HAVE_SMIME
6963 case LIBBALSA_PROTECT_SMIMEV3:
6964 bsmsg->gpg_mode |= LIBBALSA_PROTECT_SMIMEV3;
6965 sw_set_active(bsmsg, "SMimeMode", TRUE);
6966 break;
6967 #endif
6968 case LIBBALSA_PROTECT_RFC3156:
6969 default:
6970 bsmsg->gpg_mode |= LIBBALSA_PROTECT_RFC3156;
6971 sw_set_active(bsmsg, "MimeMode", TRUE);
6972 }
6973 #endif /* ENABLE_TOUCH_UI */
6974 }
6975
6976 static void
bsmsg_setup_gpg_ui(BalsaSendmsg * bsmsg)6977 bsmsg_setup_gpg_ui(BalsaSendmsg *bsmsg)
6978 {
6979 #if !defined(ENABLE_TOUCH_UI)
6980 gtk_action_group_set_sensitive(bsmsg->gpg_action_group, FALSE);
6981 #endif /* ENABLE_TOUCH_UI */
6982
6983 /* make everything insensitive if we don't have crypto support */
6984 if (!balsa_app.has_openpgp && !balsa_app.has_smime) {
6985 sw_set_active(bsmsg, "SignMessage", FALSE);
6986 sw_set_sensitive(bsmsg, "SignMessage", FALSE);
6987 sw_set_active(bsmsg, "EncryptMessage", FALSE);
6988 sw_set_sensitive(bsmsg, "EncryptMessage", FALSE);
6989 }
6990 }
6991
6992 static void
bsmsg_setup_gpg_ui_by_mode(BalsaSendmsg * bsmsg,gint mode)6993 bsmsg_setup_gpg_ui_by_mode(BalsaSendmsg *bsmsg, gint mode)
6994 {
6995 /* do nothing if we don't support crypto */
6996 if (!balsa_app.has_openpgp && !balsa_app.has_smime)
6997 return;
6998
6999 bsmsg->gpg_mode = mode;
7000 sw_set_active(bsmsg, "SignMessage", mode & LIBBALSA_PROTECT_SIGN);
7001 sw_set_active(bsmsg, "EncryptMessage", mode & LIBBALSA_PROTECT_ENCRYPT);
7002
7003 #ifdef HAVE_SMIME
7004 if (mode & LIBBALSA_PROTECT_SMIMEV3)
7005 sw_set_active(bsmsg, "SMimeMode", TRUE);
7006 else
7007 #endif
7008 if (mode & LIBBALSA_PROTECT_OPENPGP)
7009 sw_set_active(bsmsg, "OldOpenPgpMode", TRUE);
7010 else
7011 sw_set_active(bsmsg, "MimeMode", TRUE);
7012 }
7013 #endif /* HAVE_GPGME */
7014
7015 #if defined(ENABLE_TOUCH_UI)
7016 static gboolean
bsmsg_check_format_compatibility(GtkWindow * parent,const gchar * filename)7017 bsmsg_check_format_compatibility(GtkWindow *parent, const gchar *filename)
7018 {
7019 static const struct {
7020 const char *linux_extension, *linux_program;
7021 const char *other_extension, *other_program;
7022 } compatibility_table[] = {
7023 { ".abw", "AbiWord", ".doc", "Microsoft Word" },
7024 { ".gnumeric", "Gnumeric", ".xls", "Microsoft Excel" }
7025 };
7026 GtkDialog *dialog;
7027 GtkBox *vbox;
7028 GtkWidget *label, *checkbox = NULL;
7029 unsigned i, fn_len = strlen(filename);
7030 int response;
7031 gchar *str;
7032
7033 if(!balsa_app.do_file_format_check)
7034 return TRUE; /* blank accept from the User */
7035
7036 for(i=0; i<ELEMENTS(compatibility_table); i++) {
7037 unsigned le_len = strlen(compatibility_table[i].linux_extension);
7038 int offset = fn_len - le_len;
7039
7040 if(offset>0 &&
7041 strcmp(filename+offset, compatibility_table[i].linux_extension)==0)
7042 break; /* a match has been found */
7043 }
7044 if(i>=ELEMENTS(compatibility_table))
7045 return TRUE; /* no potential compatibility problems */
7046
7047 /* time to ask the user for his/her opinion */
7048 dialog = (GtkDialog*)gtk_dialog_new_with_buttons
7049 ("Compatibility check", parent,
7050 GTK_DIALOG_MODAL| GTK_DIALOG_DESTROY_WITH_PARENT,
7051 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
7052 "_Attach it in the current format", GTK_RESPONSE_OK, NULL);
7053 #if HAVE_MACOSX_DESKTOP
7054 libbalsa_macosx_menu_for_parent(GTK_WIDGET(dialog), parent);
7055 #endif
7056
7057 gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
7058 str = g_strdup_printf
7059 ("File %s is currently in the %s's own format.\n"
7060 "If you need to send it to people who use %s, "
7061 "then open the file in the %s, use \"Save As\" "
7062 "on the \"File\" menu, and select the \"%s\" format. "
7063 "When you click \"OK\" it will save a new "
7064 "\"%s\" version of the file, with \"%s\" on the end "
7065 "of the document name, which you can then attach instead.",
7066 filename,
7067 compatibility_table[i].linux_program,
7068 compatibility_table[i].other_program,
7069 compatibility_table[i].linux_program,
7070 compatibility_table[i].other_program,
7071 compatibility_table[i].other_program,
7072 compatibility_table[i].other_extension);
7073 vbox = GTK_BOX(gtk_dialog_get_content_area(dialog));
7074 gtk_box_set_spacing(vbox, 10);
7075 gtk_box_pack_start(vbox, label = gtk_label_new(str),
7076 TRUE, TRUE, 0);
7077 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
7078 g_free(str);
7079 checkbox = gtk_check_button_new_with_mnemonic
7080 ("_Do not show this dialog any more.");
7081 gtk_box_pack_start(vbox, checkbox, TRUE, TRUE, 0);
7082 gtk_widget_show(checkbox);
7083 gtk_widget_show(label);
7084 response = gtk_dialog_run(dialog);
7085 balsa_app.do_file_format_check =
7086 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox));
7087 gtk_widget_destroy(GTK_WIDGET(dialog));
7088 return response == GTK_RESPONSE_OK;
7089 }
7090 #endif /* ENABLE_TOUCH_UI */
7091