Make Up key bring focus to Subject line, if the cursor is on the first line of body...
[claws.git] / src / compose.c
index 48170ae1324197eca7dff36e0f130b1ad3838e83..7a1f177319cdde802bf09b3450fb655646492588 100644 (file)
@@ -182,6 +182,9 @@ typedef enum {
 
 #define MAX_REFERENCES_LEN     999
 
+#define COMPOSE_DRAFT_TIMEOUT_UNSET -1
+#define COMPOSE_DRAFT_TIMEOUT_FORBIDDEN -2
+
 static GList *compose_list = NULL;
 static GSList *extra_headers = NULL;
 
@@ -1266,7 +1269,8 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
        if (prefs_common.auto_exteditor)
                compose_exec_ext_editor(compose);
 
-       compose->draft_timeout_tag = -1;
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
+
        SCROLL_TO_CURSOR(compose);
 
        compose->modified = FALSE;
@@ -1707,7 +1711,7 @@ static Compose *compose_generic_reply(MsgInfo *msginfo,
        compose_set_title(compose);
 
        compose->updating = FALSE;
-       compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
        SCROLL_TO_CURSOR(compose);
        
        if (compose->deferred_destroy) {
@@ -1910,7 +1914,7 @@ Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
        compose_set_title(compose);
 
        compose->updating = FALSE;
-       compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
        SCROLL_TO_CURSOR(compose);
 
        if (compose->deferred_destroy) {
@@ -2058,7 +2062,7 @@ static Compose *compose_forward_multiple(PrefsAccount *account, GSList *msginfo_
        compose_set_title(compose);
 
        compose->updating = FALSE;
-       compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
        SCROLL_TO_CURSOR(compose);
 
        if (compose->deferred_destroy) {
@@ -2131,21 +2135,21 @@ static void compose_colorize_signature(Compose *compose)
                                G_CALLBACK(text_inserted),              \
                                compose);                               \
 }
-#define UNBLOCK_WRAP() {                                               \
-       compose->autowrap = prev_autowrap;                              \
-       if (compose->autowrap) {                                        \
-               gint old = compose->draft_timeout_tag;                  \
-               compose->draft_timeout_tag = -2;                        \
-               compose_wrap_all(compose);                              \
-               compose->draft_timeout_tag = old;                       \
-       }                                                               \
-                                                                       \
-       g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
-                               G_CALLBACK(compose_changed_cb),         \
-                               compose);                               \
-       g_signal_handlers_unblock_by_func(G_OBJECT(buffer),             \
-                               G_CALLBACK(text_inserted),              \
-                               compose);                               \
+#define UNBLOCK_WRAP() {                                                       \
+       compose->autowrap = prev_autowrap;                                      \
+       if (compose->autowrap) {                                                \
+               gint old = compose->draft_timeout_tag;                          \
+               compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN;   \
+               compose_wrap_all(compose);                                      \
+               compose->draft_timeout_tag = old;                               \
+       }                                                                       \
+                                                                               \
+       g_signal_handlers_unblock_by_func(G_OBJECT(buffer),                     \
+                               G_CALLBACK(compose_changed_cb),                 \
+                               compose);                                       \
+       g_signal_handlers_unblock_by_func(G_OBJECT(buffer),                     \
+                               G_CALLBACK(text_inserted),                      \
+                               compose);                                       \
 }
 
 Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
@@ -2408,7 +2412,7 @@ Compose *compose_reedit(MsgInfo *msginfo, gboolean batch)
        compose_set_title(compose);
 
        compose->updating = FALSE;
-       compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
        SCROLL_TO_CURSOR(compose);
 
        if (compose->deferred_destroy) {
@@ -2518,7 +2522,7 @@ Compose *compose_redirect(PrefsAccount *account, MsgInfo *msginfo,
        compose->modified = FALSE;
        compose_set_title(compose);
        compose->updating = FALSE;
-       compose->draft_timeout_tag = -1; /* desinhibit auto-drafting after loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET; /* desinhibit auto-drafting after loading */
        SCROLL_TO_CURSOR(compose);
 
        if (compose->deferred_destroy) {
@@ -2672,6 +2676,9 @@ void compose_toolbar_cb(gint action, gpointer data)
        case A_SIG:
                compose_insert_sig(compose, FALSE);
                break;
+       case A_REP_SIG:
+               compose_insert_sig(compose, TRUE);
+               break;
        case A_EXTEDITOR:
                compose_ext_editor_cb(NULL, compose);
                break;
@@ -3279,16 +3286,18 @@ static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
                        gchar *tmp1 = NULL;
                        if (!msginfo->from)
                                return;
-                       Xstrdup_a(tmp1, msginfo->from, return);
-                       extract_address(tmp1);
-                       if (to_all || to_sender ||
-                           !account_find_from_address(tmp1, FALSE))
+                       if (to_sender)
+                               compose_entry_append(compose, msginfo->from,
+                                                    COMPOSE_TO, PREF_NONE);
+                       else if (to_all) {
+                               Xstrdup_a(tmp1, msginfo->from, return);
+                               extract_address(tmp1);
                                compose_entry_append(compose,
-                                (compose->replyto && !to_sender)
-                                         ? compose->replyto :
-                                         msginfo->from ? msginfo->from : "",
+                                (!account_find_from_address(tmp1, FALSE))
+                                         ? msginfo->from :
+                                         msginfo->to,
                                          COMPOSE_TO, PREF_NONE);
-                       else if (!to_all && !to_sender) {
+                       } else {
                                if (!folder_has_parent_of_type(msginfo->folder, F_QUEUE) &&
                                    !folder_has_parent_of_type(msginfo->folder, F_OUTBOX) &&
                                    !folder_has_parent_of_type(msginfo->folder, F_DRAFT)) {
@@ -3668,9 +3677,18 @@ static gboolean compose_attach_append(Compose *compose, const gchar *file,
                alertpanel_error("Can't get file size of %s\n", filename);
                return FALSE;
        }
-       if (size == 0) {
-               alertpanel_error(_("File %s is empty."), filename);
-               return FALSE;
+
+       /* In batch mode, we allow 0-length files to be attached no questions asked */
+       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,
+                               NULL, ALERT_WARNING, G_ALERTDEFAULT);
+               g_free(msg);
+
+               if (aval != G_ALERTALTERNATE) {
+                       return FALSE;
+               }
        }
        if ((fp = g_fopen(file, "rb")) == NULL) {
                alertpanel_error(_("Can't read %s."), filename);
@@ -4298,7 +4316,7 @@ static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_it
        if (force) {
                modified = TRUE;
        }
-       if (compose->draft_timeout_tag == -2) {
+       if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
                modified = TRUE;
        }
 
@@ -4315,7 +4333,7 @@ static gboolean compose_beautify_paragraph(Compose *compose, GtkTextIter *par_it
        }
 
 
-       if (compose->draft_timeout_tag == -2) {
+       if (compose->draft_timeout_tag == COMPOSE_DRAFT_TIMEOUT_FORBIDDEN) {
                if (gtk_text_iter_ends_line(&iter)) {
                        while (gtk_text_iter_ends_line(&iter) &&
                               gtk_text_iter_forward_line(&iter))
@@ -5334,6 +5352,8 @@ static gint compose_redirect_write_headers(Compose *compose, FILE *fp)
                }
                generate_msgid(buf, sizeof(buf), addr);
                err |= (fprintf(fp, "Resent-Message-ID: <%s>\n", buf) < 0);
+               if (compose->msgid)
+                       g_free(compose->msgid);
                compose->msgid = g_strdup(buf);
        } else {
                compose->msgid = NULL;
@@ -6410,6 +6430,8 @@ static gchar *compose_get_header(Compose *compose)
                }
                generate_msgid(buf, sizeof(buf), addr);
                g_string_append_printf(header, "Message-ID: <%s>\n", buf);
+               if (compose->msgid)
+                       g_free(compose->msgid);
                compose->msgid = g_strdup(buf);
        } else {
                compose->msgid = NULL;
@@ -6450,7 +6472,8 @@ static gchar *compose_get_header(Compose *compose)
                        gtk_major_version, gtk_minor_version, gtk_micro_version,
                        TARGET_ALIAS);
        }
-       if (g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
+       if (compose->account->gen_xmailer &&
+           g_slist_length(compose->newsgroup_list) && !IS_IN_CUSTOM_HEADER("X-Newsreader")) {
                g_string_append_printf(header, "X-Newsreader: %s (GTK+ %d.%d.%d; %s)\n",
                        prog_version,
                        gtk_major_version, gtk_minor_version, gtk_micro_version,
@@ -6800,7 +6823,7 @@ static void compose_create_header_entry(Compose *compose)
        gtk_table_attach(GTK_TABLE(compose->header_table), combo, 0, 1,
                        compose->header_nextrow, compose->header_nextrow+1,
                        GTK_SHRINK, GTK_FILL, 0, 0);
-       if (compose->header_last && (compose->draft_timeout_tag != -2)) {
+       if (compose->header_last && (compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN)) {
                const gchar *last_header_entry = gtk_entry_get_text(
                                GTK_ENTRY(gtk_bin_get_child(GTK_BIN((compose->header_last->combo)))));
                string = headers;
@@ -7634,7 +7657,6 @@ static Compose *compose_create(PrefsAccount *account,
        
        /* Notebook */
        notebook = gtk_notebook_new();
-       gtk_widget_set_size_request(notebook, -1, prefs_common.compose_notebook_height);
        gtk_widget_show(notebook);
 
        /* header labels and entries */
@@ -7674,7 +7696,7 @@ static Compose *compose_create(PrefsAccount *account,
        gtk_container_set_border_width(GTK_CONTAINER(subject), 0);
        gtk_widget_show(subject);
 
-       label = gtk_label_new(_("Subject:"));
+       label = gtk_label_new_with_mnemonic(_("Subject:"));
        gtk_box_pack_start(GTK_BOX(subject), label, FALSE, FALSE, 0);
        gtk_widget_show(label);
 
@@ -7686,6 +7708,7 @@ static Compose *compose_create(PrefsAccount *account,
        gtk_box_pack_start(GTK_BOX(subject), subject_entry, TRUE, TRUE, 0);
        g_signal_connect_after(G_OBJECT(subject_entry), "grab_focus",
                         G_CALLBACK(compose_grab_focus_cb), compose);
+       gtk_label_set_mnemonic_widget(GTK_LABEL(label), subject_entry);
        gtk_widget_show(subject_entry);
        compose->subject_entry = subject_entry;
        gtk_container_add(GTK_CONTAINER(subject_frame), subject);
@@ -7762,8 +7785,9 @@ static Compose *compose_create(PrefsAccount *account,
        /* pane between attach clist and text */
        paned = gtk_vpaned_new();
        gtk_container_add(GTK_CONTAINER(vbox2), paned);
-       gtk_paned_add1(GTK_PANED(paned), notebook);
-       gtk_paned_add2(GTK_PANED(paned), edit_vbox);
+       gtk_paned_pack1(GTK_PANED(paned), notebook, FALSE, FALSE);
+       gtk_paned_pack2(GTK_PANED(paned), edit_vbox, TRUE, FALSE);
+       gtk_paned_set_position(GTK_PANED(paned), prefs_common.compose_notebook_height);
        gtk_widget_show_all(paned);
 
 
@@ -7867,7 +7891,7 @@ static Compose *compose_create(PrefsAccount *account,
        compose->exteditor_file    = NULL;
        compose->exteditor_pid     = -1;
        compose->exteditor_tag     = -1;
-       compose->draft_timeout_tag = -2; /* inhibit auto-drafting while loading */
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_FORBIDDEN; /* inhibit auto-drafting while loading */
 
 #if USE_ENCHANT
        cm_menu_set_sensitive_full(compose->ui_manager, "Menu/Spelling", FALSE);
@@ -9612,7 +9636,7 @@ static void compose_send_cb(GtkAction *action, gpointer data)
        
        if (compose->draft_timeout_tag >= 0) { /* CLAWS: disable draft timeout */
                g_source_remove(compose->draft_timeout_tag);
-               compose->draft_timeout_tag = -1;
+               compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
        }
 
        compose_send(compose);
@@ -9773,9 +9797,11 @@ gboolean compose_draft (gpointer data, guint action)
                goto warn_err;
        }
        
+       flag.perm_flags = MSG_NEW|MSG_UNREAD;
        if (compose->targetinfo) {
                target_locked = MSG_IS_LOCKED(compose->targetinfo->flags);
-               flag.perm_flags = target_locked?MSG_LOCKED:0;
+               if (target_locked) 
+                       flag.perm_flags |= MSG_LOCKED;
        }
        flag.tmp_flags = MSG_DRAFT;
 
@@ -9836,9 +9862,9 @@ warn_err:
        if (newmsginfo) {
                procmsg_msginfo_unset_flags(newmsginfo, ~0, ~0);
                if (target_locked)
-                       procmsg_msginfo_set_flags(newmsginfo, MSG_LOCKED, MSG_DRAFT);
+                       procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD|MSG_LOCKED, MSG_DRAFT);
                else
-                       procmsg_msginfo_set_flags(newmsginfo, 0, MSG_DRAFT);
+                       procmsg_msginfo_set_flags(newmsginfo, MSG_NEW|MSG_UNREAD, MSG_DRAFT);
                if (compose_use_attach(compose) && action != COMPOSE_AUTO_SAVE)
                        procmsg_msginfo_set_flags(newmsginfo, 0,
                                                  MSG_HAS_ATTACHMENT);
@@ -10055,6 +10081,14 @@ void compose_close_toolbar(Compose *compose)
        compose_close_cb(NULL, compose);
 }
 
+static gboolean compose_can_autosave(Compose *compose)
+{
+       if (compose->privacy_system && compose->use_encryption)
+               return prefs_common.autosave && prefs_common.autosave_encrypted;
+       else
+               return prefs_common.autosave;
+}
+
 static void compose_close_cb(GtkAction *action, gpointer data)
 {
        Compose *compose = (Compose *)data;
@@ -10089,8 +10123,8 @@ static void compose_close_cb(GtkAction *action, gpointer data)
                g_mutex_unlock(compose->mutex);
                switch (val) {
                case G_ALERTDEFAULT:
-                       if (prefs_common.autosave && !reedit)
-                               compose_remove_draft(compose);                  
+                       if (compose_can_autosave(compose) && !reedit)
+                               compose_remove_draft(compose);
                        break;
                case G_ALERTALTERNATE:
                        compose_draft(data, COMPOSE_QUIT_EDITING);
@@ -10971,6 +11005,10 @@ static gboolean completion_set_focus_to_subject
                                         GdkEventKey  *event,
                                         Compose      *compose)
 {
+       GtkTextBuffer *buffer;
+       GtkTextMark *mark;
+       GtkTextIter iter;
+
        cm_return_val_if_fail(compose != NULL, FALSE);
 
        /* make backtab move to subject field */
@@ -10978,6 +11016,24 @@ static gboolean completion_set_focus_to_subject
                gtk_widget_grab_focus(compose->subject_entry);
                return TRUE;
        }
+
+       // Up key should also move the focus to subject field, if the cursor
+       // is on the first line.
+       if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) {
+               buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
+               g_return_val_if_fail(buffer != NULL, FALSE);
+
+               mark = gtk_text_buffer_get_mark(buffer, "insert");
+               g_return_val_if_fail(mark != NULL, FALSE);
+
+               gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
+
+               if (gtk_text_iter_get_line(&iter) == 0) {
+                       gtk_widget_grab_focus(compose->subject_entry);
+                       return TRUE;
+               }
+       }
+
        return FALSE;
 }
 
@@ -11164,7 +11220,8 @@ static gboolean scroll_postpone(gpointer data)
 {
        Compose *compose = (Compose *)data;
 
-       cm_return_val_if_fail(!compose->batch, FALSE);
+       if (compose->batch)
+               return FALSE;
 
        GTK_EVENTS_FLUSH();
        compose_show_first_last_header(compose, FALSE);
@@ -11187,7 +11244,7 @@ static void compose_headerentry_changed_cb(GtkWidget *entry,
 
 static gboolean compose_defer_auto_save_draft(Compose *compose)
 {
-       compose->draft_timeout_tag = -1;
+       compose->draft_timeout_tag = COMPOSE_DRAFT_TIMEOUT_UNSET;
        compose_draft((gpointer)compose, COMPOSE_AUTO_SAVE);
        return FALSE;
 }
@@ -11197,7 +11254,10 @@ static void compose_show_first_last_header(Compose *compose, gboolean show_first
        GtkAdjustment *vadj;
 
        cm_return_if_fail(compose);
-       cm_return_if_fail(!compose->batch);
+
+       if(compose->batch)
+               return;
+
        cm_return_if_fail(GTK_IS_WIDGET(compose->header_table));
        cm_return_if_fail(GTK_IS_VIEWPORT(gtk_widget_get_parent(compose->header_table)));
        vadj = gtk_viewport_get_vadjustment(GTK_VIEWPORT(
@@ -11306,9 +11366,9 @@ static void text_inserted(GtkTextBuffer *buffer, GtkTextIter *iter,
                                          compose);
        g_signal_stop_emission_by_name(G_OBJECT(buffer), "insert-text");
 
-       if (prefs_common.autosave && 
+       if (compose_can_autosave(compose) && 
            gtk_text_buffer_get_char_count(buffer) % prefs_common.autosave_length == 0 &&
-           compose->draft_timeout_tag != -2 /* disabled while loading */)
+           compose->draft_timeout_tag != COMPOSE_DRAFT_TIMEOUT_FORBIDDEN /* disabled while loading */)
                compose->draft_timeout_tag = g_timeout_add
                        (500, (GSourceFunc) compose_defer_auto_save_draft, compose);
 }
@@ -11420,16 +11480,28 @@ gboolean compose_close(Compose *compose)
 {
        gint x, y;
 
+       cm_return_val_if_fail(compose, FALSE);
+
        if (!g_mutex_trylock(compose->mutex)) {
                /* we have to wait for the (possibly deferred by auto-save)
                 * drafting to be done, before destroying the compose under
                 * it. */
                debug_print("waiting for drafting to finish...\n");
                compose_allow_user_actions(compose, FALSE);
-               g_timeout_add (500, (GSourceFunc) compose_close, compose);
+               if (compose->close_timeout_tag == 0) {
+                       compose->close_timeout_tag = 
+                               g_timeout_add (500, (GSourceFunc) compose_close,
+                               compose);
+               }
                return FALSE;
        }
-       cm_return_val_if_fail(compose, FALSE);
+       
+       if (compose->close_timeout_tag) {
+               /* let the close be done by the deferred callback */
+               g_mutex_unlock(compose->mutex);
+               return FALSE;
+       }
+
        gtkut_widget_get_uposition(compose->window, &x, &y);
        if (!compose->batch) {
                prefs_common.compose_x = x;