em-composer-utils.c 83.6 KB
Newer Older
1
2
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

Not Zed's avatar
Not Zed committed
3
/*
4
5
6
7
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
Not Zed's avatar
Not Zed committed
8
 *
9
10
11
12
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
Not Zed's avatar
Not Zed committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
Not Zed's avatar
Not Zed committed
16
17
 *
 *
18
19
20
21
 * Authors:
 *		Jeffrey Stedfast <fejj@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
Not Zed's avatar
Not Zed committed
22
23
24
25
26
27
28
29
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
30
#include <gtk/gtk.h>
31
#include <glib/gi18n.h>
32

33
34
35
36
#include <e-util/e-util.h>

#include <libemail-engine/e-mail-folder-utils.h>
#include <libemail-engine/e-mail-session-utils.h>
37
#include <libemail-engine/e-mail-session.h>
38
#include <libemail-engine/e-mail-utils.h>
39
#include <libemail-engine/mail-mt.h>
40
41
#include <libemail-engine/mail-ops.h>
#include <libemail-engine/mail-tools.h>
Not Zed's avatar
Not Zed committed
42

Dan Vrátil's avatar
Dan Vrátil committed
43
44
45
#include <em-format/e-mail-parser.h>
#include <em-format/e-mail-formatter-quote.h>

46
#include <shell/e-shell.h>
47

48
49
50
#include <composer/e-msg-composer.h>
#include <composer/e-composer-actions.h>
#include <composer/e-composer-post-header.h>
Not Zed's avatar
Not Zed committed
51

52
#include "e-mail-printer.h"
Not Zed's avatar
Not Zed committed
53
54
#include "em-utils.h"
#include "em-composer-utils.h"
55
56
#include "em-folder-selector.h"
#include "em-folder-tree.h"
57
#include "em-event.h"
58
59
#include "mail-send-recv.h"

60
#ifdef G_OS_WIN32
61
#ifdef gmtime_r
62
#undef gmtime_r
63
#endif
64
65
66
67
68

/* The gmtime() in Microsoft's C library is MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#endif

69
typedef struct _AsyncContext AsyncContext;
70
typedef struct _ForwardData ForwardData;
71

72
73
struct _AsyncContext {
	CamelMimeMessage *message;
74
	EMailSession *session;
75
76
	EMsgComposer *composer;
	EActivity *activity;
77
78
79
	EMailReader *reader;
	GPtrArray *ptr_array;
	EMailForwardStyle style;
80
81
	gchar *folder_uri;
	gchar *message_uid;
82
	gboolean replace;
83
	GtkWidget *destroy_when_done;
Not Zed's avatar
Not Zed committed
84
85
};

86
87
88
89
90
91
92
struct _ForwardData {
	EShell *shell;
	CamelFolder *folder;
	GPtrArray *uids;
	EMailForwardStyle style;
};

Not Zed's avatar
Not Zed committed
93
static void
94
async_context_free (AsyncContext *context)
Not Zed's avatar
Not Zed committed
95
{
96
97
	if (context->message != NULL)
		g_object_unref (context->message);
98

99
100
101
	if (context->session != NULL)
		g_object_unref (context->session);

102
103
	if (context->composer != NULL)
		g_object_unref (context->composer);
104

105
106
	if (context->activity != NULL)
		g_object_unref (context->activity);
Not Zed's avatar
Not Zed committed
107

108
109
110
111
112
113
	if (context->reader != NULL)
		g_object_unref (context->reader);

	if (context->ptr_array != NULL)
		g_ptr_array_unref (context->ptr_array);

114
115
116
	if (context->destroy_when_done != NULL)
		gtk_widget_destroy (context->destroy_when_done);

117
118
	g_free (context->folder_uri);
	g_free (context->message_uid);
Not Zed's avatar
Not Zed committed
119

120
	g_slice_free (AsyncContext, context);
Not Zed's avatar
Not Zed committed
121
122
}

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
static void
forward_data_free (ForwardData *data)
{
	if (data->shell != NULL)
		g_object_unref (data->shell);

	if (data->folder != NULL)
		g_object_unref (data->folder);

	if (data->uids != NULL)
		em_utils_uids_free (data->uids);

	g_slice_free (ForwardData, data);
}

Not Zed's avatar
Not Zed committed
138
static gboolean
139
140
ask_confirm_for_unwanted_html_mail (EMsgComposer *composer,
                                    EDestination **recipients)
Not Zed's avatar
Not Zed committed
141
{
Not Zed's avatar
Not Zed committed
142
	gboolean res;
Not Zed's avatar
Not Zed committed
143
	GString *str;
144
	gint i;
145

146
	str = g_string_new ("");
Not Zed's avatar
Not Zed committed
147
	for (i = 0; recipients[i] != NULL; ++i) {
148
		if (!e_destination_get_html_mail_pref (recipients[i])) {
149
			const gchar *name;
150

151
			name = e_destination_get_textrep (recipients[i], FALSE);
152

Not Zed's avatar
Not Zed committed
153
154
155
			g_string_append_printf (str, "     %s\n", name);
		}
	}
Not Zed's avatar
Not Zed committed
156

157
	if (str->len)
158
159
		res = em_utils_prompt_user (
			GTK_WINDOW (composer),
160
			"prompt-on-unwanted-html",
161
			"mail:ask-send-html", str->str, NULL);
162
163
164
	else
		res = TRUE;

Matthew Barnes's avatar
Matthew Barnes committed
165
	g_string_free (str, TRUE);
166

Not Zed's avatar
Not Zed committed
167
168
169
170
171
172
	return res;
}

static gboolean
ask_confirm_for_empty_subject (EMsgComposer *composer)
{
173
174
	return em_utils_prompt_user (
		GTK_WINDOW (composer),
175
		"prompt-on-empty-subject",
176
		"mail:ask-send-no-subject", NULL);
Not Zed's avatar
Not Zed committed
177
178
179
}

static gboolean
180
181
ask_confirm_for_only_bcc (EMsgComposer *composer,
                          gboolean hidden_list_case)
Not Zed's avatar
Not Zed committed
182
183
{
	/* If the user is mailing a hidden contact list, it is possible for
184
185
186
187
	 * them to create a message with only Bcc recipients without really
	 * realizing it.  To try to avoid being totally confusing, I've changed
	 * this dialog to provide slightly different text in that case, to
	 * better explain what the hell is going on. */
188

189
190
	return em_utils_prompt_user (
		GTK_WINDOW (composer),
191
		"prompt-on-only-bcc",
192
193
194
		hidden_list_case ?
		"mail:ask-send-only-bcc-contact" :
		"mail:ask-send-only-bcc", NULL);
Not Zed's avatar
Not Zed committed
195
}
Not Zed's avatar
Not Zed committed
196

197
198
199
200
201
202
203
204
205
206
207
208
static gboolean
is_group_definition (const gchar *str)
{
	const gchar *colon;

	if (!str || !*str)
		return FALSE;

	colon = strchr (str, ':');
	return colon > str && strchr (str, ';') > colon;
}

209
static gboolean
210
211
composer_presend_check_recipients (EMsgComposer *composer,
                                   EMailSession *session)
Not Zed's avatar
Not Zed committed
212
{
213
214
	EDestination **recipients;
	EDestination **recipients_bcc;
Not Zed's avatar
Not Zed committed
215
	CamelInternetAddress *cia;
216
	EComposerHeaderTable *table;
217
218
	EComposerHeader *post_to_header;
	GString *invalid_addrs = NULL;
219
220
221
222
223
224
225
	gboolean check_passed = FALSE;
	gint hidden = 0;
	gint shown = 0;
	gint num = 0;
	gint num_bcc = 0;
	gint num_post = 0;
	gint ii;
226

227
228
229
	/* We should do all of the validity checks based on the composer,
	 * and not on the created message, as extra interaction may occur
	 * when we get the message (e.g. passphrase to sign a message). */
230

231
	table = e_msg_composer_get_header_table (composer);
232
	recipients = e_composer_header_table_get_destinations (table);
233

Not Zed's avatar
Not Zed committed
234
	cia = camel_internet_address_new ();
235

236
237
238
239
	/* See which ones are visible, present, etc. */
	for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) {
		const gchar *addr;
		gint len, j;
240

241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
		addr = e_destination_get_address (recipients[ii]);
		if (addr == NULL || *addr == '\0')
			continue;

		camel_address_decode (CAMEL_ADDRESS (cia), addr);
		len = camel_address_length (CAMEL_ADDRESS (cia));

		if (len > 0) {
			if (!e_destination_is_evolution_list (recipients[ii])) {
				for (j = 0; j < len; j++) {
					const gchar *name = NULL, *eml = NULL;

					if (!camel_internet_address_get (cia, j, &name, &eml) ||
					    !eml ||
					    strchr (eml, '@') <= eml) {
						if (!invalid_addrs)
							invalid_addrs = g_string_new ("");
						else
							g_string_append (invalid_addrs, ", ");

						if (name)
							g_string_append (invalid_addrs, name);
						if (eml) {
							g_string_append (invalid_addrs, name ? " <" : "");
							g_string_append (invalid_addrs, eml);
							g_string_append (invalid_addrs, name ? ">" : "");
						}
Not Zed's avatar
Not Zed committed
268
269
270
					}
				}
			}
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288

			camel_address_remove (CAMEL_ADDRESS (cia), -1);
			num++;
			if (e_destination_is_evolution_list (recipients[ii])
			    && !e_destination_list_show_addresses (recipients[ii])) {
				hidden++;
			} else {
				shown++;
			}
		} else if (is_group_definition (addr)) {
			/* like an address, it will not claim on only-bcc */
			shown++;
			num++;
		} else if (!invalid_addrs) {
			invalid_addrs = g_string_new (addr);
		} else {
			g_string_append (invalid_addrs, ", ");
			g_string_append (invalid_addrs, addr);
Not Zed's avatar
Not Zed committed
289
290
		}
	}
291

292
	recipients_bcc = e_composer_header_table_get_destinations_bcc (table);
Not Zed's avatar
Not Zed committed
293
	if (recipients_bcc) {
294
295
296
297
298
299
300
301
302
303
304
		for (ii = 0; recipients_bcc[ii] != NULL; ii++) {
			const gchar *addr;

			addr = e_destination_get_address (recipients_bcc[ii]);
			if (addr == NULL || *addr == '\0')
				continue;

			camel_address_decode (CAMEL_ADDRESS (cia), addr);
			if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) {
				camel_address_remove (CAMEL_ADDRESS (cia), -1);
				num_bcc++;
Not Zed's avatar
Not Zed committed
305
306
			}
		}
307

308
		e_destination_freev (recipients_bcc);
Not Zed's avatar
Not Zed committed
309
	}
310

Matthew Barnes's avatar
Matthew Barnes committed
311
	g_object_unref (cia);
Not Zed's avatar
Not Zed committed
312

313
314
	post_to_header = e_composer_header_table_get_header (
		table, E_COMPOSER_HEADER_POST_TO);
315
316
317
318
319
	if (e_composer_header_get_visible (post_to_header)) {
		GList *postlist;

		postlist = e_composer_header_table_get_post_to (table);
		num_post = g_list_length (postlist);
320
		g_list_foreach (postlist, (GFunc) g_free, NULL);
321
322
		g_list_free (postlist);
	}
323

Not Zed's avatar
Not Zed committed
324
	/* I'm sensing a lack of love, er, I mean recipients. */
Not Zed's avatar
Not Zed committed
325
	if (num == 0 && num_post == 0) {
326
		e_alert_submit (
327
			E_ALERT_SINK (composer),
328
			"mail:send-no-recipients", NULL);
Not Zed's avatar
Not Zed committed
329
		goto finished;
Not Zed's avatar
Not Zed committed
330
	}
331

332
	if (invalid_addrs) {
333
		if (!em_utils_prompt_user (
334
			GTK_WINDOW (composer),
335
			"prompt-on-invalid-recip",
336
337
338
			strstr (invalid_addrs->str, ", ") ?
				"mail:ask-send-invalid-recip-multi" :
				"mail:ask-send-invalid-recip-one",
339
			invalid_addrs->str, NULL)) {
340
341
342
343
344
345
346
			g_string_free (invalid_addrs, TRUE);
			goto finished;
		}

		g_string_free (invalid_addrs, TRUE);
	}

Not Zed's avatar
Not Zed committed
347
	if (num > 0 && (num == num_bcc || shown == 0)) {
348
		/* this means that the only recipients are Bcc's */
Not Zed's avatar
Not Zed committed
349
350
351
		if (!ask_confirm_for_only_bcc (composer, shown == 0))
			goto finished;
	}
352

353
	check_passed = TRUE;
354

355
356
357
finished:
	if (recipients != NULL)
		e_destination_freev (recipients);
358

359
360
	return check_passed;
}
361

362
static gboolean
363
364
composer_presend_check_identity (EMsgComposer *composer,
                                 EMailSession *session)
365
366
{
	EComposerHeaderTable *table;
367
368
369
370
	ESourceRegistry *registry;
	ESource *source;
	const gchar *uid;
	gboolean success = TRUE;
371

372
	table = e_msg_composer_get_header_table (composer);
373
374
375
376
	registry = e_composer_header_table_get_registry (table);
	uid = e_composer_header_table_get_identity_uid (table);
	source = e_source_registry_ref_source (registry, uid);
	g_return_val_if_fail (source != NULL, FALSE);
377

378
	if (!e_source_registry_check_enabled (registry, source)) {
379
		e_alert_submit (
380
			E_ALERT_SINK (composer),
381
			"mail:send-no-account-enabled", NULL);
382
383
		success = FALSE;
	}
384

385
386
387
	g_object_unref (source);

	return success;
388
389
390
}

static gboolean
391
392
composer_presend_check_downloads (EMsgComposer *composer,
                                  EMailSession *session)
393
394
395
396
397
398
399
400
401
402
403
404
{
	EAttachmentView *view;
	EAttachmentStore *store;
	gboolean check_passed = TRUE;

	view = e_msg_composer_get_attachment_view (composer);
	store = e_attachment_view_get_store (view);

	if (e_attachment_store_get_num_loading (store) > 0) {
		if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL,
		    "mail-composer:ask-send-message-pending-download", NULL))
			check_passed = FALSE;
Not Zed's avatar
Not Zed committed
405
	}
406

407
408
409
410
	return check_passed;
}

static gboolean
411
412
composer_presend_check_plugins (EMsgComposer *composer,
                                EMailSession *session)
413
414
415
416
417
{
	EMEvent *eme;
	EMEventTargetComposer *target;
	gpointer data;

418
419
420
	/** @Event: composer.presendchecks
	 * @Title: Composer PreSend Checks
	 * @Target: EMEventTargetMessage
421
	 *
422
423
424
425
	 * composer.presendchecks is emitted during pre-checks for the
	 * message just before sending.  Since the e-plugin framework
	 * doesn't provide a way to return a value from the plugin,
	 * use 'presend_check_status' to set whether the check passed.
426
	 */
Matthew Barnes's avatar
Matthew Barnes committed
427
	eme = em_event_peek ();
428
429
	target = em_event_target_new_composer (eme, composer, 0);

430
431
432
	e_event_emit (
		(EEvent *) eme, "composer.presendchecks",
		(EEventTarget *) target);
433

434
435
	/* A non-NULL value for this key means the check failed. */
	data = g_object_get_data (G_OBJECT (composer), "presend_check_status");
436

437
438
439
	/* Clear the value in case we have to run these checks again. */
	g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL);

440
441
442
443
	return (data == NULL);
}

static gboolean
444
445
composer_presend_check_subject (EMsgComposer *composer,
                                EMailSession *session)
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
{
	EComposerHeaderTable *table;
	const gchar *subject;
	gboolean check_passed = TRUE;

	table = e_msg_composer_get_header_table (composer);
	subject = e_composer_header_table_get_subject (table);

	if (subject == NULL || subject[0] == '\0') {
		if (!ask_confirm_for_empty_subject (composer))
			check_passed = FALSE;
	}

	return check_passed;
}

static gboolean
463
464
composer_presend_check_unwanted_html (EMsgComposer *composer,
                                      EMailSession *session)
465
466
467
{
	EDestination **recipients;
	EComposerHeaderTable *table;
468
	GSettings *settings;
469
470
471
472
473
474
	gboolean check_passed = TRUE;
	gboolean html_mode;
	gboolean send_html;
	gboolean confirm_html;
	gint ii;

475
	settings = g_settings_new ("org.gnome.evolution.mail");
476
477
478
479
480

	table = e_msg_composer_get_header_table (composer);
	recipients = e_composer_header_table_get_destinations (table);
	html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer));

481
482
	send_html = g_settings_get_boolean (settings, "composer-send-html");
	confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505

	/* Only show this warning if our default is to send html.  If it
	 * isn't, we've manually switched into html mode in the composer
	 * and (presumably) had a good reason for doing this. */
	if (html_mode && send_html && confirm_html && recipients != NULL) {
		gboolean html_problem = FALSE;

		for (ii = 0; recipients[ii] != NULL; ii++) {
			if (!e_destination_get_html_mail_pref (recipients[ii]))
				html_problem = TRUE;
				break;
		}

		if (html_problem) {
			if (!ask_confirm_for_unwanted_html_mail (
				composer, recipients))
				check_passed = FALSE;
		}
	}

	if (recipients != NULL)
		e_destination_freev (recipients);

506
	g_object_unref (settings);
507
508
509
510
511
512
513
514
515
516

	return check_passed;
}

static void
composer_send_completed (EMailSession *session,
                         GAsyncResult *result,
                         AsyncContext *context)
{
	GError *error = NULL;
517
	gboolean set_changed = FALSE;
518
519

	e_mail_session_send_to_finish (session, result, &error);
Matthew Barnes's avatar
Matthew Barnes committed
520

521
	if (e_activity_handle_cancellation (context->activity, error)) {
Matthew Barnes's avatar
Matthew Barnes committed
522
		g_error_free (error);
523
		set_changed = TRUE;
524
		goto exit;
Matthew Barnes's avatar
Matthew Barnes committed
525
526
	}

527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
	/* Post-processing errors are shown in the shell window. */
	if (g_error_matches (error, E_MAIL_ERROR, E_MAIL_ERROR_POST_PROCESSING)) {
		EAlert *alert;
		EShell *shell;

		shell = e_msg_composer_get_shell (context->composer);

		alert = e_alert_new (
			"mail-composer:send-post-processing-error",
			error->message, NULL);
		e_shell_submit_alert (shell, alert);
		g_object_unref (alert);

	/* All other errors are shown in the composer window. */
	} else if (error != NULL) {
542
543
544
545
546
547
548
549
550
		gint response;

		/* Clear the activity bar before
		 * presenting the error dialog. */
		g_object_unref (context->activity);
		context->activity = NULL;

		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (context->composer),
551
			"mail-composer:send-error",
Matthew Barnes's avatar
Matthew Barnes committed
552
			error->message, NULL);
553
554
555
556
		if (response == GTK_RESPONSE_OK)  /* Try Again */
			e_msg_composer_send (context->composer);
		if (response == GTK_RESPONSE_ACCEPT)  /* Save to Outbox */
			e_msg_composer_save_to_outbox (context->composer);
Matthew Barnes's avatar
Matthew Barnes committed
557
		g_error_free (error);
558
		set_changed = TRUE;
559
		goto exit;
Not Zed's avatar
Not Zed committed
560
	}
561

Matthew Barnes's avatar
Matthew Barnes committed
562
	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
563

564
565
566
567
568
	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (context->activity), (GWeakNotify)
		gtk_widget_destroy, context->composer);
569

570
exit:
571
	if (set_changed) {
572
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
573
574
		gtk_window_present (GTK_WINDOW (context->composer));
	}
575

576
	async_context_free (context);
Not Zed's avatar
Not Zed committed
577
578
}

579
580
581
static void
em_utils_composer_send_cb (EMsgComposer *composer,
                           CamelMimeMessage *message,
582
583
                           EActivity *activity,
                           EMailSession *session)
584
585
586
587
588
589
590
591
592
593
594
{
	AsyncContext *context;
	GCancellable *cancellable;

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);

595
	e_mail_session_send_to (
596
		session, message,
597
598
		G_PRIORITY_DEFAULT, cancellable, NULL, NULL,
		(GAsyncReadyCallback) composer_send_completed,
599
600
		context);
}
Not Zed's avatar
Not Zed committed
601

602
static void
603
composer_set_no_change (EMsgComposer *composer)
604
605
{
	GtkhtmlEditor *editor;
606

607
608
609
610
	g_return_if_fail (composer != NULL);

	editor = GTKHTML_EDITOR (composer);

611
612
	gtkhtml_editor_drop_undo (editor);
	gtkhtml_editor_set_changed (editor, FALSE);
613
614
}

615
616
/* delete original messages from Outbox folder */
static void
617
manage_x_evolution_replace_outbox (EMsgComposer *composer,
618
                                   EMailSession *session,
619
620
                                   CamelMimeMessage *message,
                                   GCancellable *cancellable)
621
622
{
	const gchar *message_uid;
623
	const gchar *header;
624
625
626
627
628
	CamelFolder *outbox;

	g_return_if_fail (composer != NULL);
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

629
630
631
	header = "X-Evolution-Replace-Outbox-UID";
	message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header);
	e_msg_composer_remove_header (composer, header);
632
633
634
635

	if (!message_uid)
		return;

636
637
	outbox = e_mail_session_get_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX);
638
639
640
641
642
643
644
645
	g_return_if_fail (outbox != NULL);

	camel_folder_set_message_flags (
		outbox, message_uid,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);

	/* ignore errors here */
646
647
	camel_folder_synchronize_message_sync (
		outbox, message_uid, cancellable, NULL);
648
649
}

Not Zed's avatar
Not Zed committed
650
static void
651
652
653
composer_save_to_drafts_complete (EMailSession *session,
                                  GAsyncResult *result,
                                  AsyncContext *context)
Not Zed's avatar
Not Zed committed
654
{
655
	GError *error = NULL;
656

657
658
659
	/* We don't really care if this failed.  If something other than
	 * cancellation happened, emit a runtime warning so the error is
	 * not completely lost. */
Not Zed's avatar
Not Zed committed
660

661
	e_mail_session_handle_draft_headers_finish (session, result, &error);
662

663
	if (e_activity_handle_cancellation (context->activity, error)) {
664
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
665
		g_error_free (error);
666

Matthew Barnes's avatar
Matthew Barnes committed
667
	} else if (error != NULL) {
668
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
669
670
		g_warning ("%s", error->message);
		g_error_free (error);
Matthew Barnes's avatar
Matthew Barnes committed
671
672
673

	} else
		e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
674

675
676
677
678
679
680
681
	/* Encode the draft message we just saved into the EMsgComposer
	 * as X-Evolution-Draft headers.  The message will be marked for
	 * deletion if the user saves a newer draft message or sends the
	 * composed message. */
	e_msg_composer_set_draft_headers (
		context->composer, context->folder_uri,
		context->message_uid);
682

683
	async_context_free (context);
Not Zed's avatar
Not Zed committed
684
685
686
}

static void
687
688
689
composer_save_to_drafts_cleanup (CamelFolder *drafts_folder,
                                 GAsyncResult *result,
                                 AsyncContext *context)
Not Zed's avatar
Not Zed committed
690
{
691
	CamelSession *session;
692
	EAlertSink *alert_sink;
693
694
695
	GCancellable *cancellable;
	GError *error = NULL;

696
697
698
699
	session = e_msg_composer_get_session (context->composer);
	alert_sink = e_activity_get_alert_sink (context->activity);
	cancellable = e_activity_get_cancellable (context->activity);

700
701
702
	e_mail_folder_append_message_finish (
		drafts_folder, result, &context->message_uid, &error);

703
	if (e_activity_handle_cancellation (context->activity, error)) {
704
		g_warn_if_fail (context->message_uid == NULL);
705
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
706
707
708
		async_context_free (context);
		g_error_free (error);
		return;
709

710
	} else if (error != NULL) {
711
712
		g_warn_if_fail (context->message_uid == NULL);
		e_alert_submit (
713
			alert_sink,
714
			"mail-composer:save-to-drafts-error",
715
			error->message, NULL);
716
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
717
718
719
		async_context_free (context);
		g_error_free (error);
		return;
Not Zed's avatar
Not Zed committed
720
	}
721
722
723
724
725
726

	/* Mark the previously saved draft message for deletion.
	 * Note: This is just a nice-to-have; ignore failures. */
	e_mail_session_handle_draft_headers (
		E_MAIL_SESSION (session), context->message,
		G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
727
		composer_save_to_drafts_complete, context);
Not Zed's avatar
Not Zed committed
728
729
}

730
static void
731
732
composer_save_to_drafts_append_mail (AsyncContext *context,
                                     CamelFolder *drafts_folder)
Not Zed's avatar
Not Zed committed
733
{
734
	CamelFolder *local_drafts_folder;
735
	GCancellable *cancellable;
Not Zed's avatar
Not Zed committed
736
	CamelMessageInfo *info;
737

738
	local_drafts_folder =
739
740
		e_mail_session_get_local_folder (
		context->session, E_MAIL_LOCAL_FOLDER_DRAFTS);
741

742
743
744
745
746
747
748
749
750
751
	if (drafts_folder == NULL)
		drafts_folder = g_object_ref (local_drafts_folder);

	cancellable = e_activity_get_cancellable (context->activity);

	info = camel_message_info_new (NULL);

	camel_message_info_set_flags (
		info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0);

752
753
754
	camel_medium_remove_header (
		CAMEL_MEDIUM (context->message),
		"X-Evolution-Replace-Outbox-UID");
755

756
757
758
	e_mail_folder_append_message (
		drafts_folder, context->message,
		info, G_PRIORITY_DEFAULT, cancellable,
759
		(GAsyncReadyCallback) composer_save_to_drafts_cleanup,
760
761
762
763
764
765
766
767
		context);

	camel_message_info_free (info);

	g_object_unref (drafts_folder);
}

static void
768
769
770
composer_save_to_drafts_got_folder (EMailSession *session,
                                    GAsyncResult *result,
                                    AsyncContext *context)
771
772
773
774
775
776
{
	CamelFolder *drafts_folder;
	GError *error = NULL;

	drafts_folder = e_mail_session_uri_to_folder_finish (
		session, result, &error);
Matthew Barnes's avatar
Matthew Barnes committed
777

778
	if (e_activity_handle_cancellation (context->activity, error)) {
779
		g_warn_if_fail (drafts_folder == NULL);
780
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
781
		async_context_free (context);
Matthew Barnes's avatar
Matthew Barnes committed
782
783
784
		g_error_free (error);
		return;

785
	} else if (error != NULL) {
786
787
788
789
790
		gint response;

		g_warn_if_fail (drafts_folder == NULL);

		/* XXX Not showing the error message in the dialog? */
Matthew Barnes's avatar
Matthew Barnes committed
791
		g_error_free (error);
792
793
794
795
796
797
798
799

		/* If we can't retrieve the Drafts folder for the
		 * selected account, ask the user if he wants to
		 * save to the local Drafts folder instead. */
		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (context->composer),
			"mail:ask-default-drafts", NULL);
		if (response != GTK_RESPONSE_YES) {
800
			gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
801
802
803
			async_context_free (context);
			return;
		}
Matthew Barnes's avatar
Matthew Barnes committed
804
805
	}

806
	composer_save_to_drafts_append_mail (context, drafts_folder);
807
808
809
}

static void
810
811
em_utils_composer_save_to_drafts_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
812
813
                                     EActivity *activity,
                                     EMailSession *session)
814
815
816
{
	AsyncContext *context;
	EComposerHeaderTable *table;
817
818
	ESourceRegistry *registry;
	ESource *source;
819
	const gchar *local_drafts_folder_uri;
820
821
	const gchar *identity_uid;
	gchar *drafts_folder_uri = NULL;
822
823
824

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
825
	context->session = g_object_ref (session);
826
827
828
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

829
	table = e_msg_composer_get_header_table (composer);
Not Zed's avatar
Not Zed committed
830

831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
	registry = e_composer_header_table_get_registry (table);
	identity_uid = e_composer_header_table_get_identity_uid (table);
	source = e_source_registry_ref_source (registry, identity_uid);

	/* Get the selected identity's preferred Drafts folder. */
	if (source != NULL) {
		ESourceMailComposition *extension;
		const gchar *extension_name;
		gchar *uri;

		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
		extension = e_source_get_extension (source, extension_name);
		uri = e_source_mail_composition_dup_drafts_folder (extension);

		drafts_folder_uri = uri;
Not Zed's avatar
Not Zed committed
846

847
848
849
850
851
852
		g_object_unref (source);
	}

	local_drafts_folder_uri =
		e_mail_session_get_local_folder_uri (
		session, E_MAIL_LOCAL_FOLDER_DRAFTS);
853

854
	if (drafts_folder_uri == NULL) {
855
		composer_save_to_drafts_append_mail (context, NULL);
856
		context->folder_uri = g_strdup (local_drafts_folder_uri);
Not Zed's avatar
Not Zed committed
857
	} else {
858
		GCancellable *cancellable;
Matthew Barnes's avatar
Matthew Barnes committed
859

860
861
		cancellable = e_activity_get_cancellable (activity);
		context->folder_uri = g_strdup (drafts_folder_uri);
Not Zed's avatar
Not Zed committed
862

863
		e_mail_session_uri_to_folder (
864
865
866
			session, drafts_folder_uri, 0,
			G_PRIORITY_DEFAULT, cancellable,
			(GAsyncReadyCallback)
867
			composer_save_to_drafts_got_folder, context);
868
869

		g_free (drafts_folder_uri);
870
	}
Not Zed's avatar
Not Zed committed
871
872
}

873
static void
874
composer_save_to_outbox_completed (EMailSession *session,
875
876
877
                                   GAsyncResult *result,
                                   AsyncContext *context)
{
878
	EAlertSink *alert_sink;
879
880
	GError *error = NULL;

881
882
	alert_sink = e_activity_get_alert_sink (context->activity);

883
884
	e_mail_session_append_to_local_folder_finish (
		session, result, NULL, &error);
885

886
	if (e_activity_handle_cancellation (context->activity, error)) {
887
888
889
		g_error_free (error);
		goto exit;

890
	} else if (error != NULL) {
891
		e_alert_submit (
892
			alert_sink,
893
894
895
896
897
898
			"mail-composer:append-to-outbox-error",
			error->message, NULL);
		g_error_free (error);
		goto exit;
	}

899
	/* special processing for Outbox folder */
900
	manage_x_evolution_replace_outbox (
901
		context->composer, session, context->message,
902
		e_activity_get_cancellable (context->activity));
903

904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (context->activity), (GWeakNotify)
		gtk_widget_destroy, context->composer);

exit:
	async_context_free (context);
}

static void
em_utils_composer_save_to_outbox_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
919
920
                                     EActivity *activity,
                                     EMailSession *session)
921
922
923
924
925
926
927
928
929
930
931
{
	AsyncContext *context;
	CamelMessageInfo *info;
	GCancellable *cancellable;

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);
932

933
934
935
	info = camel_message_info_new (NULL);
	camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);

936
937
938
	e_mail_session_append_to_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX,
		message, info, G_PRIORITY_DEFAULT, cancellable,
939
940
941
942
943
944
		(GAsyncReadyCallback) composer_save_to_outbox_completed,
		context);

	camel_message_info_free (info);
}

945
946
947
948
949
950
951
952
953
static void
composer_print_done_cb (EMailPrinter *emp,
                        GtkPrintOperation *operation,
                        GtkPrintOperationResult result,
                        gpointer user_data)
{
	g_object_unref (emp);
}

954
955
static void
em_utils_composer_print_cb (EMsgComposer *composer,
956
957
                            GtkPrintOperationAction action,
                            CamelMimeMessage *message,
958
959
                            EActivity *activity,
                            EMailSession *session)
960
{
961
	EMailPrinter *emp;
Dan Vrátil's avatar
Dan Vrátil committed
962
963
	EMailParser *parser;
	EMailPartList *parts;
964
	const gchar *message_id;
965

Dan Vrátil's avatar
Dan Vrátil committed
966
	parser = e_mail_parser_new (CAMEL_SESSION (session));
967
968

	message_id = camel_mime_message_get_message_id (message);
Dan Vrátil's avatar
Dan Vrátil committed
969
	parts = e_mail_parser_parse_sync (parser, NULL, g_strdup (message_id), message, NULL);
970
971

        /* Use EMailPrinter and WebKit to print the message */
Dan Vrátil's avatar
Dan Vrátil committed
972
	emp = e_mail_printer_new (parts);
973
974
	g_signal_connect (
		emp, "done",
Dan Vrátil's avatar
Dan Vrátil committed
975
		G_CALLBACK (composer_print_done_cb), NULL);
976

977
	e_mail_printer_print (emp, action, NULL, NULL);
Dan Vrátil's avatar
Dan Vrátil committed
978
979

	g_object_unref (parts);
980
981
}

Not Zed's avatar
Not Zed committed
982
983
984
/* Composing messages... */

static EMsgComposer *
985
986
create_new_composer (EShell *shell,
                     const gchar *subject,
987
                     CamelFolder *folder)
Not Zed's avatar
Not Zed committed
988
989
{
	EMsgComposer *composer;
990
	ESourceRegistry *registry;
991
	EComposerHeaderTable *table;
992
993
	ESource *source = NULL;
	gchar *identity = NULL;
994

995
	composer = e_msg_composer_new (shell);
996

997
	table = e_msg_composer_get_header_table (composer);
998
	registry = e_composer_header_table_get_registry (table);
999

1000
1001
1002
	if (folder != NULL) {
		CamelStore *store;
		gchar *folder_uri;
1003
		GList *list;
1004

1005
		store = camel_folder_get_parent_store (folder);
1006
		source = em_utils_ref_mail_identity_for_store (registry, store);
1007
1008

		folder_uri = e_mail_folder_uri_from_folder (folder);
1009

1010
		list = g_list_prepend (NULL, folder_uri);
1011
1012
		e_composer_header_table_set_post_to_list (table, list);
		g_list_free (list);
1013
1014

		g_free (folder_uri);
1015
	}
1016

1017
1018
1019
1020
1021
	if (source != NULL) {
		identity = e_source_dup_uid (source);
		g_object_unref (source);
	}

1022
	e_composer_header_table_set_subject (table, subject);
1023
1024
1025
	e_composer_header_table_set_identity_uid (table, identity);

	g_free (identity);
1026

Not Zed's avatar
Not Zed committed
1027
1028
1029
1030
1031
	return composer;
}

/**
 * em_utils_compose_new_message:
1032
 * @shell: an #EShell
1033
 * @folder: a #CamelFolder, or %NULL
Not Zed's avatar
Not Zed committed
1034
1035
1036
1037
1038
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window.
 **/
void
1039
em_utils_compose_new_message (EShell *shell,
1040
                              CamelFolder *folder)
Not Zed's avatar
Not Zed committed
1041
{
1042
	EMsgComposer *composer;
1043

1044
1045
	g_return_if_fail (E_IS_SHELL (shell));

1046
1047
	if (folder != NULL)