1 # include <iostream>
2 # include <vector>
3 # include <algorithm>
4 # include <random>
5 # include <ctime>
6 # include <memory>
7 
8 # include <gtkmm.h>
9 
10 # include <boost/filesystem.hpp>
11 
12 # include "astroid.hh"
13 # include "config.hh"
14 # include "account_manager.hh"
15 # include "edit_message.hh"
16 # include "compose_message.hh"
17 # include "db.hh"
18 # include "thread_view/thread_view.hh"
19 # include "raw_message.hh"
20 # include "main_window.hh"
21 # include "message_thread.hh"
22 # include "chunk.hh"
23 # include "utils/utils.hh"
24 # include "utils/ustring_utils.hh"
25 # include "utils/resource.hh"
26 # include "utils/gmime/gmime-compat.h"
27 # include "actions/onmessage.hh"
28 
29 # include "editor/plugin.hh"
30 # include "editor/external.hh"
31 
32 using namespace std;
33 using namespace boost::filesystem;
34 
35 namespace Astroid {
36   int EditMessage::edit_id = 0;
37 
EditMessage(MainWindow * mw,ustring _to,ustring _from,ustring _cc,ustring _bcc,ustring _subject,ustring _body)38   EditMessage::EditMessage (MainWindow * mw, ustring _to, ustring _from, ustring _cc, ustring _bcc, ustring _subject, ustring _body) :
39     EditMessage (mw, false) { // {{{
40 
41     in_read = false;
42     to  = _to;
43     cc  = _cc;
44     bcc = _bcc;
45     subject = _subject;
46     body = _body;
47     if (!_from.empty ()) {
48       set_from (Address (_from));
49     }
50 
51     /* reload message */
52     prepare_message ();
53     read_edited_message ();
54 
55     edit_when_ready ();
56   }
57 
EditMessage(MainWindow * mw,refptr<Message> msg)58   EditMessage::EditMessage (MainWindow * mw, refptr<Message> msg) :
59     EditMessage (mw, false) {
60     /* load draft */
61     LOG (info) << "em: loading draft from: " << msg->fname;
62 
63     /* if this is a previously saved draft: use it, otherwise make new draft
64      * based on original message */
65 
66     if (any_of (Db::draft_tags.begin (),
67                 Db::draft_tags.end (),
68                 [&](ustring t) {
69                   return has (msg->tags, t);
70                 }))
71     {
72       draft_msg = msg;
73       msg_id = msg->mid;
74     }
75 
76     for (auto &c : msg->mime_messages_and_attachments ()) {
77       add_attachment (new ComposeMessage::Attachment (c));
78     }
79 
80     inreplyto = msg->inreplyto;
81     references = msg->references;
82 
83     /* write msg to new tmpfile */
84     msg->save_to (tmpfile_path.c_str ());
85 
86     /* reload message */
87     read_edited_message ();
88 
89     /* TODO: read encryption / signing / signature state from message properties */
90 
91     edit_when_ready ();
92   }
93 
EditMessage(MainWindow * mw,bool _edit_when_ready)94   EditMessage::EditMessage (MainWindow * mw, bool _edit_when_ready) : Mode (mw) {
95     editor_config = astroid->config ("editor");
96     in_read = false;
97 
98 # ifndef DISABLE_EMBEDDED
99     embed_editor = !editor_config.get<bool> ("external_editor");
100 # endif
101     save_draft_on_force_quit = editor_config.get <bool> ("save_draft_on_force_quit");
102 
103     ustring attachment_words_s = editor_config.get<string> ("attachment_words");
104     attachment_words = VectorUtils::split_and_trim (attachment_words_s.lowercase (), ",");
105     sort (attachment_words.begin (), attachment_words.end ());
106 
107     tmpfile_path = astroid->standard_paths ().runtime_dir;
108 
109     gpgenabled = astroid->config ("crypto").get<bool> ("gpg.enabled");
110 
111     set_label ("New message");
112 
113     path ui = Resource (false, "ui/edit-message.glade").get_path ();
114 
115     Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file(ui.c_str(), "box_message");
116 
117     builder->get_widget ("box_message", box_message);
118 
119     builder->get_widget ("from_combo", from_combo);
120     builder->get_widget ("switch_encrypt", switch_encrypt);
121     builder->get_widget ("switch_sign", switch_sign);
122     builder->get_widget ("reply_mode_combo", reply_mode_combo);
123     builder->get_widget ("fields_revealer", fields_revealer);
124     builder->get_widget ("reply_revealer", reply_revealer);
125     builder->get_widget ("encryption_revealer", encryption_revealer);
126 
127     builder->get_widget ("editor_box", editor_box);
128     builder->get_widget ("switch_signature", switch_signature);
129     builder->get_widget ("switch_markdown", switch_markdown);
130     /*
131     builder->get_widget ("editor_rev", editor_rev);
132     builder->get_widget ("thread_rev", thread_rev);
133     */
134 
135     pack_start (*box_message, true, 5);
136 
137     /* set up message id and random server name for editor */
138     id = edit_id++;
139 
140     ustring _mid = "";
141     msg_time = time(0);
142 
143 # ifndef DISABLE_PLUGINS
144     if (!astroid->plugin_manager->astroid_extension->generate_mid (_mid)) {
145 # endif
146 
147       char _hostname[1024];
148       _hostname[1023] = 0;
149       gethostname (_hostname, 1023);
150 
151       char _domainname[1024];
152       _domainname[1023] = 0;
153       if (getdomainname (_domainname, 1023) < 0) {
154         *_domainname = '\0';
155       }
156 
157       ustring hostname = astroid->config ().get <string> ("mail.message_id_fqdn");
158       UstringUtils::trim (hostname);
159       if (hostname.empty ()) {
160         if (*_hostname != 0) {
161           hostname = _hostname;
162 
163           if (*_domainname != 0) {
164             ustring d (_domainname);
165             d = UstringUtils::replace (d, "(", ""); // often (none) is returned
166             d = UstringUtils::replace (d, ")", "");
167             hostname += ".";
168             hostname += d;
169           }
170 
171           if (hostname.find (".", 0) == std::string::npos) {
172             /* add a top level domain */
173             hostname += ".none";
174           }
175         } else {
176           hostname = UstringUtils::random_alphanumeric (10) + ".none";
177         }
178       }
179 
180 
181       ustring user = astroid->config ().get<string> ("mail.message_id_user");
182       UstringUtils::trim (user);
183       if (user.empty ()) user = "astroid";
184 
185       _mid = UstringUtils::random_alphanumeric (10);
186 
187       _mid = ustring::compose ("%1.%2.%3@%4", msg_time, _mid, user, hostname);
188 
189 # ifndef DISABLE_PLUGINS
190     }
191 # endif
192 
193     if (msg_id == "") {
194       msg_id = _mid;
195     }
196 
197     LOG (info) << "em: msg id: " << msg_id;
198 
199     make_tmpfile ();
200 
201 # ifndef DISABLE_EMBEDDED
202     if (embed_editor) {
203       editor = new Plugin (this, _mid);
204 
205       //editor_rev->add (*editor_socket);
206       editor_box->pack_start (dynamic_cast<Plugin *> (editor)->bin, false, false, 2);
207 
208     } else {
209 # endif
210       editor = new External (this);
211 # ifndef DISABLE_EMBEDDED
212     }
213 # endif
214 
215     thread_view = Gtk::manage(new ThreadView(main_window, true));
216     //thread_rev->add (*thread_view);
217     editor_box->pack_start (*thread_view, false, false, 2);
218 
219     thread_view->signal_ready().connect (
220         sigc::mem_fun (this, &EditMessage::on_tv_ready));
221 
222     thread_view->signal_element_action().connect (
223         sigc::mem_fun (this, &EditMessage::on_element_action));
224 
225     /* defaults */
226     accounts = astroid->accounts;
227 
228     /* from combobox */
229     from_store = Gtk::ListStore::create (from_columns);
230     from_combo->set_model (from_store);
231 
232     account_no = 0;
233     for (Account &a : accounts->accounts) {
234       auto row = *(from_store->append ());
235       row[from_columns.name_and_address] = a.full_address();
236       row[from_columns.account] = &a;
237 
238       if (a.isdefault) {
239         from_combo->set_active (account_no);
240       }
241 
242       account_no++;
243     }
244 
245     from_combo->pack_start (from_columns.name_and_address);
246     /*
247     from_combo->signal_key_press_event ().connect (
248         sigc::mem_fun (this, &EditMessage::on_from_combo_key_press));
249         */
250 
251 
252 
253     show_all_children ();
254 
255     prepare_message ();
256 
257     sending_in_progress.store (false);
258 
259     if (!embed_editor) {
260       thread_view->show ();
261       gtk_box_set_child_packing (editor_box->gobj (), GTK_WIDGET(thread_view->gobj ()), true, true, 5, GTK_PACK_START);
262       grab_modal ();
263       thread_view->grab_focus ();
264     }
265 
266     editor_toggle (false);
267 
268     from_combo->signal_changed().connect (
269         sigc::mem_fun (this, &EditMessage::on_from_combo_changed));
270 
271     /* editor->start_editor_when_ready = true; */
272 
273     switch_signature->property_active().signal_changed ().connect (
274         sigc::mem_fun (*this, &EditMessage::switch_signature_set));
275 
276     switch_markdown->property_active().signal_changed ().connect (
277         sigc::mem_fun (*this, &EditMessage::switch_signature_set));
278 
279     switch_encrypt->property_active().signal_changed ().connect (
280         sigc::mem_fun (*this, &EditMessage::switch_signature_set));
281 
282     switch_sign->property_active().signal_changed ().connect (
283         sigc::mem_fun (*this, &EditMessage::switch_signature_set));
284 
285     reset_signature ();
286 
287     switch_markdown->set_active (astroid->config ().get<bool> ("editor.markdown_on"));
288 
289     /* register keys {{{ */
290     keys.title = "Edit mode";
291     keys.register_key (Key (GDK_KEY_Return), { Key (GDK_KEY_KP_Enter) },
292         "edit_message.edit",
293         "Edit message in editor",
294         [&] (Key) {
295           if (!editor_active && !message_sent && !sending_in_progress.load()) {
296             editor_toggle (true);
297           }
298           return true;
299         });
300 
301     keys.register_key ("y", "edit_message.send",
302         "Send message",
303         [&] (Key) {
304           if (!message_sent && !sending_in_progress.load()) {
305             if (editor_active) {
306               set_warning ("Cannot send message when editing.");
307 
308               return true;
309             }
310 
311             if (!check_fields ()) {
312               /* warning str is set in check_fields () */
313               LOG (error) << "em: error problem with some of the input fields..";
314 
315               return true;
316             }
317 
318             ask_yes_no ("Really send message?", [&](bool yes){ if (yes) send_message (); });
319           }
320           return true;
321         });
322 
323     keys.register_key ("C-c", "edit_message.cancel",
324         "Cancel sending message (unreliable)",
325         [&] (Key) {
326           if (!message_sent && sending_in_progress.load ()) {
327             sending_message->cancel_sending ();
328 
329             /* send_message_finished will still be called to clean up sending_message */
330           }
331 
332           return true;
333         });
334 
335     keys.register_key ("V", "edit_message.view_raw",
336         "View raw message",
337         [&] (Key) {
338           /* view raw source of to be sent message */
339           auto c = make_message ();
340 
341           GMimeStream * m = g_mime_stream_mem_new ();
342           assert (g_mime_stream_mem_get_owner (GMIME_STREAM_MEM(m)) == true);
343           c->write (m);
344 
345           main_window->add_mode (new RawMessage (main_window, refptr<Message>(new UnprocessedMessage(m))));
346 
347           g_object_unref (m);
348 
349           return true;
350         });
351 
352     keys.register_key ("f", "edit_message.cycle_from",
353         "Cycle through From selector",
354         [&] (Key) {
355 
356           if (editor_active) {
357             set_warning ("Cannot change from address when editing.");
358 
359             return true;
360           }
361 
362           /* cycle through from combo box */
363           if (!message_sent && !sending_in_progress.load()) {
364             if (from_store->children().size() > 1) {
365               int i = from_combo->get_active_row_number ();
366               if (i >= (static_cast<int>(from_store->children().size())-1)) i = 0;
367               else i++;
368               from_combo->set_active (i);
369               reset_signature ();
370             }
371           }
372 
373           return true;
374         });
375 
376     keys.register_key ("a", "edit_message.attach",
377         "Attach file",
378         [&] (Key) {
379           attach_file ();
380           return true;
381         });
382 
383     keys.register_key ("A", "edit_messsage.attach_mids",
384         "Attach messages by mids",
385         [&] (Key) {
386           main_window->enable_command (CommandBar::CommandMode::AttachMids,
387               "Attach mids:", "", [&] (ustring mids)
388               {
389                 auto midsv = VectorUtils::split_and_trim (mids, ",");
390                 if (!midsv.empty ()) {
391                   Db db;
392 
393                   for (auto mid : midsv) {
394                     db.on_message (mid, [&] (notmuch_message_t * msg) {
395                         if (msg != NULL) {
396                           LOG (debug) << "em: attaching: " << mid;
397 
398                           refptr<Message> mmsg  = refptr<Message> (new UnprocessedMessage (msg, 0));
399                           add_attachment (new ComposeMessage::Attachment (mmsg));
400 
401                         } else {
402                           LOG (warn) << "em: could not find and attach mid: " << mid;
403                         }
404                         });
405                   }
406                   prepare_message ();
407                   read_edited_message ();
408                 }
409               });
410 
411           return true;
412         });
413 
414     keys.register_key ("s", "edit_message.save_draft",
415         "Save draft",
416         [&] (Key) {
417 
418           if (editor_active) {
419             set_warning ("Cannot save draft when editing.");
420 
421             return true;
422           }
423 
424           if (sending_in_progress.load ()) {
425             /* block closing the window while sending */
426             LOG (error) << "em: message is being sent, it cannot be saved as draft anymore.";
427           } else {
428 
429             bool r;
430 
431             r = save_draft ();
432 
433             if (!r) {
434               on_tv_ready ();
435             } else {
436               close (true);
437             }
438           }
439           return true;
440         });
441 
442     keys.register_key ("D", "edit_message.delete_draft",
443         "Delete draft",
444         [&] (Key) {
445           if (!draft_msg) {
446             LOG (debug) << "em: not a draft.";
447             return true;
448           }
449 
450           if (sending_in_progress.load ()) {
451             /* block closing the window while sending */
452             LOG (error) << "em: message is being sent, cannot delete draft now. it will be deleted upon successfully sent message.";
453           } else if (!message_sent) {
454             ask_yes_no ("Do you want to delete this draft and close it? (any changes will be lost)",
455                 [&](bool yes) {
456                   if (yes) {
457                     delete_draft ();
458                     close (true);
459                   }
460                 });
461           } else {
462             // message has been sent successfully, no need to complain.
463             close ();
464           }
465           return true;
466         });
467 
468 
469     keys.register_key ("S", "edit_message.toggle_signature",
470         "Toggle signature",
471         [&] (Key) {
472           auto iter = from_combo->get_active ();
473           auto row = *iter;
474           Account * a = row[from_columns.account];
475           if (a->has_signature) {
476             switch_signature->set_active (!switch_signature->get_active ());
477           }
478           return true;
479         });
480 
481     keys.register_key ("M", "edit_message.toggle_markdown",
482         "Toggle markdown",
483         [&] (Key) {
484             switch_markdown->set_active (!switch_markdown->get_active ());
485 
486             return true;
487         });
488 
489     keys.register_key ("E", "edit_message.toggle_encrypt",
490         "Toggle encryption and signature",
491         [&] (Key) {
492 
493           auto iter = from_combo->get_active ();
494           if (!iter) {
495             return true;
496           }
497 
498           auto row = *iter;
499           Account * a = row[from_columns.account];
500 
501           if (gpgenabled && a->has_gpg) {
502             if (!switch_encrypt->get_active () && !switch_sign->get_active ()) {
503               switch_encrypt->set_active (false);
504               switch_sign->set_active (true);
505             } else if (!switch_encrypt->get_active() && switch_sign->get_active ()) {
506               switch_encrypt->set_active (true);
507               switch_sign->set_active (true);
508             } else if (switch_encrypt->get_active () && switch_sign->get_active ()) {
509               switch_encrypt->set_active (true);
510               switch_sign->set_active (false);
511             } else  {
512               switch_encrypt->set_active (false);
513               switch_sign->set_active (false);
514             }
515           }
516 
517           return true;
518         });
519 
520     // }}}
521 
522     if (_edit_when_ready) edit_when_ready ();
523   } // }}}
524 
edit_when_ready()525   void EditMessage::edit_when_ready () {
526     if (!embed_editor) {
527        editor_toggle (true);
528     } else {
529       if (!editor->ready ()) editor->start_editor_when_ready = true;
530       else editor_toggle (true);
531     }
532   }
533 
~EditMessage()534   EditMessage::~EditMessage () {
535     LOG (debug) << "em: deconstruct.";
536 
537     if (status_icon_visible) {
538       main_window->notebook.remove_widget (&message_sending_status_icon);
539     }
540 
541     if (editor->started ()) {
542       editor->stop ();
543     }
544 
545     if (is_regular_file (tmpfile_path)) {
546       boost::filesystem::remove (tmpfile_path);
547     }
548   }
549 
pre_close()550   void EditMessage::pre_close () {
551     ((Mode*) thread_view)->pre_close ();
552   }
553 
close(bool force)554   void EditMessage::close (bool force) {
555     if (sending_in_progress.load ()) {
556       /* block closing the window while sending */
557     } else if (!force && !message_sent && !draft_saved) {
558 
559       if (editor->started ()) {
560         set_warning ("Cannot close when editing.");
561 
562         return;
563       }
564 
565       ask_yes_no ("Do you want to close this message? (unsaved changes will be lost)", [&](bool yes){ if (yes) { Mode::close (force); } });
566 
567     } else if (force && !message_sent && !draft_saved && save_draft_on_force_quit) {
568 
569       LOG (warn) << "em: force quit, trying to save draft..";
570       bool r = save_draft ();
571       if (!r) {
572         LOG (error) << "em: cannot save draft! check account config. changes will be lost.";
573       }
574       Mode::close (force);
575 
576     } else {
577       // message has been sent successfully, no need to complain.
578       Mode::close (force);
579     }
580   }
581 
582   /* drafts */
save_draft()583   bool EditMessage::save_draft () {
584     LOG (info) << "em: saving draft..";
585     draft_saved = false;
586     auto c = make_draft_message ();
587     ustring fname;
588 
589     bool add_to_notmuch = false;
590 
591     if (!draft_msg) {
592       /* make new message */
593 
594       path ddir = c->account->save_drafts_to;
595       if (!is_directory(ddir)) {
596         LOG (error) << "em: no draft directory specified!";
597         set_warning ("draft could not be saved, no suitable draft directory for account specified.");
598         return false;
599 
600       } else {
601         /* msg_id might come from external client or server */
602         ddir = ddir / path(Utils::safe_fname (msg_id));
603         fname = ddir.c_str ();
604 
605         add_to_notmuch = true;
606       }
607     } else {
608       fname = draft_msg->fname; // overwrite
609     }
610 
611     c->write (fname);
612 
613     LOG (info) << "em: saved draft to: " << fname;
614 
615     if (add_to_notmuch) {
616       astroid->actions->doit (refptr<Action> (
617             new AddDraftMessage (fname)));
618     }
619 
620     draft_saved = true;
621     return true;
622   }
623 
delete_draft()624   void EditMessage::delete_draft () {
625     if (draft_msg) {
626 
627       delete_draft (draft_msg);
628 
629       draft_msg = refptr<Message> ();
630       draft_saved = true; /* avoid saving on exit */
631 
632     } else {
633       LOG (warn) << "em: not a draft, not deleting.";
634     }
635   }
636 
delete_draft(refptr<Message> draft_msg)637   void EditMessage::delete_draft (refptr<Message> draft_msg) {
638     LOG (info) << "em: deleting draft.";
639     path fname = path(draft_msg->fname.c_str());
640 
641     if (is_regular_file (fname)) {
642       boost::filesystem::remove (fname);
643 
644       /* first remove tag in case it has been sent */
645       astroid->actions->doit (refptr<Action>(
646             new OnMessageAction (draft_msg->mid, draft_msg->tid,
647 
648               [fname] (Db * db, notmuch_message_t * msg) {
649                 for (ustring t : Db::draft_tags) {
650                   notmuch_message_remove_tag (msg,
651                       t.c_str ());
652                 }
653 
654                 bool persists = !db->remove_message (fname.c_str ());
655 
656                 if (persists && db->maildir_synchronize_flags) {
657                   /* sync in case there are other copies of the message */
658                   notmuch_message_tags_to_maildir_flags (msg);
659                 }
660               })));
661     }
662 
663   }
664 
665   /* edit / read message cycling {{{ */
on_from_combo_changed()666   void EditMessage::on_from_combo_changed () {
667     /* this will be called when the From: field has been changed
668      * manually in the e-mail as well. this check prevents the
669      * message currently being read from the edited draft to be
670      * overwritten before it is read. */
671     if (!in_read) {
672       prepare_message ();
673       read_edited_message ();
674     }
675   }
676 
prepare_message()677   void EditMessage::prepare_message () {
678     LOG (debug) << "em: preparing message from fields..";
679 
680     if (in_read) {
681       LOG (error) << "em: preparing message while in read";
682       throw std::logic_error ("em: preparing message while in read");
683     }
684 
685     auto iter = from_combo->get_active ();
686     if (!iter) {
687       LOG (warn) << "em: error: no from account selected.";
688       return;
689     }
690 
691     auto row = *iter;
692     Account * a = row[from_columns.account];
693     auto from_ia = internet_address_mailbox_new (a->name.c_str(), a->email.c_str());
694     ustring from = internet_address_to_string (from_ia, NULL, true);
695 
696     tmpfile.open (tmpfile_path.c_str(), std::fstream::out);
697     tmpfile << "From: " << from << endl;
698     tmpfile << "To: " << to << endl;
699     tmpfile << "Cc: " << cc << endl;
700     if (bcc.size() > 0) {
701       tmpfile << "Bcc: " << bcc << endl;
702     }
703     tmpfile << "Subject: " << subject << endl;
704     tmpfile << endl;
705     tmpfile << body.raw();
706 
707     tmpfile.close ();
708     LOG (debug) << "em: prepare message done.";
709   }
710 
set_from(Address a)711   bool EditMessage::set_from (Address a) {
712     if (!accounts->is_me (a)) {
713       LOG (error) << "em: from address is not a defined account.";
714     }
715 
716     return set_from (accounts->get_account_for_address (a));
717   }
718 
set_from(Account * a)719   bool EditMessage::set_from (Account * a) {
720     int rn = from_combo->get_active_row_number ();
721 
722     account_no = 0;
723     for (Gtk::TreeRow row : from_store->children ()) {
724       if (row[from_columns.account] == a) {
725         from_combo->set_active (row);
726         break;
727       }
728       account_no ++;
729     }
730 
731     bool same_account = (rn == from_combo->get_active_row_number ());
732     LOG (debug) << "same account: " << same_account;
733     if (!same_account) {
734       reset_signature ();
735     }
736 
737     return same_account;
738   }
739 
reset_signature()740   void EditMessage::reset_signature () {
741     /* should not be run unless the account has been changed */
742     auto it = from_combo->get_active ();
743     Account * a = (*it)[from_columns.account];
744 
745     switch_signature->set_sensitive (a->has_signature);
746     switch_signature->set_active (a->has_signature && a->signature_default_on);
747 
748     encryption_revealer->set_reveal_child (gpgenabled && a->has_gpg);
749     switch_sign->set_sensitive (gpgenabled && a->has_gpg);
750     switch_encrypt->set_sensitive (gpgenabled && a->has_gpg);
751 
752     switch_encrypt->set_active (false);
753     switch_sign->set_active (gpgenabled && a->has_gpg && a->always_gpg_sign);
754   }
755 
switch_signature_set()756   void EditMessage::switch_signature_set () {
757     LOG (debug) << "got sig: " << switch_signature->get_active ();
758     if (!in_read) {
759       prepare_message ();
760       read_edited_message ();
761     }
762   }
763 
read_edited_message()764   void EditMessage::read_edited_message () {
765     LOG (debug) << "em: reading edited message..";
766     std::lock_guard<std::mutex> lk (message_draft_m);
767 
768     if (in_read) {
769       LOG (error) << "em: read while already reading!";
770       throw std::logic_error ("read while already reading");
771     }
772 
773     in_read = true;
774 
775     draft_saved = false; // we expect changes to have been made
776     set_warning ("");
777 
778     if (!editor_active) {
779       if (astroid->hint_level () < 1) {
780         set_info ("Edit message with 'Enter'.");
781       } else {
782         set_info ("");
783       }
784     }
785 
786     /* make message */
787     auto c = setup_message ();
788 
789     /* set account selector to from address email */
790     set_from (c->account);
791 
792     /* build message */
793     finalize_message (c);
794 
795     if (c->markdown && !c->markdown_success) {
796       set_warning ("Failed processing markdown: " + UstringUtils::replace (c->markdown_error, "\n", "<br />"));
797     }
798 
799     if (c->encrypt || c->sign) {
800       if (!c->encryption_success) {
801         set_warning ("Failed encrypting: " + UstringUtils::replace (c->encryption_error, "\n", "<br />"));
802       }
803     }
804 
805     to = c->to;
806     cc = c->cc;
807     bcc = c->bcc;
808     references = c->references;
809     inreplyto = c->inreplyto;
810     subject = c->subject;
811     body = ustring(c->body.str());
812 
813     GMimeStream * m = g_mime_stream_mem_new ();
814     assert (g_mime_stream_mem_get_owner (GMIME_STREAM_MEM(m)) == true);
815     c->write (m);
816 
817     auto msgt = refptr<MessageThread>(new MessageThread());
818     msgt->add_message (refptr<Message>(new UnprocessedMessage(m)));
819     thread_view->load_message_thread (msgt);
820 
821     g_object_unref (m);
822 
823     in_read = false;
824   }
825 
826   /* }}} */
827 
set_info(ustring msg)828   void EditMessage::set_info (ustring msg) {
829     info_str = msg;
830     LOG (debug) << "em: set info (ready: " << thread_view->ready << "): " << msg;
831     if (thread_view->ready) {
832       if (info_str.length () > 0) {
833         thread_view->set_info (thread_view->focused_message, info_str);
834       } else {
835         thread_view->hide_info (thread_view->focused_message);
836       }
837     }
838   }
839 
set_warning(ustring msg)840   void EditMessage::set_warning (ustring msg) {
841     warning_str = msg;
842     LOG (debug) << "em: set warning (ready: " << thread_view->ready << "): " << msg;
843     if (thread_view->ready) {
844       if (warning_str.length () > 0) {
845         thread_view->set_warning (thread_view->focused_message, warning_str);
846       } else {
847         thread_view->hide_warning (thread_view->focused_message);
848       }
849     }
850   }
851 
on_tv_ready()852   void EditMessage::on_tv_ready () {
853     LOG (debug) << "em: got tv ready.";
854 
855     set_info (info_str);
856     set_warning (warning_str);
857   }
858 
859   /* swapping between edit and read mode {{{ */
fields_hide()860   void EditMessage::fields_hide () {
861     fields_revealer->set_reveal_child (false);
862   }
863 
fields_show()864   void EditMessage::fields_show () {
865    fields_revealer->set_reveal_child (true);
866   }
867 
868   /* turn on or off the editor or set up for the editor
869    * only run for embedded editor */
editor_toggle(bool on)870   void EditMessage::editor_toggle (bool on) {
871     LOG (debug) << "em: editor toggle: " << on;
872 
873 # ifndef DISABLE_EMBEDDED
874     if (embed_editor) {
875       if (on) {
876         prepare_message ();
877 
878         editor_active = true;
879 
880         /*
881         editor_rev->set_reveal_child (true);
882         thread_rev->set_reveal_child (false);
883         */
884 
885         dynamic_cast<Plugin *> (editor)->bin.show ();
886         thread_view->hide ();
887 
888         gtk_box_set_child_packing (editor_box->gobj (), GTK_WIDGET(dynamic_cast<Plugin *> (editor)->bin.gobj ()), true, true, 5, GTK_PACK_START);
889         gtk_box_set_child_packing (editor_box->gobj (), GTK_WIDGET(thread_view->gobj ()), false, false, 5, GTK_PACK_START);
890 
891         /* future Gtk
892         editor_box->set_child_packing (editor_rev, true, true, 2);
893         editor_box->set_child_packing (thread_rev, false, false, 2);
894         */
895 
896         fields_hide ();
897 
898         editor->start ();
899 
900       } else {
901         if (editor->started ()) {
902           editor->stop ();
903         }
904 
905         /*
906         editor_rev->set_reveal_child (false);
907         thread_rev->set_reveal_child (true);
908         */
909         dynamic_cast<Plugin *> (editor)->bin.hide ();
910         thread_view->show ();
911 
912         gtk_box_set_child_packing (editor_box->gobj (), GTK_WIDGET(dynamic_cast<Plugin *>(editor)->bin.gobj ()), false, false, 5, GTK_PACK_START);
913         gtk_box_set_child_packing (editor_box->gobj (), GTK_WIDGET(thread_view->gobj ()), true, true, 5, GTK_PACK_START);
914 
915         /* future Gtk
916         editor_box->set_child_packing (editor_rev, false, false, 2);
917         editor_box->set_child_packing (thread_rev, true, true, 2);
918         */
919 
920         fields_show ();
921 
922         if (editor_active)
923           read_edited_message ();
924 
925         editor_active = false;
926 
927         grab_modal ();
928         thread_view->grab_focus ();
929       }
930 
931     } else {
932 # endif
933       if (on && !editor->started ()) {
934         /* start editor */
935         editor_active = true;
936 
937         prepare_message ();
938         read_edited_message ();
939 
940         editor->start ();
941 
942         info_str = "Editing..";
943 
944       } else {
945         /* return from editor */
946         set_info ("");
947 
948         if (editor_active) {
949           read_edited_message ();
950         }
951 
952         editor_active = false;
953       }
954 # ifndef DISABLE_EMBEDDED
955     }
956 # endif
957   }
958 
activate_editor()959   void EditMessage::activate_editor () {
960     /* used by Plugin */
961     LOG (debug) << "em: activate editor.";
962 
963     editor_active = true;
964 
965     LOG (debug) << "em: activate editor.";
966 
967     if (!editor->ready ()) {
968       LOG (warn) << "em: activate editor: not ready.";
969       return;
970     }
971 
972     /*
973      * https://bugzilla.gnome.org/show_bug.cgi?id=729248
974      */
975 
976     release_modal ();
977 
978     if (editor->ready ()) {
979 
980       editor->focus ();
981 
982     } else {
983 
984       LOG (warn) << "em: activate editor, editor not yet started!";
985 
986     }
987   }
988 
989   /* }}} */
990 
on_element_action(int id,ThreadView::ElementAction action)991   void EditMessage::on_element_action (int id, ThreadView::ElementAction action) {
992 
993     if (sending_in_progress.load ()) return;
994 
995     if (action == ThreadView::ElementAction::EDelete) {
996 
997       /* delete attachment */
998       auto e = thread_view->state[thread_view->focused_message].elements[id];
999 
1000       if (e.type == ThreadView::MessageState::ElementType::Attachment ||
1001           e.type == ThreadView::MessageState::ElementType::MimeMessage )
1002       {
1003         LOG (info) << "em: remove attachment: " << id << ", cid: " << e.id;
1004 
1005         /* find attachment */
1006         unsigned int attachment = 0;
1007 
1008         for (unsigned int i = 0;
1009             i < thread_view->state[thread_view->focused_message].elements.size ();
1010             i++)
1011         {
1012           auto xe = thread_view->state[thread_view->focused_message].elements[i];
1013           if (xe.type == ThreadView::MessageState::ElementType::Attachment ||
1014               xe.type == ThreadView::MessageState::ElementType::MimeMessage )
1015           {
1016 
1017             if (i == (unsigned int) id) {
1018               attachments.erase (attachments.begin () + attachment);
1019               break;
1020             }
1021 
1022             attachment++;
1023           }
1024         }
1025 
1026         prepare_message ();
1027         read_edited_message ();
1028       }
1029     }
1030   }
1031 
1032   /* }}} */
1033 
1034   /* send message {{{ */
check_fields()1035   bool EditMessage::check_fields () {
1036     if (to.empty () && cc.empty () && bcc.empty ()) {
1037       set_warning ("No recipients defined!");
1038 
1039       on_tv_ready ();
1040 
1041       return false;
1042     }
1043 
1044     /* check if attachments are mentioned */
1045     if (attachments.empty () && !attachment_words.empty ()) {
1046       ustring bl = body.lowercase ();
1047 
1048       /* strip quoted lines */
1049       ostringstream nonquoted;
1050       stringstream sstr (bl);
1051       while (sstr.good()) {
1052         string line;
1053         getline (sstr, line);
1054 
1055         if (line[0] != '>')
1056           nonquoted << line << endl;
1057       }
1058       ustring nqb = ustring(nonquoted.str());
1059 
1060       if (any_of (attachment_words.begin (),
1061                   attachment_words.end (),
1062                   [&] (ustring w) {
1063                     return nqb.find (w) != string::npos;
1064                   }))
1065       {
1066         set_warning ("Attachments have been mentioned in the message, but none are attached, do you still want to send?");
1067 
1068         on_tv_ready ();
1069       }
1070     }
1071 
1072     return true;
1073   }
1074 
send_message()1075   bool EditMessage::send_message () {
1076     LOG (info) << "em: sending message..";
1077 
1078     /* load body */
1079     editor_toggle (false); // resets warning and info
1080 
1081     set_warning ("");
1082     set_info ("sending message..");
1083 
1084     on_tv_ready ();
1085 
1086     auto c = make_message ();
1087 
1088     if (c == NULL) return false;
1089 
1090     if (c->markdown && !c->markdown_success) {
1091       set_warning ("Cannot send, failed processing markdown: " + UstringUtils::replace (c->markdown_error, "\n", "<br />"));
1092       return false;
1093     }
1094 
1095     if (c->encrypt || c->sign) {
1096       if (!c->encryption_success) {
1097         set_warning ("Cannot send, failed encrypting: " + UstringUtils::replace (c->encryption_error, "\n", "<br />"));
1098         return false;
1099       }
1100     }
1101 
1102     c->message_sent().connect (
1103         sigc::mem_fun (this, &EditMessage::send_message_finished));
1104     c->message_send_status ().connect (
1105         sigc::mem_fun (this, &EditMessage::update_send_message_status));
1106 
1107     fields_hide ();
1108     sending_in_progress.store (true);
1109 
1110     /* set message sending icon */
1111     status_icon_visible = true;
1112     Glib::RefPtr<Gtk::IconTheme> theme = Gtk::IconTheme::get_default();
1113     Glib::RefPtr<Gdk::Pixbuf> pixbuf = theme->load_icon (
1114         "mail-send",
1115         Notebook::icon_size,
1116         Gtk::ICON_LOOKUP_USE_BUILTIN | Gtk::ICON_LOOKUP_FORCE_SIZE);
1117     message_sending_status_icon.set (pixbuf);
1118 
1119     main_window->notebook.add_widget (&message_sending_status_icon);
1120 
1121     c->send_threaded ();
1122 
1123     sending_message = std::move (c);
1124 
1125     return true;
1126   }
1127 
update_send_message_status(bool warn,ustring msg)1128   void EditMessage::update_send_message_status (bool warn, ustring msg) {
1129     if (warn) {
1130       set_info ("");
1131       set_warning (msg);
1132     } else {
1133       set_info (msg);
1134       set_warning ("");
1135     }
1136 
1137     on_tv_ready ();
1138   }
1139 
send_message_finished(bool result_from_sender)1140   void EditMessage::send_message_finished (bool result_from_sender) {
1141     LOG (info) << "em: message sending done.";
1142     status_icon_visible = true;
1143 
1144     Glib::RefPtr<Gtk::IconTheme> theme = Gtk::IconTheme::get_default();
1145     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
1146 
1147     if (result_from_sender) {
1148       lock_message_after_send ();
1149 
1150       pixbuf = theme->load_icon (
1151           "gtk-apply",
1152           Notebook::icon_size,
1153           Gtk::ICON_LOOKUP_USE_BUILTIN | Gtk::ICON_LOOKUP_FORCE_SIZE);
1154 
1155       /* delete draft */
1156       if (draft_msg) {
1157         LOG (info) << "em: deleting draft: " << draft_msg->fname;
1158         delete_draft ();
1159       }
1160 
1161     } else {
1162       fields_show ();
1163 
1164       pixbuf = theme->load_icon (
1165          "dialog-error",
1166           Notebook::icon_size,
1167           Gtk::ICON_LOOKUP_USE_BUILTIN | Gtk::ICON_LOOKUP_FORCE_SIZE);
1168     }
1169 
1170     message_sending_status_icon.set (pixbuf);
1171     sending_in_progress.store (false);
1172 
1173     sending_message.reset();
1174 
1175     emit_message_sent_attempt (result_from_sender);
1176 
1177     if (result_from_sender && (astroid->config().get<bool> ("mail.close_on_success"))) {
1178       LOG (info) << "cm: sending successful, auto-closing window";
1179       close (true);
1180     }
1181   }
1182 
lock_message_after_send()1183   void EditMessage::lock_message_after_send () {
1184     message_sent = true;
1185 
1186     fields_hide ();
1187   }
1188 
setup_message()1189   std::unique_ptr<ComposeMessage> EditMessage::setup_message () {
1190     auto c = std::unique_ptr<ComposeMessage>(new ComposeMessage ());
1191 
1192     c->load_message (msg_id, tmpfile_path.c_str());
1193 
1194     c->set_references (references);
1195     c->set_inreplyto (inreplyto);
1196 
1197     for (shared_ptr<ComposeMessage::Attachment> a : attachments) {
1198       c->add_attachment (a);
1199     }
1200 
1201     return c;
1202   }
1203 
finalize_message(std::unique_ptr<ComposeMessage> & c)1204   void EditMessage::finalize_message (std::unique_ptr<ComposeMessage> &c) {
1205     /* these options are not known before setup_message is done, and the
1206      * new account information has been applied to the editor */
1207     if (c->account->has_signature && switch_signature->get_active ()) {
1208       c->include_signature = true;
1209     } else {
1210       c->include_signature = false;
1211     }
1212     c->markdown = switch_markdown->get_active ();
1213 
1214     if (gpgenabled && c->account->has_gpg) {
1215       c->encrypt = switch_encrypt->get_active ();
1216       c->sign    = switch_sign->get_active ();
1217     }
1218 
1219     c->build ();
1220     c->finalize ();
1221   }
1222 
make_message()1223   std::unique_ptr<ComposeMessage> EditMessage::make_message () {
1224     return make_message (false);
1225   }
1226 
make_draft_message()1227   std::unique_ptr<ComposeMessage> EditMessage::make_draft_message () {
1228     return make_message (true);
1229   }
1230 
make_message(bool draft=false)1231   std::unique_ptr<ComposeMessage> EditMessage::make_message (bool draft = false) {
1232     auto c = setup_message ();
1233     bool sigstate = c->account->has_signature;
1234 
1235     if (draft) {
1236       /* Do not save signature in a draft */
1237       c->account->has_signature = false;
1238     }
1239     finalize_message (c);
1240     c->account->has_signature = sigstate;
1241 
1242     return c;
1243   }
1244 
1245   /* }}} */
1246 
make_tmpfile()1247   void EditMessage::make_tmpfile () {
1248     tmpfile_path = tmpfile_path / path(msg_id);
1249 
1250     LOG (info) << "em: tmpfile: " << tmpfile_path;
1251 
1252     if (is_regular_file (tmpfile_path)) {
1253       LOG (error) << "em: error: tmpfile already exists!";
1254       throw runtime_error ("em: tmpfile already exists!");
1255     }
1256 
1257     tmpfile.open (tmpfile_path.c_str(), std::fstream::out);
1258 
1259     if (tmpfile.fail()) {
1260       LOG (error) << "em: error: could not create tmpfile!";
1261       throw runtime_error ("em: could not create tmpfile!");
1262     }
1263 
1264     tmpfile.close ();
1265   }
1266 
attach_file()1267   void EditMessage::attach_file () {
1268     LOG (info) << "em: attach file..";
1269 
1270     if (message_sent) {
1271       LOG (debug) << "em: message already sent.";
1272       return;
1273     }
1274 
1275     Gtk::FileChooserDialog dialog ("Choose file to attach..",
1276         Gtk::FILE_CHOOSER_ACTION_OPEN);
1277 
1278     dialog.add_button ("_Cancel", Gtk::RESPONSE_CANCEL);
1279     dialog.add_button ("_Attach", Gtk::RESPONSE_OK);
1280     dialog.set_select_multiple (true);
1281     dialog.set_current_folder (astroid->runtime_paths ().attach_dir.c_str ());
1282 
1283     int result = dialog.run ();
1284 
1285     switch (result) {
1286       case (Gtk::RESPONSE_OK):
1287         {
1288           vector<string> fnames = dialog.get_filenames ();
1289           astroid->runtime_paths ().attach_dir = bfs::path (dialog.get_current_folder ());
1290           for (string &fname : fnames) {
1291             path p (fname.c_str());
1292 
1293             if (!is_regular (p)) {
1294               LOG (error) << "em: attach: file is not regular: " << p.c_str();
1295             } else {
1296               LOG (info) << "em: attaching file: " << p.c_str();
1297               add_attachment (new ComposeMessage::Attachment (p));
1298             }
1299           }
1300 
1301           prepare_message ();
1302           read_edited_message ();
1303 
1304           break;
1305         }
1306 
1307       default:
1308         {
1309           LOG (debug) << "em: attach: cancelled.";
1310         }
1311     }
1312   }
1313 
add_attachment(ComposeMessage::Attachment * a)1314   void EditMessage::add_attachment (ComposeMessage::Attachment * a) {
1315     if (a->valid) {
1316       attachments.push_back (shared_ptr<ComposeMessage::Attachment> (a));
1317     } else {
1318       LOG (error) << "em: invalid attachment, not adding: " << a->name;
1319       delete a;
1320     }
1321   }
1322 
grab_modal()1323   void EditMessage::grab_modal () {
1324     if (!embed_editor || !editor_active) add_modal_grab ();
1325   }
1326 
release_modal()1327   void EditMessage::release_modal () {
1328     remove_modal_grab ();
1329   }
1330 
1331   /* message sent attempt signal */
1332   EditMessage::type_message_sent_attempt
message_sent_attempt()1333     EditMessage::message_sent_attempt ()
1334   {
1335     return m_message_sent_attempt;
1336   }
1337 
emit_message_sent_attempt(bool res)1338   void EditMessage::emit_message_sent_attempt (bool res) {
1339     m_message_sent_attempt.emit (res);
1340   }
1341 
1342   /* from combo box {{{ */
1343   /*
1344   bool EditMessage::on_from_combo_key_press (GdkEventKey * event) {
1345 
1346     switch (event->keyval) {
1347       case GDK_KEY_j:
1348         {
1349           from_combo->set_active (from_combo->get_active_row_number()+1);
1350           return true;
1351         }
1352     }
1353 
1354     return false;
1355   }
1356   */
1357 
1358   // }}}
1359 
1360 }
1361