Bug #2355: don't send while external editor is open
[claws.git] / src / compose.c
index ed92268122df1c84c43ecc1cec53e32e65132ac4..2e41dbee1af533dfc7d3b98841902e1b3461ae09 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,
@@ -3608,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? */
@@ -3734,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);
 
@@ -5024,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;
                }
@@ -5056,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;
                }
@@ -5089,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?"));
@@ -5612,7 +5618,8 @@ static gint compose_write_to_file(Compose *compose, FILE *fp, gint action, gbool
                        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);
 
@@ -5899,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;
@@ -8012,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 =
@@ -8147,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) {
@@ -9277,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);
@@ -9327,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);
@@ -9347,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;
 
@@ -9368,10 +9425,20 @@ 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'",
@@ -9491,6 +9558,11 @@ 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;
 }
@@ -9507,7 +9579,29 @@ static void compose_set_ext_editor_sensitive(Compose *compose,
        cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/WrapAllLines", sensitive);
        cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Edit/ExtEditor", sensitive);
 
-       gtk_widget_set_sensitive(compose->text,                       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)
@@ -9525,6 +9619,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 */
 
 /**
@@ -9825,6 +9932,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 "
@@ -10323,7 +10436,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) {
@@ -11257,7 +11371,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: