Adjust menus for external embedded editor
[claws.git] / src / compose.c
index 2cf8df88864e2f8dcf690fd4195f0e41a921ecbd..7b781724704914ec2b5104578ddd842229a5b3e4 100644 (file)
@@ -336,13 +336,19 @@ static gboolean attach_property_key_pressed       (GtkWidget      *widget,
 
 static void compose_exec_ext_editor            (Compose        *compose);
 #ifdef G_OS_UNIX
-static gint compose_exec_ext_editor_real       (const gchar    *file);
+static gint compose_exec_ext_editor_real       (const gchar    *file,
+                                                GdkNativeWindow socket_wid);
 static gboolean compose_ext_editor_kill                (Compose        *compose);
 static gboolean compose_input_cb               (GIOChannel     *source,
                                                 GIOCondition    condition,
                                                 gpointer        data);
 static void compose_set_ext_editor_sensitive   (Compose        *compose,
                                                 gboolean        sensitive);
+static gboolean compose_get_ext_editor_cmd_valid();
+static gboolean compose_get_ext_editor_uses_socket();
+static gboolean compose_ext_editor_plug_removed_cb
+                                               (GtkSocket      *socket,
+                                                Compose        *compose);
 #endif /* G_OS_UNIX */
 
 static void compose_undo_state_changed         (UndoMain       *undostruct,
@@ -899,7 +905,7 @@ static void compose_create_tags(GtkTextView *text, Compose *compose)
 
        for (i = 0; i < 8; i++) {
                if (success[i] == FALSE) {
-                       g_warning("Compose: color allocation failed.\n");
+                       g_warning("Compose: color allocation failed.");
                        quote_color1 = quote_color2 = quote_color3 = 
                                quote_bgcolor1 = quote_bgcolor2 = quote_bgcolor3 = 
                                signature_color = uri_color = black;
@@ -1307,11 +1313,15 @@ static void compose_force_encryption(Compose *compose, PrefsAccount *account,
                if (system) {
                        g_free(compose->privacy_system);
                        compose->privacy_system = NULL;
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                }
                if (compose->privacy_system == NULL)
                        compose->privacy_system = g_strdup(privacy);
                else if (*(compose->privacy_system) == '\0') {
                        g_free(compose->privacy_system);
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                        compose->privacy_system = g_strdup(privacy);
                }
                compose_update_privacy_system_menu_item(compose, FALSE);
@@ -1338,6 +1348,8 @@ static void compose_force_signing(Compose *compose, PrefsAccount *account, const
                if (system) {
                        g_free(compose->privacy_system);
                        compose->privacy_system = NULL;
+                       g_free(compose->encdata);
+                       compose->encdata = NULL;
                }
                if (compose->privacy_system == NULL)
                        compose->privacy_system = g_strdup(privacy);
@@ -1437,7 +1449,7 @@ static Compose *compose_reply_mode(ComposeMode mode, GSList *msginfo_list, gchar
                compose = compose_redirect(NULL, msginfo, FALSE);
                break;
        default:
-               g_warning("compose_reply_mode(): invalid Compose Mode: %d\n", mode);
+               g_warning("compose_reply_mode(): invalid Compose Mode: %d", mode);
        }
        
        if (compose == NULL) {
@@ -1827,7 +1839,7 @@ Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
 
                msgfile = procmsg_get_message_file(msginfo);
                if (!is_file_exist(msgfile))
-                       g_warning("%s: file not exist\n", msgfile);
+                       g_warning("%s: file does not exist", msgfile);
                else
                        compose_attach_append(compose, msgfile, msgfile,
                                              "message/rfc822", NULL);
@@ -2018,7 +2030,7 @@ static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_
                msgfile = procmsg_get_message_file((MsgInfo *)msginfo->data);
 
                if (!is_file_exist(msgfile))
-                       g_warning("%s: file not exist\n", msgfile);
+                       g_warning("%s: file does not exist", msgfile);
                else
                        compose_attach_append(compose, msgfile, msgfile,
                                "message/rfc822", NULL);
@@ -2409,7 +2421,7 @@ Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
                fp = procmime_get_first_text_content(msginfo);
        }
        if (fp == NULL) {
-               g_warning("Can't get text part\n");
+               g_warning("Can't get text part");
        }
 
        if (fp != NULL) {
@@ -3602,7 +3614,7 @@ static ComposeInsertResult compose_insert_file(Compose *compose, const gchar *fi
                                                "in the message body. Are you sure you want to do that?"),
                                                to_human_readable(file_stat.st_size));
                        aval = alertpanel_full(_("Are you sure?"), msg, GTK_STOCK_CANCEL,
-                                       _("+_Insert"), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
+                                       g_strconcat("+", _("_Insert"), NULL), NULL, TRUE, NULL, ALERT_QUESTION, G_ALERTDEFAULT);
                        g_free(msg);
 
                        /* do we ask for confirmation next time? */
@@ -3728,7 +3740,7 @@ static gboolean compose_attach_append(Compose *compose, const gchar *file,
        if (size == 0 && !compose->batch) {
                gchar * msg = g_strdup_printf(_("File %s is empty."), filename);
                AlertValue aval = alertpanel_full(_("Empty file"), msg, 
-                               GTK_STOCK_CANCEL, _("+_Attach anyway"), NULL, FALSE,
+                               GTK_STOCK_CANCEL,  g_strconcat("+", _("_Attach anyway"), NULL), NULL, FALSE,
                                NULL, ALERT_WARNING, G_ALERTDEFAULT);
                g_free(msg);
 
@@ -4138,7 +4150,7 @@ static gboolean compose_get_line_break_pos(GtkTextBuffer *buffer,
        
        if (len == 0) {
                g_free(str);
-               g_warning("compose_get_line_break_pos: len = 0!\n");
+               g_warning("compose_get_line_break_pos: len = 0!");
                return FALSE;
        }
 
@@ -4333,7 +4345,7 @@ static gboolean compose_join_next_line(Compose *compose,
                last->bp = (bp_); last->ep = (ep_); last->pti = (pti_); \
                last->next = NULL; \
        } else { \
-               g_warning("alloc error scanning URIs\n"); \
+               g_warning("alloc error scanning URIs"); \
        }
 
 static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_iter, gboolean force)
@@ -4812,7 +4824,7 @@ compose_current_mail_account(void)
                                                                        \
                len = strlen(str) + 3;                                  \
                if ((__tmp = alloca(len)) == NULL) {                    \
-                       g_warning("can't allocate memory\n");           \
+                       g_warning("can't allocate memory");             \
                        g_string_free(header, TRUE);                    \
                        return NULL;                                    \
                }                                                       \
@@ -4822,7 +4834,7 @@ compose_current_mail_account(void)
                gchar *__tmp;                                           \
                                                                        \
                if ((__tmp = alloca(strlen(str) + 1)) == NULL) {        \
-                       g_warning("can't allocate memory\n");           \
+                       g_warning("can't allocate memory");             \
                        g_string_free(header, TRUE);                    \
                        return NULL;                                    \
                } else                                                  \
@@ -4840,7 +4852,7 @@ compose_current_mail_account(void)
                                                                        \
                len = strlen(str) + 3;                                  \
                if ((__tmp = alloca(len)) == NULL) {                    \
-                       g_warning("can't allocate memory\n");           \
+                       g_warning("can't allocate memory");             \
                        errret;                                         \
                }                                                       \
                g_snprintf(__tmp, len, "\"%s\"", str);                  \
@@ -4849,7 +4861,7 @@ compose_current_mail_account(void)
                gchar *__tmp;                                           \
                                                                        \
                if ((__tmp = alloca(strlen(str) + 1)) == NULL) {        \
-                       g_warning("can't allocate memory\n");           \
+                       g_warning("can't allocate memory");             \
                        errret;                                         \
                } else                                                  \
                        strcpy(__tmp, str);                             \
@@ -5018,7 +5030,7 @@ static gboolean compose_check_for_set_recipients(Compose *compose)
                        }
                        aval = alertpanel(_("Send"),
                                          _("The only recipient is the default CC address. Send anyway?"),
-                                         GTK_STOCK_CANCEL, _("+_Send"), NULL);
+                                         GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
                        if (aval != G_ALERTALTERNATE)
                                return FALSE;
                }
@@ -5050,7 +5062,7 @@ static gboolean compose_check_for_set_recipients(Compose *compose)
                        }
                        aval = alertpanel(_("Send"),
                                          _("The only recipient is the default BCC address. Send anyway?"),
-                                         GTK_STOCK_CANCEL, _("+_Send"), NULL);
+                                         GTK_STOCK_CANCEL, g_strconcat("+", _("_Send"), NULL), NULL);
                        if (aval != G_ALERTALTERNATE)
                                return FALSE;
                }
@@ -5083,9 +5095,9 @@ static gboolean compose_check_entries(Compose *compose, gboolean check_everythin
                        gchar *message;
 
                        if (compose->sending)
-                               button_label = _("+_Send");
+                               button_label = g_strconcat("+", _("_Send"), NULL);
                        else
-                               button_label = _("+_Queue");
+                               button_label = g_strconcat("+", _("_Queue"), NULL);
                        message = g_strdup_printf(_("Subject is empty. %s"),
                                        compose->sending?_("Send it anyway?"):
                                        _("Queue it anyway?"));
@@ -5512,8 +5524,8 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
 {
        GtkTextBuffer *buffer;
        GtkTextIter start, end;
-       gchar *chars;
-       gchar *buf;
+       gchar *chars, *tmp_enc_file, *content;
+       gchar *buf, *msg;
        const gchar *out_codeset;
        EncodingType encoding = ENC_UNKNOWN;
        MimeInfo *mimemsg, *mimetext;
@@ -5521,6 +5533,7 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
        const gchar *src_codeset = CS_INTERNAL;
        gchar *from_addr = NULL;
        gchar *from_name = NULL;
+       FolderItem *outbox;
 
        if (action == COMPOSE_WRITE_FOR_SEND)
                attach_parts = TRUE;
@@ -5601,12 +5614,12 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
 
                if (!buf) {
                        AlertValue aval;
-                       gchar *msg;
 
                        msg = g_strdup_printf(_("Can't convert the character encoding of the message \n"
                                                "to the specified %s charset.\n"
                                                "Send it as %s?"), out_codeset, src_codeset);
-                       aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL, _("+_Send"), NULL, FALSE,
+                       aval = alertpanel_full(_("Error"), msg, GTK_STOCK_CANCEL,
+                                              g_strconcat("+", _("_Send"), NULL), NULL, FALSE,
                                              NULL, ALERT_ERROR, G_ALERTDEFAULT);
                        g_free(msg);
 
@@ -5658,7 +5671,6 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
            encoding != ENC_QUOTED_PRINTABLE && encoding != ENC_BASE64 &&
            check_line_length(buf, 1000, &line) < 0) {
                AlertValue aval;
-               gchar *msg;
 
                msg = g_strdup_printf
                        (_("Line %d exceeds the line length limit (998 bytes).\n"
@@ -5730,6 +5742,54 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
        }
        g_free(from_name);
        g_free(from_addr);
+
+       if (compose->use_encryption) {
+               if (compose->encdata != NULL &&
+                               strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
+
+                       /* First, write an unencrypted copy and save it to outbox, if
+                        * user wants that. */
+                       if (compose->account->save_encrypted_as_clear_text) {
+                               debug_print("saving sent message unencrypted...\n");
+                               FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
+                               if (tmpfp) {
+                                       fclose(tmpfp);
+
+                                       /* fp now points to a file with headers written,
+                                        * let's make a copy. */
+                                       rewind(fp);
+                                       content = file_read_stream_to_str(fp);
+
+                                       str_write_to_file(content, tmp_enc_file);
+                                       g_free(content);
+
+                                       /* Now write the unencrypted body. */
+                                       if ((tmpfp = g_fopen(tmp_enc_file, "a")) != NULL) {
+                                               procmime_write_mimeinfo(mimemsg, tmpfp);
+                                               fclose(tmpfp);
+
+                                               outbox = folder_find_item_from_identifier(compose_get_save_to(compose));
+                                               if (!outbox)
+                                                       outbox = folder_get_default_outbox();
+
+                                               procmsg_save_to_outbox(outbox, tmp_enc_file, TRUE);
+                                               claws_unlink(tmp_enc_file);
+                                       } else {
+                                               g_warning("Can't open file '%s'", tmp_enc_file);
+                                       }
+                               } else {
+                                       g_warning("couldn't get tempfile");
+                               }
+                       }
+                       if (!privacy_encrypt(compose->privacy_system, mimemsg, compose->encdata)) {
+                               debug_print("Couldn't encrypt mime structure: %s.\n",
+                                               privacy_get_error());
+                               alertpanel_error(_("Couldn't encrypt the email: %s"),
+                                               privacy_get_error());
+                       }
+               }
+       }
+
        procmime_write_mimeinfo(mimemsg, fp);
        
        procmime_mimeinfo_free_all(mimemsg);
@@ -5753,7 +5813,7 @@ static gint compose_write_body_to_file(Compose *compose, const gchar *file)
        /* chmod for security */
        if (change_file_mode_rw(fp, file) < 0) {
                FILE_OP_ERROR(file, "chmod");
-               g_warning("can't change file mode\n");
+               g_warning("can't change file mode");
        }
 
        buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(compose->text));
@@ -5809,7 +5869,7 @@ static gint compose_remove_reedit_target(Compose *compose, gboolean force)
             folder_has_parent_of_type(item, F_DRAFT) 
             || msginfo == compose->autosaved_draft)) {
                if (folder_item_remove_msg(item, msginfo->msgnum) < 0) {
-                       g_warning("can't remove the old message\n");
+                       g_warning("can't remove the old message");
                        return -1;
                } else {
                        debug_print("removed reedit target %d\n", msginfo->msgnum);
@@ -5846,7 +5906,7 @@ static gboolean compose_warn_encryption(Compose *compose)
                return TRUE;
 
        val = alertpanel_full(_("Encryption warning"), warning,
-                 GTK_STOCK_CANCEL, _("+C_ontinue"), NULL,
+                 GTK_STOCK_CANCEL, g_strconcat("+", _("C_ontinue"), NULL), NULL,
                  TRUE, NULL, ALERT_WARNING, G_ALERTALTERNATE);
        if (val & G_ALERTDISABLE) {
                val &= ~G_ALERTDISABLE;
@@ -5913,7 +5973,7 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
        tmp = g_strdup_printf("%s%cqueue.%p%08x", get_tmp_dir(),
                              G_DIR_SEPARATOR, compose, (guint) rand());
        debug_print("queuing to %s\n", tmp);
-       if ((fp = g_fopen(tmp, "wb")) == NULL) {
+       if ((fp = g_fopen(tmp, "w+b")) == NULL) {
                FILE_OP_ERROR(tmp, "fopen");
                g_free(tmp);
                return -2;
@@ -5921,7 +5981,7 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
 
        if (change_file_mode_rw(fp, tmp) < 0) {
                FILE_OP_ERROR(tmp, "chmod");
-               g_warning("can't change file mode\n");
+               g_warning("can't change file mode");
        }
 
        /* queueing variables */
@@ -5975,7 +6035,6 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                err |= (fprintf(fp, "X-Claws-Privacy-System:%s\n", compose->privacy_system) < 0);
                err |= (fprintf(fp, "X-Claws-Sign:%d\n", compose->use_signing) < 0);
                if (compose->use_encryption) {
-                       gchar *encdata;
                        if (!compose_warn_encryption(compose)) {
                                fclose(fp);
                                claws_unlink(tmp);
@@ -5985,16 +6044,16 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                        if (mailac && mailac->encrypt_to_self) {
                                GSList *tmp_list = g_slist_copy(compose->to_list);
                                tmp_list = g_slist_append(tmp_list, compose->account->address);
-                               encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
+                               compose->encdata = privacy_get_encrypt_data(compose->privacy_system, tmp_list);
                                g_slist_free(tmp_list);
                        } else {
-                               encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
+                               compose->encdata = privacy_get_encrypt_data(compose->privacy_system, compose->to_list);
                        }
-                       if (encdata != NULL) {
-                               if (strcmp(encdata, "_DONT_ENCRYPT_")) {
+                       if (compose->encdata != NULL) {
+                               if (strcmp(compose->encdata, "_DONT_ENCRYPT_")) {
                                        err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
                                        err |= (fprintf(fp, "X-Claws-Encrypt-Data:%s\n", 
-                                               encdata) < 0);
+                                               compose->encdata) < 0);
                                } /* else we finally dont want to encrypt */
                        } else {
                                err |= (fprintf(fp, "X-Claws-Encrypt:%d\n", compose->use_encryption) < 0);
@@ -6007,7 +6066,6 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                                g_free(tmp);
                                return -5;
                        }
-                       g_free(encdata);
                }
        }
 
@@ -6071,7 +6129,7 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                }
        }
        if (err == TRUE) {
-               g_warning("failed to write queue message\n");
+               g_warning("failed to write queue message");
                fclose(fp);
                claws_unlink(tmp);
                g_free(tmp);
@@ -6090,14 +6148,14 @@ static gint compose_queue_sub(Compose *compose, gint *msgnum, FolderItem **item,
                queue = account_get_special_folder(compose->account, F_QUEUE);
        }
        if (!queue) {
-               g_warning("can't find queue folder\n");
+               g_warning("can't find queue folder");
                claws_unlink(tmp);
                g_free(tmp);
                return -1;
        }
        folder_item_scan(queue);
        if ((num = folder_item_add_msg(queue, tmp, NULL, FALSE)) < 0) {
-               g_warning("can't queue the message\n");
+               g_warning("can't queue the message");
                claws_unlink(tmp);
                g_free(tmp);
                return -1;
@@ -6912,6 +6970,10 @@ static void compose_create_header_entry(Compose *compose)
        if (header)
                gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN((combo)))), header);
 
+       gtk_editable_set_editable(
+               GTK_EDITABLE(gtk_bin_get_child(GTK_BIN((combo)))),
+               prefs_common.type_any_header);
+
        g_signal_connect_after(G_OBJECT(gtk_bin_get_child(GTK_BIN((combo)))), "grab_focus",
                         G_CALLBACK(compose_grab_focus_cb), compose);
 
@@ -7941,6 +8003,7 @@ static Compose *compose_create(PrefsAccount *account,
        compose->use_signing    = FALSE;
        compose->use_encryption = FALSE;
        compose->privacy_system = NULL;
+       compose->encdata        = NULL;
 
        compose->modified = FALSE;
 
@@ -7956,6 +8019,7 @@ static Compose *compose_create(PrefsAccount *account,
        compose->exteditor_file    = NULL;
        compose->exteditor_pid     = -1;
        compose->exteditor_tag     = -1;
+       compose->exteditor_socket  = NULL;
        compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
 
        compose->folder_update_callback_id =
@@ -8091,7 +8155,7 @@ static GtkWidget *compose_account_option_menu_create(Compose *compose)
 
                if (ac == compose->account) def_menu = num;
 
-               name = g_markup_printf_escaped(_("<i>%s</i>"),
+               name = g_markup_printf_escaped("<i>%s</i>",
                                       ac->account_name);
                
                if (ac == compose->account) {
@@ -8245,6 +8309,8 @@ static void compose_set_privacy_system_cb(GtkWidget *widget, gpointer data)
        systemid = g_object_get_data(G_OBJECT(widget), "privacy_system");
        g_free(compose->privacy_system);
        compose->privacy_system = NULL;
+       g_free(compose->encdata);
+       compose->encdata = NULL;
        if (systemid != NULL) {
                compose->privacy_system = g_strdup(systemid);
 
@@ -8782,6 +8848,7 @@ static void compose_destroy(Compose *compose)
        g_free(compose->orig_charset);
 
        g_free(compose->privacy_system);
+       g_free(compose->encdata);
 
 #ifndef USE_NEW_ADDRBOOK
        if (addressbook_get_target_compose() == compose)
@@ -9218,12 +9285,37 @@ static void compose_exec_ext_editor(Compose *compose)
 {
 #ifdef G_OS_UNIX
        gchar *tmp;
+       GtkWidget *socket;
+       GdkNativeWindow socket_wid = 0;
        pid_t pid;
        gint pipe_fds[2];
 
        tmp = g_strdup_printf("%s%ctmpmsg.%p", get_tmp_dir(),
                              G_DIR_SEPARATOR, compose);
 
+       if (compose_get_ext_editor_uses_socket()) {
+               /* Only allow one socket */
+               if (compose->exteditor_socket != NULL) {
+                       if (gtk_widget_is_focus(compose->exteditor_socket)) {
+                               /* Move the focus off of the socket */
+                               gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
+                       }
+                       g_free(tmp);
+                       return;
+               }
+               /* Create the receiving GtkSocket */
+               socket = gtk_socket_new ();
+               g_signal_connect (GTK_OBJECT(socket), "plug-removed",
+                                 G_CALLBACK(compose_ext_editor_plug_removed_cb),
+                                 compose);
+               gtk_box_pack_start(GTK_BOX(compose->edit_vbox), socket, TRUE, TRUE, 0);
+               gtk_widget_set_size_request(socket, prefs_common.compose_width, -1);
+               /* Realize the socket so that we can use its ID */
+               gtk_widget_realize(socket);
+               socket_wid = gtk_socket_get_id(GTK_SOCKET (socket));
+               compose->exteditor_socket = socket;
+       }
+
        if (pipe(pipe_fds) < 0) {
                perror("pipe");
                g_free(tmp);
@@ -9268,7 +9360,7 @@ static void compose_exec_ext_editor(Compose *compose)
                        _exit(1);
                }
 
-               pid_ed = compose_exec_ext_editor_real(tmp);
+               pid_ed = compose_exec_ext_editor_real(tmp, socket_wid);
                if (pid_ed < 0) {
                        fd_write_all(pipe_fds[1], "1\n", 2);
                        _exit(1);
@@ -9288,10 +9380,34 @@ static void compose_exec_ext_editor(Compose *compose)
 }
 
 #ifdef G_OS_UNIX
-static gint compose_exec_ext_editor_real(const gchar *file)
+static gboolean compose_get_ext_editor_cmd_valid()
+{
+       gboolean has_s = FALSE;
+       gboolean has_w = FALSE;
+       const gchar *p = prefs_common_get_ext_editor_cmd();
+       if (!p)
+               return FALSE;
+       while ((p = strchr(p, '%'))) {
+               p++;
+               if (*p == 's') {
+                       if (has_s)
+                               return FALSE;
+                       has_s = TRUE;
+               } else if (*p == 'w') {
+                       if (has_w)
+                               return FALSE;
+                       has_w = TRUE;
+               } else {
+                       return FALSE;
+               }
+       }
+       return TRUE;
+}
+
+static gint compose_exec_ext_editor_real(const gchar *file, GdkNativeWindow socket_wid)
 {
        gchar buf[1024];
-       gchar *p;
+       gchar *p, *s;
        gchar **cmdline;
        pid_t pid;
 
@@ -9309,13 +9425,23 @@ static gint compose_exec_ext_editor_real(const gchar *file)
        if (setpgid(0, getppid()))
                perror("setpgid");
 
-       if (prefs_common_get_ext_editor_cmd() &&
-           (p = strchr(prefs_common_get_ext_editor_cmd(), '%')) &&
-           *(p + 1) == 's' && !strchr(p + 2, '%')) {
-               g_snprintf(buf, sizeof(buf), prefs_common_get_ext_editor_cmd(), file);
+       if (compose_get_ext_editor_cmd_valid()) {
+               if (compose_get_ext_editor_uses_socket()) {
+                       p = g_strdup(prefs_common_get_ext_editor_cmd());
+                       s = strstr(p, "%w");
+                       s[1] = 'u';
+                       if (strstr(p, "%s") < s)
+                               g_snprintf(buf, sizeof(buf), p, file, socket_wid);
+                       else
+                               g_snprintf(buf, sizeof(buf), p, socket_wid, file);
+                       g_free(p);
+               } else {
+                       g_snprintf(buf, sizeof(buf),
+                                  prefs_common_get_ext_editor_cmd(), file);
+               }
        } else {
                if (prefs_common_get_ext_editor_cmd())
-                       g_warning("External editor command-line is invalid: '%s'\n",
+                       g_warning("External editor command-line is invalid: '%s'",
                                  prefs_common_get_ext_editor_cmd());
                g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, file);
        }
@@ -9358,9 +9484,8 @@ static gboolean compose_ext_editor_kill(Compose *compose)
                        if (kill(pgid, SIGTERM) < 0) perror("kill");
                        waitpid(compose->exteditor_pid, NULL, 0);
 
-                       g_warning("Terminated process group id: %d", -pgid);
-                       g_warning("Temporary file: %s",
-                                 compose->exteditor_file);
+                       g_warning("Terminated process group id: %d. "
+                                 "Temporary file: %s", -pgid, compose->exteditor_file);
 
                        compose_set_ext_editor_sensitive(compose, TRUE);
 
@@ -9385,7 +9510,10 @@ static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
 
        debug_print("Compose: input from monitoring process\n");
 
-       g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL);
+       if (g_io_channel_read_chars(source, buf, sizeof(buf), &bytes_read, NULL) != G_IO_STATUS_NORMAL) {
+               bytes_read = 0;
+               buf[0] = '\0';
+       }
 
        g_io_channel_shutdown(source, FALSE, NULL);
        g_io_channel_unref(source);
@@ -9414,13 +9542,13 @@ static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
                        compose->modified = TRUE;
                g_free(chars);
        } else if (buf[0] == '1') {     /* failed */
-               g_warning("Couldn't exec external editor\n");
+               g_warning("Couldn't exec external editor");
                if (claws_unlink(compose->exteditor_file) < 0)
                        FILE_OP_ERROR(compose->exteditor_file, "unlink");
        } else if (buf[0] == '2') {
-               g_warning("Couldn't write to file\n");
+               g_warning("Couldn't write to file");
        } else if (buf[0] == '3') {
-               g_warning("Pipe read failed\n");
+               g_warning("Pipe read failed");
        }
 
        compose_set_ext_editor_sensitive(compose, TRUE);
@@ -9430,23 +9558,66 @@ static gboolean compose_input_cb(GIOChannel *source, GIOCondition condition,
        compose->exteditor_pid     = -1;
        compose->exteditor_ch      = NULL;
        compose->exteditor_tag     = -1;
+       if (compose->exteditor_socket) {
+               gtk_widget_destroy(compose->exteditor_socket);
+               compose->exteditor_socket = NULL;
+       }
+
 
        return FALSE;
 }
 
+static char *ext_editor_menu_entries[] = {
+       "Menu/Message/Send",
+       "Menu/Message/SendLater",
+       "Menu/Message/InsertFile",
+       "Menu/Message/InsertSig",
+       "Menu/Message/ReplaceSig",
+       "Menu/Message/Save",
+       "Menu/Message/Print",
+       "Menu/Edit",
+#if USE_ENCHANT
+       "Menu/Spelling",
+#endif
+       "Menu/Tools/ShowRuler",
+       "Menu/Tools/Actions",
+       "Menu/Help",
+       NULL
+};
+
 static void compose_set_ext_editor_sensitive(Compose *compose,
                                             gboolean sensitive)
 {
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/Send", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/SendLater", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertFile", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/InsertSig", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Message/ReplaceSig", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapPara", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
-       cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
+       int i;
 
-       gtk_widget_set_sensitive(compose->text,                       sensitive);
+       for (i = 0; ext_editor_menu_entries[i]; ++i) {
+               cm_menu_set_sensitive_full(compose->ui_manager,
+                       ext_editor_menu_entries[i], sensitive);
+       }
+
+       if (compose_get_ext_editor_uses_socket()) {
+               if (sensitive) {
+                       if (compose->exteditor_socket)
+                               gtk_widget_hide(compose->exteditor_socket);
+                       gtk_widget_show(compose->scrolledwin);
+                       if (prefs_common.show_ruler)
+                               gtk_widget_show(compose->ruler_hbox);
+                       /* Fix the focus, as it doesn't go anywhere when the
+                        * socket is hidden or destroyed */
+                       gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
+               } else {
+                       g_assert (compose->exteditor_socket != NULL);
+                       /* Fix the focus, as it doesn't go anywhere when the
+                        * edit box is hidden */
+                       if (gtk_widget_is_focus(compose->text))
+                               gtk_widget_child_focus(compose->window, GTK_DIR_TAB_BACKWARD);
+                       gtk_widget_hide(compose->scrolledwin);
+                       gtk_widget_hide(compose->ruler_hbox);
+                       gtk_widget_show(compose->exteditor_socket);
+               }
+       } else {
+               gtk_widget_set_sensitive(compose->text,                   sensitive);
+       }
        if (compose->toolbar->send_btn)
                gtk_widget_set_sensitive(compose->toolbar->send_btn,      sensitive);
        if (compose->toolbar->sendl_btn)
@@ -9464,6 +9635,19 @@ static void compose_set_ext_editor_sensitive(Compose *compose,
        if (compose->toolbar->linewrap_all_btn)
                gtk_widget_set_sensitive(compose->toolbar->linewrap_all_btn, sensitive);
 }
+
+static gboolean compose_get_ext_editor_uses_socket()
+{
+       return (prefs_common_get_ext_editor_cmd() &&
+               strstr(prefs_common_get_ext_editor_cmd(), "%w"));
+}
+
+static gboolean compose_ext_editor_plug_removed_cb(GtkSocket *socket, Compose *compose)
+{
+       compose->exteditor_socket = NULL;
+       /* returning FALSE allows destruction of the socket */
+       return FALSE;
+}
 #endif /* G_OS_UNIX */
 
 /**
@@ -9691,15 +9875,35 @@ static gboolean attach_button_pressed(GtkWidget *widget, GdkEventButton *event,
        Compose *compose = (Compose *)data;
        GtkTreeSelection *attach_selection;
        gint attach_nr_selected;
+       GtkTreePath *path;
        
        if (!event) return FALSE;
 
        if (event->button == 3) {
                attach_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
                attach_nr_selected = gtk_tree_selection_count_selected_rows(attach_selection);
-                       
+
+               /* If no rows, or just one row is selected, right-click should
+                * open menu relevant to the row being right-clicked on. We
+                * achieve that by selecting the clicked row first. If more
+                * than one row is selected, we shouldn't modify the selection,
+                * as user may want to remove selected rows (attachments). */
+               if (attach_nr_selected < 2) {
+                       gtk_tree_selection_unselect_all(attach_selection);
+                       attach_nr_selected = 0;
+                       gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
+                                       event->x, event->y, &path, NULL, NULL, NULL);
+                       if (path != NULL) {
+                               gtk_tree_selection_select_path(attach_selection, path);
+                               gtk_tree_path_free(path);
+                               attach_nr_selected++;
+                       }
+               }
+
                cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Remove", (attach_nr_selected > 0));
-               cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected > 0));
+               /* Properties menu item makes no sense with more than one row
+                * selected, the properties dialog can only edit one attachment. */
+               cm_menu_set_sensitive_full(compose->ui_manager, "Popup/Compose/Properties", (attach_nr_selected == 1));
                        
                gtk_menu_popup(GTK_MENU(compose->popupmenu), NULL, NULL,
                               NULL, NULL, event->button, event->time);
@@ -9744,6 +9948,12 @@ static void compose_send_cb(GtkAction *action, gpointer data)
 {
        Compose *compose = (Compose *)data;
 
+#ifdef G_OS_UNIX
+       if (compose->exteditor_tag != -1) {
+               debug_print("ignoring send: external editor still open\n");
+               return;
+       }
+#endif
        if (prefs_common.work_offline && 
            !inc_offline_should_override(TRUE,
                _("Claws Mail needs network access in order "
@@ -9850,7 +10060,7 @@ gboolean compose_draft (gpointer data, guint action)
        /* chmod for security */
        if (change_file_mode_rw(fp, tmp) < 0) {
                FILE_OP_ERROR(tmp, "chmod");
-               g_warning("can't change file mode\n");
+               g_warning("can't change file mode");
        }
 
        /* Save draft infos */
@@ -10007,7 +10217,7 @@ warn_err:
                compose_close(compose);
                return TRUE;
        } else {
-               struct stat s;
+               GStatBuf s;
                gchar *path;
 
                path = folder_item_fetch_msg(draft, msgnum);
@@ -10242,7 +10452,8 @@ static void compose_close_cb(GtkAction *action, gpointer data)
                } else {
                        val = alertpanel(_("Save changes"),
                                 _("This message has been modified. Save the latest changes?"),
-                                _("_Don't save"), _("+_Save to Drafts"), GTK_STOCK_CANCEL);
+                                _("_Don't save"), g_strconcat("+", _("_Save to Drafts"), NULL),
+                               GTK_STOCK_CANCEL);
                }
                g_mutex_unlock(compose->mutex);
                switch (val) {
@@ -11035,6 +11246,7 @@ static void compose_toggle_encrypt_cb(GtkToggleAction *action, gpointer data)
 static void activate_privacy_system(Compose *compose, PrefsAccount *account, gboolean warn) 
 {
        g_free(compose->privacy_system);
+       g_free(compose->encdata);
 
        compose->privacy_system = g_strdup(account->default_privacy_system);
        compose_update_privacy_system_menu_item(compose, warn);
@@ -11068,11 +11280,8 @@ static void compose_attach_drag_received_cb (GtkWidget         *widget,
        GdkAtom type;
 
        type = gtk_selection_data_get_data_type(data);
-       if (((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
-#ifdef G_OS_WIN32
-        || (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND"))
-#endif
-          ) && gtk_drag_get_source_widget(context) != 
+       if ((gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list"))
+          && gtk_drag_get_source_widget(context) !=
                summary_get_main_widget(mainwindow_get_mainwindow()->summaryview)) {
                list = uri_list_extract_filenames(
                        (const gchar *)gtk_selection_data_get_data(data));
@@ -11155,11 +11364,7 @@ static void compose_insert_drag_received_cb (GtkWidget         *widget,
        /* strangely, testing data->type == gdk_atom_intern("text/uri-list", TRUE)
         * does not work */
        type = gtk_selection_data_get_data_type(data);
-#ifndef G_OS_WIN32
        if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "text/uri-list")) {
-#else
-       if (gdk_atom_name(type) && !strcmp(gdk_atom_name(type), "DROPFILES_DND")) {
-#endif
                AlertValue val = G_ALERTDEFAULT;
                const gchar* ddata = (const gchar *)gtk_selection_data_get_data(data);
 
@@ -11182,7 +11387,7 @@ static void compose_insert_drag_received_cb (GtkWidget          *widget,
                                val = alertpanel_full(_("Insert or attach?"),
                                         _("Do you want to insert the contents of the file(s) "
                                           "into the message body, or attach it to the email?"),
-                                         GTK_STOCK_CANCEL, _("+_Insert"), _("_Attach"),
+                                         GTK_STOCK_CANCEL, g_strconcat("+", _("_Insert"), NULL), _("_Attach"),
                                          TRUE, NULL, ALERT_QUESTION, G_ALERTALTERNATE);
                                break;
                        case COMPOSE_DND_INSERT: