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