2009-12-09 [mir] 3.7.3cvs35
[claws.git] / src / compose.c
index 97056e26ab13f03ce3d2891e1a7a6a9fd71d0745..56b990d1973ee1b5ed38c79d9884bf40a0d3c743 100644 (file)
@@ -162,6 +162,13 @@ typedef enum
        COMPOSE_QUOTE_SKIP
 } ComposeQuoteMode;
 
+typedef enum {
+    TO_FIELD_PRESENT,
+    SUBJECT_FIELD_PRESENT,
+    BODY_FIELD_PRESENT,
+    NO_FIELD_PRESENT
+} MailField;
+
 #define B64_LINE_SIZE          57
 #define B64_BUFFSIZE           77
 
@@ -207,7 +214,7 @@ static void compose_set_out_encoding                (Compose        *compose);
 static void compose_set_template_menu          (Compose        *compose);
 static void compose_destroy                    (Compose        *compose);
 
-static void compose_entries_set                        (Compose        *compose,
+static MailField compose_entries_set           (Compose        *compose,
                                                 const gchar    *mailto,
                                                 ComposeEntryType to_type);
 static gint compose_parse_header               (Compose        *compose,
@@ -936,6 +943,9 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
        gchar *mailto_from = NULL;
        PrefsAccount *mailto_account = NULL;
        MsgInfo* dummyinfo = NULL;
+       MailField mfield = NO_FIELD_PRESENT;
+       gchar* buf;
+       GtkTextMark *mark;
 
        /* check if mailto defines a from */
        if (mailto && *mailto != '\0') {
@@ -1017,7 +1027,7 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
 
        if (account->protocol != A_NNTP) {
                if (mailto && *mailto != '\0') {
-                       compose_entries_set(compose, mailto, COMPOSE_TO);
+                       mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
 
                } else if (item && item->prefs) {
                        if (item->prefs->enable_default_bcc) {
@@ -1044,9 +1054,9 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
        } else {
                if (mailto && *mailto != '\0') {
                        if (!strchr(mailto, '@'))
-                               compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
+                               mfield = compose_entries_set(compose, mailto, COMPOSE_NEWSGROUPS);
                        else
-                               compose_entries_set(compose, mailto, COMPOSE_TO);
+                               mfield = compose_entries_set(compose, mailto, COMPOSE_TO);
                } else if (item && FOLDER_CLASS(item->folder) == news_get_class()) {
                        compose_entry_append(compose, item->path, COMPOSE_NEWSGROUPS, PREF_FOLDER);
                }
@@ -1105,6 +1115,7 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
 
                        g_free(subject);
                        g_free(tmp);
+                       mfield = SUBJECT_FIELD_PRESENT;
                }
 
                if ( body_format
@@ -1132,6 +1143,11 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
                        quote_fmt_reset_vartable();
 
                        g_free(tmp);
+#ifdef USE_ENCHANT
+                       if (compose->gtkaspell->check_while_typing)
+                               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
+                       mfield = BODY_FIELD_PRESENT;
                }
 
        }
@@ -1158,8 +1174,39 @@ Compose *compose_generic_new(PrefsAccount *account, const gchar *mailto, FolderI
                compose_set_save_to(compose, folderidentifier);
                g_free(folderidentifier);
        }
-       
-       gtk_widget_grab_focus(compose->header_last->entry);
+
+       /* Place cursor according to provided input (mfield) */
+       switch (mfield) { 
+               case NO_FIELD_PRESENT:
+                       gtk_widget_grab_focus(compose->header_last->entry);
+                       break;
+               case TO_FIELD_PRESENT:
+                       buf = gtk_editable_get_chars(GTK_EDITABLE(compose->subject_entry), 0, -1);
+                       /*
+                        * buf will always contain an allocated string,
+                        * either empty or populated with some text
+                        */
+                       gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), buf);     
+                       gtk_widget_grab_focus(compose->subject_entry);
+                       break;
+               case SUBJECT_FIELD_PRESENT:
+                       textview = GTK_TEXT_VIEW(compose->text);
+                       textbuf = gtk_text_view_get_buffer(textview);
+                       mark = gtk_text_buffer_get_insert(textbuf);
+                       gtk_text_buffer_get_iter_at_mark(textbuf, &iter, mark);
+                       gtk_text_buffer_insert(textbuf, &iter, "", -1);
+                   /* 
+                    * SUBJECT_FIELD_PRESENT and BODY_FIELD_PRESENT
+                    * only defers where it comes to the variable body
+                    * is not null. If no body is present compose->text
+                    * will be null in which case you cannot place the
+                    * cursor inside the component so. An empty component
+                    * is therefore created before placing the cursor
+                    */
+               case BODY_FIELD_PRESENT:
+                       gtk_widget_grab_focus(compose->text);
+                       break;
+       }
 
        undo_unblock(compose->undostruct);
 
@@ -1564,6 +1611,10 @@ static Compose *compose_generic_reply(MsgInfo *msginfo,
                                          _("The body of the \"Reply\" template has an error at line %d."));
                compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
                quote_fmt_reset_vartable();
+#ifdef USE_ENCHANT
+               if (compose->gtkaspell->check_while_typing)
+                       gtkaspell_highlight_all(compose->gtkaspell);
+#endif
        }
 
        if (MSG_IS_ENCRYPTED(compose->replyinfo->flags)) {
@@ -1756,6 +1807,10 @@ Compose *compose_forward(PrefsAccount *account, MsgInfo *msginfo,
                compose_attach_parts(compose, msginfo);
 
                procmsg_msginfo_free(full_msginfo);
+#ifdef USE_ENCHANT
+               if (compose->gtkaspell->check_while_typing)
+                       gtkaspell_highlight_all(compose->gtkaspell);
+#endif
        }
 
        SIGNAL_BLOCK(textbuf);
@@ -2532,7 +2587,7 @@ void compose_toolbar_cb(gint action, gpointer data)
        }
 }
 
-static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
+static MailField compose_entries_set(Compose *compose, const gchar *mailto, ComposeEntryType to_type)
 {
        gchar *to = NULL;
        gchar *cc = NULL;
@@ -2542,12 +2597,15 @@ static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEn
        gchar *temp = NULL;
        gsize  len = 0;
        gchar **attach = NULL;
+       MailField mfield = NO_FIELD_PRESENT;
 
        /* get mailto parts but skip from */
        scan_mailto_url(mailto, NULL, &to, &cc, &bcc, &subject, &body, &attach);
 
-       if (to)
+       if (to) {
                compose_entry_append(compose, to, to_type, PREF_MAILTO);
+               mfield = TO_FIELD_PRESENT;
+       }
        if (cc)
                compose_entry_append(compose, cc, COMPOSE_CC, PREF_MAILTO);
        if (bcc)
@@ -2560,6 +2618,7 @@ static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEn
                } else {
                        gtk_entry_set_text(GTK_ENTRY(compose->subject_entry), subject);
                }
+               mfield = SUBJECT_FIELD_PRESENT;
        }
        if (body) {
                GtkTextView *text = GTK_TEXT_VIEW(compose->text);
@@ -2585,6 +2644,7 @@ static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEn
                compose->autowrap = prev_autowrap;
                if (compose->autowrap)
                        compose_wrap_all(compose);
+               mfield = BODY_FIELD_PRESENT;
        }
 
        if (attach) {
@@ -2620,6 +2680,8 @@ static void compose_entries_set(Compose *compose, const gchar *mailto, ComposeEn
        g_free(subject);
        g_free(body);
        g_strfreev(attach);
+       
+       return mfield;
 }
 
 static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
@@ -2699,12 +2761,15 @@ static gint compose_parse_header(Compose *compose, MsgInfo *msginfo)
                hentry[H_FOLLOWUP_TO].body = NULL;
        }
        if (hentry[H_LIST_POST].body != NULL) {
-               gchar *to = NULL;
+               gchar *to = NULL, *start = NULL;
 
                extract_address(hentry[H_LIST_POST].body);
                if (hentry[H_LIST_POST].body[0] != '\0') {
-                       scan_mailto_url(hentry[H_LIST_POST].body,
+                       start = strstr(hentry[H_LIST_POST].body, "mailto:");
+                       
+                       scan_mailto_url(start ? start : hentry[H_LIST_POST].body,
                                        NULL, &to, NULL, NULL, NULL, NULL, NULL);
+
                        if (to) {
                                g_free(compose->ml_post);
                                compose->ml_post = to;
@@ -2971,22 +3036,6 @@ static gboolean is_subscription(const gchar *ml_post, const gchar *from)
        return result;
 }
 
-static gboolean same_address(const gchar *addr1, const gchar *addr2)
-{
-       gchar *my_addr1, *my_addr2;
-       
-       if (!addr1 || !addr2)
-               return FALSE;
-
-       Xstrdup_a(my_addr1, addr1, return FALSE);
-       Xstrdup_a(my_addr2, addr2, return FALSE);
-       
-       extract_address(my_addr1);
-       extract_address(my_addr2);
-       
-       return !strcasecmp(my_addr1, my_addr2);
-}
-
 static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
                                    gboolean to_all, gboolean to_ml,
                                    gboolean to_sender,
@@ -2996,7 +3045,6 @@ static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
        GSList *cur;
        gchar *from = NULL;
        gchar *replyto = NULL;
-       GHashTable *to_table;
 
        gboolean reply_to_ml = FALSE;
        gboolean default_reply_to = FALSE;
@@ -3033,8 +3081,7 @@ static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
                                compose_entry_append(compose,
                                           compose->ml_post,
                                           COMPOSE_TO, PREF_ML);
-                               if (compose->replyto
-                               &&  !same_address(compose->ml_post, compose->replyto))
+                               if (compose->replyto)
                                        compose_entry_append(compose,
                                                compose->replyto,
                                                COMPOSE_CC, PREF_ML);
@@ -3160,31 +3207,6 @@ static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
        cc_list = address_list_append_with_comments(cc_list, msginfo->to);
        cc_list = address_list_append_with_comments(cc_list, compose->cc);
 
-       to_table = g_hash_table_new(g_str_hash, g_str_equal);
-       if (replyto)
-               g_hash_table_insert(to_table, g_utf8_strdown(replyto, -1), GINT_TO_POINTER(1));
-       if (compose->account) {
-               g_hash_table_insert(to_table, g_utf8_strdown(compose->account->address, -1),
-                                   GINT_TO_POINTER(1));
-       }
-       /* remove address on To: and that of current account */
-       for (cur = cc_list; cur != NULL; ) {
-               GSList *next = cur->next;
-               gchar *addr;
-
-               addr = g_utf8_strdown(cur->data, -1);
-               extract_address(addr);
-
-               if (GPOINTER_TO_INT(g_hash_table_lookup(to_table, addr)) == 1)
-                       cc_list = g_slist_remove(cc_list, cur->data);
-               else
-                       g_hash_table_insert(to_table, addr, GINT_TO_POINTER(1));
-
-               cur = next;
-       }
-       hash_free_strings(to_table);
-       g_hash_table_destroy(to_table);
-
        if (cc_list) {
                for (cur = cc_list; cur != NULL; cur = cur->next)
                        compose_entry_append(compose, (gchar *)cur->data,
@@ -3192,7 +3214,6 @@ static void compose_reply_set_entry(Compose *compose, MsgInfo *msginfo,
                slist_free_strings(cc_list);
                g_slist_free(cc_list);
        }
-
 }
 
 #define SET_ENTRY(entry, str) \
@@ -4588,12 +4609,19 @@ compose_current_mail_account(void)
 static void compose_select_account(Compose *compose, PrefsAccount *account,
                                   gboolean init)
 {
-       gchar *from = NULL, *header;
+       gchar *from = NULL, *email, *header;
        ComposeHeaderEntry *header_entry;
 
        cm_return_if_fail(account != NULL);
 
+       email = g_utf8_strdown(compose->account->address, -1);
+       g_hash_table_remove(compose->email_hashtable, email);
+       g_free(email);
+
        compose->account = account;
+       g_hash_table_insert(compose->email_hashtable,
+                           g_utf8_strdown(account->address, -1),
+                           GUINT_TO_POINTER(1)); 
 
        if (account->name && *account->name) {
                gchar *buf;
@@ -6501,17 +6529,42 @@ static void compose_create_header_entry(Compose *compose)
 static void compose_add_header_entry(Compose *compose, const gchar *header,
                                gchar *text, ComposePrefType pref_type) 
 {
-       ComposeHeaderEntry *last_header;
+       ComposeHeaderEntry *last_header = compose->header_last;
+       gchar *tmp = g_strdup(text), *email;
+       
+       extract_address(tmp);
+       email = g_utf8_strdown(tmp, -1);
+       
+       if (!(!strcmp(header, "To:") && !strcasecmp(compose->account->address, email))
+           && g_hash_table_lookup(compose->email_hashtable, email) != NULL) {
+               debug_print("Ignoring duplicate address - %s %s, pref_type: %d\n",
+                               header, text, (gint) pref_type);
+               g_free(email);
+               g_free(tmp);
+               return;
+       }
        
-       last_header = compose->header_last;
-
        combobox_select_by_text(GTK_COMBO_BOX(last_header->combo), header);
        gtk_entry_set_text(GTK_ENTRY(last_header->entry), text);
        last_header->type = pref_type;
+
+       g_hash_table_insert(compose->email_hashtable, email,
+                               GUINT_TO_POINTER(1));
+       g_free(tmp);
 }
 
-static void compose_destroy_headerentry(ComposeHeaderEntry *headerentry)
+static void compose_destroy_headerentry(Compose *compose, 
+                                       ComposeHeaderEntry *headerentry)
 {
+       gchar *text = gtk_editable_get_chars(GTK_EDITABLE(headerentry->entry), 0, -1);
+       gchar *email;
+
+       extract_address(text);
+       email = g_utf8_strdown(text, -1);
+       g_hash_table_remove(compose->email_hashtable, email);
+       g_free(text);
+       g_free(email);
+       
        gtk_widget_destroy(headerentry->combo);
        gtk_widget_destroy(headerentry->entry);
        gtk_widget_destroy(headerentry->button);
@@ -6523,7 +6576,7 @@ static void compose_remove_header_entries(Compose *compose)
 {
        GSList *list;
        for (list = compose->header_list; list; list = list->next)
-               compose_destroy_headerentry((ComposeHeaderEntry *)list->data);
+               compose_destroy_headerentry(compose, (ComposeHeaderEntry *)list->data);
 
        compose->header_last = NULL;
        g_slist_free(compose->header_list);
@@ -7403,6 +7456,9 @@ static Compose *compose_create(PrefsAccount *account,
        compose->replyinfo  = NULL;
        compose->fwdinfo    = NULL;
 
+       compose->email_hashtable = g_hash_table_new_full(g_str_hash,
+                               g_str_equal, (GDestroyNotify) g_free, NULL);
+       
        compose->replyto     = NULL;
        compose->cc          = NULL;
        compose->bcc         = NULL;
@@ -8049,6 +8105,11 @@ static void compose_template_apply(Compose *compose, Template *tmpl,
        compose_attach_from_list(compose, quote_fmt_get_attachments_list(), FALSE);
        quote_fmt_reset_vartable();
        compose_changed_cb(NULL, compose);
+
+#ifdef USE_ENCHANT
+       if (compose->gtkaspell->check_while_typing)
+               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
 }
 
 static void compose_template_apply_fields(Compose *compose, Template *tmpl)
@@ -8183,6 +8244,8 @@ static void compose_destroy(Compose *compose)
        slist_free_strings(compose->header_list);
        g_slist_free(compose->header_list);
 
+       g_hash_table_destroy(compose->email_hashtable);
+
        procmsg_msginfo_free(compose->targetinfo);
        procmsg_msginfo_free(compose->replyinfo);
        procmsg_msginfo_free(compose->fwdinfo);
@@ -8993,7 +9056,7 @@ static void account_activated(GtkComboBox *optmenu, gpointer data)
                        ComposeHeaderEntry *hentry=(ComposeHeaderEntry *)list->data;
                        
                        if (hentry->type == PREF_ACCOUNT || !list->next) {
-                               compose_destroy_headerentry(hentry);
+                               compose_destroy_headerentry(compose, hentry);
                                continue;
                        }
                        
@@ -9016,7 +9079,7 @@ static void account_activated(GtkComboBox *optmenu, gpointer data)
                                                &style->base[GTK_STATE_NORMAL]);
 
                        saved_list = g_slist_append(saved_list, state);
-                       compose_destroy_headerentry(hentry);
+                       compose_destroy_headerentry(compose, hentry);
                }
 
                compose->header_last = NULL;
@@ -9509,6 +9572,7 @@ static void compose_insert_file_cb(GtkAction *action, gpointer data)
 {
        Compose *compose = (Compose *)data;
        GList *file_list;
+       gint files_inserted = 0;
 
        file_list = filesel_select_multiple_files_open(_("Select file"));
 
@@ -9529,7 +9593,8 @@ static void compose_insert_file_cb(GtkAction *action, gpointer data)
                                alertpanel_error(_("File '%s' contained invalid characters\n"
                                                        "for the current encoding, insertion may be incorrect."),
                                                        shortfile);
-                       }
+                       } else if (res == COMPOSE_INSERT_SUCCESS)
+                               files_inserted++;
 
                        g_free(shortfile);
                        g_free(filedup);
@@ -9537,6 +9602,11 @@ static void compose_insert_file_cb(GtkAction *action, gpointer data)
                }
                g_list_free(file_list);
        }
+
+#ifdef USE_ENCHANT     
+       if (files_inserted > 0 && compose->gtkaspell->check_while_typing)
+               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
 }
 
 static void compose_insert_sig_cb(GtkAction *action, gpointer data)
@@ -9815,6 +9885,12 @@ static void compose_paste_cb(GtkAction *action, gpointer data)
                                prefs_common.linewrap_pastes,
                                GDK_SELECTION_CLIPBOARD, NULL);
        UNBLOCK_WRAP();
+
+#ifdef USE_ENCHANT
+       if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
+           compose->gtkaspell->check_while_typing)
+               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
 }
 
 static void compose_paste_as_quote_cb(GtkAction *action, gpointer data)
@@ -9858,6 +9934,12 @@ static void compose_paste_no_wrap_cb(GtkAction *action, gpointer data)
                entry_paste_clipboard(compose, compose->focused_editable, FALSE,
                        GDK_SELECTION_CLIPBOARD, NULL);
        UNBLOCK_WRAP();
+
+#ifdef USE_ENCHANT
+       if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
+           compose->gtkaspell->check_while_typing)
+               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
 }
 
 static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
@@ -9874,6 +9956,12 @@ static void compose_paste_wrap_cb(GtkAction *action, gpointer data)
                entry_paste_clipboard(compose, compose->focused_editable, TRUE,
                        GDK_SELECTION_CLIPBOARD, NULL);
        UNBLOCK_WRAP();
+
+#ifdef USE_ENCHANT
+       if (GTK_WIDGET_HAS_FOCUS(compose->text) &&
+           compose->gtkaspell->check_while_typing)
+               gtkaspell_highlight_all(compose->gtkaspell);
+#endif
 }
 
 static void compose_allsel_cb(GtkAction *action, gpointer data)