2007-07-02 [colin] 2.10.0cvs1
authorColin Leroy <colin@colino.net>
Mon, 2 Jul 2007 16:35:40 +0000 (16:35 +0000)
committerColin Leroy <colin@colino.net>
Mon, 2 Jul 2007 16:35:40 +0000 (16:35 +0000)
* src/Makefile.am
* src/compose.c
* src/edittags.c
* src/edittags.h
* src/filtering.c
* src/folder.c
* src/folder.h
* src/headerview.c
* src/headerview.h
* src/main.c
* src/mainwindow.c
* src/mainwindow.h
* src/manual.h
* src/matcher.c
* src/matcher.h
* src/matcher_parser_parse.y
* src/mimeview.c
* src/msgcache.c
* src/msgcache.h
* src/prefs_common.c
* src/prefs_common.h
* src/prefs_filtering_action.c
* src/prefs_matcher.c
* src/prefs_summary_column.c
* src/procmsg.c
* src/procmsg.h
* src/quote_fmt.c
* src/quote_fmt_lex.l
* src/quote_fmt_parse.y
* src/summaryview.c
* src/summaryview.h
* src/textview.c
* src/common/Makefile.am
* src/common/defs.h
* src/common/tags.c
* src/common/tags.h
* src/gtk/quicksearch.c
* src/gtk/quicksearch.h
Add Tags implementation. Tags are arbitrary labels
that can be applied to messages. It is possible
to create, edit, remove tags; apply them to mails;
filter on tags or tag presence; apply or unset
tags via filtering actions; reference tags in
reply templates.

41 files changed:
ChangeLog
PATCHSETS
configure.ac
src/Makefile.am
src/common/Makefile.am
src/common/defs.h
src/common/tags.c [new file with mode: 0644]
src/common/tags.h [new file with mode: 0644]
src/compose.c
src/edittags.c [new file with mode: 0644]
src/edittags.h [new file with mode: 0644]
src/filtering.c
src/folder.c
src/folder.h
src/gtk/quicksearch.c
src/gtk/quicksearch.h
src/headerview.c
src/headerview.h
src/main.c
src/mainwindow.c
src/mainwindow.h
src/manual.h
src/matcher.c
src/matcher.h
src/matcher_parser_parse.y
src/mimeview.c
src/msgcache.c
src/msgcache.h
src/prefs_common.c
src/prefs_common.h
src/prefs_filtering_action.c
src/prefs_matcher.c
src/prefs_summary_column.c
src/procmsg.c
src/procmsg.h
src/quote_fmt.c
src/quote_fmt_lex.l
src/quote_fmt_parse.y
src/summaryview.c
src/summaryview.h
src/textview.c

index 59926d1..f68b5d9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,50 @@
+2007-07-02 [colin]     2.10.0cvs1
+
+       * src/Makefile.am
+       * src/compose.c
+       * src/edittags.c
+       * src/edittags.h
+       * src/filtering.c
+       * src/folder.c
+       * src/folder.h
+       * src/headerview.c
+       * src/headerview.h
+       * src/main.c
+       * src/mainwindow.c
+       * src/mainwindow.h
+       * src/manual.h
+       * src/matcher.c
+       * src/matcher.h
+       * src/matcher_parser_parse.y
+       * src/mimeview.c
+       * src/msgcache.c
+       * src/msgcache.h
+       * src/prefs_common.c
+       * src/prefs_common.h
+       * src/prefs_filtering_action.c
+       * src/prefs_matcher.c
+       * src/prefs_summary_column.c
+       * src/procmsg.c
+       * src/procmsg.h
+       * src/quote_fmt.c
+       * src/quote_fmt_lex.l
+       * src/quote_fmt_parse.y
+       * src/summaryview.c
+       * src/summaryview.h
+       * src/textview.c
+       * src/common/Makefile.am
+       * src/common/defs.h
+       * src/common/tags.c
+       * src/common/tags.h
+       * src/gtk/quicksearch.c
+       * src/gtk/quicksearch.h
+               Add Tags implementation. Tags are arbitrary labels
+               that can be applied to messages. It is possible 
+               to create, edit, remove tags; apply them to mails;
+               filter on tags or tag presence; apply or unset
+               tags via filtering actions; reference tags in
+               reply templates.
+
 2007-07-02 [paul]      2.10.0
 
        * NEWS
index adbd5b0..255697f 100644 (file)
--- a/PATCHSETS
+++ b/PATCHSETS
 ( cvs diff -u -r 1.1.2.1 -r 1.1.2.2 src/gtk/headers.h;  ) > 2.9.2cvs76.patchset
 ( cvs diff -u -r 1.100.2.50 -r 1.100.2.51 AUTHORS;  ) > 2.9.2cvs77.patchset
 ( cvs diff -u -r 1.58.2.31 -r 1.58.2.32 po/de.po;  cvs diff -u -r 1.12.2.14 -r 1.12.2.15 po/en_GB.po;  cvs diff -u -r 1.60.2.44 -r 1.60.2.45 po/es.po;  cvs diff -u -r 1.42.2.31 -r 1.42.2.32 po/fr.po;  cvs diff -u -r 1.5.2.5 -r 1.5.2.6 po/hu.po;  cvs diff -u -r 1.50.2.25 -r 1.50.2.26 po/pt_BR.po;  cvs diff -u -r 1.17.2.14 -r 1.17.2.15 po/ru.po;  cvs diff -u -r 1.5.2.23 -r 1.5.2.24 po/zh_CN.po;  ) > 2.9.2cvs78.patchset
+( cvs diff -u -r 1.155.2.65 -r 1.155.2.66 src/Makefile.am;  cvs diff -u -r 1.382.2.391 -r 1.382.2.392 src/compose.c;  diff -u /dev/null src/edittags.c;  diff -u /dev/null src/edittags.h;  cvs diff -u -r 1.60.2.32 -r 1.60.2.33 src/filtering.c;  cvs diff -u -r 1.213.2.145 -r 1.213.2.146 src/folder.c;  cvs diff -u -r 1.87.2.41 -r 1.87.2.42 src/folder.h;  cvs diff -u -r 1.8.2.20 -r 1.8.2.21 src/headerview.c;  cvs diff -u -r 1.2.2.2 -r 1.2.2.3 src/headerview.h;  cvs diff -u -r 1.115.2.155 -r 1.115.2.156 src/main.c;  cvs diff -u -r 1.274.2.191 -r 1.274.2.192 src/mainwindow.c;  cvs diff -u -r 1.39.2.37 -r 1.39.2.38 src/mainwindow.h;  cvs diff -u -r 1.8.2.6 -r 1.8.2.7 src/manual.h;  cvs diff -u -r 1.75.2.41 -r 1.75.2.42 src/matcher.c;  cvs diff -u -r 1.39.2.12 -r 1.39.2.13 src/matcher.h;  cvs diff -u -r 1.25.2.28 -r 1.25.2.29 src/matcher_parser_parse.y;  cvs diff -u -r 1.83.2.107 -r 1.83.2.108 src/mimeview.c;  cvs diff -u -r 1.16.2.50 -r 1.16.2.51 src/msgcache.c;  cvs diff -u -r 1.5.2.5 -r 1.5.2.6 src/msgcache.h;  cvs diff -u -r 1.204.2.137 -r 1.204.2.138 src/prefs_common.c;  cvs diff -u -r 1.103.2.84 -r 1.103.2.85 src/prefs_common.h;  cvs diff -u -r 1.1.4.36 -r 1.1.4.37 src/prefs_filtering_action.c;  cvs diff -u -r 1.43.2.57 -r 1.43.2.58 src/prefs_matcher.c;  cvs diff -u -r 1.10.2.17 -r 1.10.2.18 src/prefs_summary_column.c;  cvs diff -u -r 1.150.2.98 -r 1.150.2.99 src/procmsg.c;  cvs diff -u -r 1.60.2.43 -r 1.60.2.44 src/procmsg.h;  cvs diff -u -r 1.8.2.19 -r 1.8.2.20 src/quote_fmt.c;  cvs diff -u -r 1.8.2.11 -r 1.8.2.12 src/quote_fmt_lex.l;  cvs diff -u -r 1.22.2.32 -r 1.22.2.33 src/quote_fmt_parse.y;  cvs diff -u -r 1.395.2.305 -r 1.395.2.306 src/summaryview.c;  cvs diff -u -r 1.68.2.37 -r 1.68.2.38 src/summaryview.h;  cvs diff -u -r 1.96.2.173 -r 1.96.2.174 src/textview.c;  cvs diff -u -r 1.24.2.13 -r 1.24.2.14 src/common/Makefile.am;  cvs diff -u -r 1.9.2.38 -r 1.9.2.39 src/common/defs.h;  diff -u /dev/null src/common/tags.c;  diff -u /dev/null src/common/tags.h;  cvs diff -u -r 1.1.2.72 -r 1.1.2.73 src/gtk/quicksearch.c;  cvs diff -u -r 1.1.2.14 -r 1.1.2.15 src/gtk/quicksearch.h;  ) > 2.10.0cvs1.patchset
index 86a1927..09342da 100644 (file)
@@ -11,7 +11,7 @@ MINOR_VERSION=10
 MICRO_VERSION=0
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=0
+EXTRA_VERSION=1
 EXTRA_RELEASE=
 EXTRA_GTK2_VERSION=
 
index 7693fc2..bcb68bc 100644 (file)
@@ -43,6 +43,7 @@ claws_mail_SOURCES = \
        editjpilot.c \
        editldap_basedn.c \
        editldap.c \
+       edittags.c \
        editvcard.c \
        enriched.c \
        exphtmldlg.c \
@@ -186,6 +187,7 @@ claws_mailinclude_HEADERS = \
        editjpilot.h \
        editldap_basedn.h \
        editldap.h \
+       edittags.h \
        editvcard.h \
        enriched.h \
        exphtmldlg.h \
index e392745..637001a 100644 (file)
@@ -32,6 +32,7 @@ libclawscommon_la_SOURCES = $(arch_sources) \
        string_match.c \
        stringtable.c \
        claws.c \
+       tags.c \
        template.c \
        utils.c \
        uuencode.c \
@@ -60,6 +61,7 @@ clawscommoninclude_HEADERS = $(arch_headers) \
        string_match.h \
        stringtable.h \
        claws.h \
+       tags.h \
        template.h \
        timing.h \
        utils.h \
index 0096435..e694f8a 100644 (file)
@@ -64,6 +64,7 @@
 #define MATCHER_RC             "matcherrc"
 #define MENU_RC                        "menurc"
 #define RENDERER_RC            "rendererrc"
+#define TAGS_RC                        "tagsrc"
 #define QUICKSEARCH_HISTORY    "quicksearch_history"
 #define SUMMARY_SEARCH_FROM_HISTORY    "summarysearch_from_history"
 #define SUMMARY_SEARCH_TO_HISTORY      "summarysearch_to_history"
 #define CACHE_FILE             ".claws_cache"
 #define OLD_MARK_FILE          ".sylpheed_mark"
 #define MARK_FILE              ".claws_mark"
+#define TAGS_FILE              ".claws_tags"
 #define CACHE_VERSION          24
 #define MARK_VERSION           2
+#define TAGS_VERSION           1
 
 #ifdef G_OS_WIN32
 #  define ACTIONS_RC           "actionswinrc"
diff --git a/src/common/tags.c b/src/common/tags.c
new file mode 100644 (file)
index 0000000..ebed405
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#if HAVE_SYS_WAIT_H
+#  include <sys/wait.h>
+#endif
+#include <signal.h>
+#include <unistd.h>
+
+#include "defs.h"
+#include "utils.h"
+
+static GHashTable *tags_table = NULL;
+static GHashTable *tags_reverse_table = NULL;
+
+static int tag_max_id = 0;
+
+void tags_read_tags(void)
+{
+       gchar *file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                       TAGS_RC, NULL);
+       gchar tmp[255];
+       gint id;
+       FILE *fp = fopen(file, "rb");
+       
+       g_free(file);
+
+       if (tags_table == NULL)
+               tags_table = g_hash_table_new_full(
+                               g_direct_hash, g_direct_equal,
+                               NULL, g_free);
+       if (tags_reverse_table == NULL)
+               tags_reverse_table = g_hash_table_new_full(
+                               g_str_hash, g_str_equal,
+                               g_free, NULL);
+               
+       if (!fp) 
+               return;
+       if (fscanf(fp, "max_id %d\n", &tag_max_id) != 1) {
+               fclose(fp);
+               return;
+       }
+       while (fscanf(fp, "%d\t%254s\n", &id, tmp) == 2) {
+               g_hash_table_insert(tags_table,
+                                   GINT_TO_POINTER(id), g_strdup(tmp));
+               g_hash_table_insert(tags_reverse_table,
+                                   g_strdup(tmp), GINT_TO_POINTER(id));                
+       }
+       
+       fclose(fp);
+}
+
+typedef struct _TagWriteData
+{
+       FILE *fp;
+       gboolean error;
+} TagWriteData;
+
+static void tag_write(gpointer key, gpointer value, gpointer user_data)
+{
+       TagWriteData *data = (TagWriteData *)user_data;
+       const gchar *str = value;
+       gint id = GPOINTER_TO_INT(key);
+
+       if (data->error)
+               return;
+               
+       if (fprintf(data->fp, "%d\t%s\n", id, str) <= 0) {
+               FILE_OP_ERROR("tagsrc", "fprintf");
+               data->error = TRUE;
+       }
+}
+
+void tags_write_tags(void)
+{
+       gchar *file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                       TAGS_RC, ".tmp", NULL);
+       gchar *file_new = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                       TAGS_RC, NULL);
+       TagWriteData data;
+
+       FILE *fp = fopen(file, "wb");
+                       
+       if (!fp) {
+               FILE_OP_ERROR(file, "fopen");
+               g_free(file);
+               g_free(file_new);
+               return;
+       }
+       
+       data.fp = fp;
+       data.error = FALSE;
+
+       if (fprintf(data.fp, "max_id %d\n", tag_max_id) <= 0) {
+               FILE_OP_ERROR("tagsrc", "fprintf");
+               data.error = TRUE;
+       } else {
+               g_hash_table_foreach(tags_table, tag_write, &data);
+       }
+
+       if (data.error) {
+               g_free(file);
+               g_free(file_new);
+               return;
+       }
+       
+       if (fclose(fp) == EOF) {
+               FILE_OP_ERROR(file, "fclose");
+               g_free(file);
+               g_free(file_new);
+               return;
+       }
+       
+       if (g_rename(file, file_new) < 0) {
+               FILE_OP_ERROR(file, "g_rename");
+       }
+
+       g_free(file);
+       g_free(file_new);
+}
+
+gint tags_add_tag(const gchar *tag)
+{
+       if (g_hash_table_lookup(tags_reverse_table, tag))
+               return -1;
+
+       tag_max_id++;
+       g_hash_table_insert(tags_table, GINT_TO_POINTER(tag_max_id), 
+               g_strdup(tag));
+       g_hash_table_insert(tags_reverse_table, g_strdup(tag),
+               GINT_TO_POINTER(tag_max_id));
+       
+       return tag_max_id;
+}
+
+void tags_remove_tag(gint id)
+{
+       gchar *old_tag = g_hash_table_lookup(tags_table, GINT_TO_POINTER(id));
+
+       if (old_tag) {
+               g_hash_table_remove(tags_reverse_table, old_tag);
+       }
+       g_hash_table_remove(tags_table, GINT_TO_POINTER(id));
+}
+
+void tags_update_tag(gint id, const gchar *tag)
+{
+       gchar *old_tag = g_hash_table_lookup(tags_table, GINT_TO_POINTER(id));
+
+       if (old_tag) {
+               g_hash_table_remove(tags_reverse_table, old_tag);
+       }
+
+       g_hash_table_replace(tags_table, GINT_TO_POINTER(id), 
+               g_strdup(tag));
+       g_hash_table_insert(tags_reverse_table, g_strdup(tag),
+               GINT_TO_POINTER(id));
+}
+
+const gchar *tags_get_tag(gint id)
+{
+       return (const gchar *)g_hash_table_lookup(tags_table,
+                               GINT_TO_POINTER(id));
+}
+
+gint tags_get_id_for_str(const gchar *str)
+{
+       gpointer id_ptr;
+       if ((id_ptr = g_hash_table_lookup(tags_reverse_table, str)) != NULL)
+               return GPOINTER_TO_INT(id_ptr);
+       else
+               return -1;
+}
+
+typedef struct _TagListData {
+       GSList *list;
+} TagListData;
+
+static void tag_add_list(gpointer key, gpointer value, gpointer user_data)
+{
+       TagListData *data = (TagListData *)user_data;
+
+       data->list = g_slist_prepend(data->list, GINT_TO_POINTER(key));
+}
+
+GSList *tags_get_list(void)
+{
+       TagListData data;
+       data.list = NULL;
+
+       g_hash_table_foreach(tags_table, tag_add_list, &data);
+
+       data.list = g_slist_reverse(data.list);
+       
+       return data.list;
+}
+
diff --git a/src/common/tags.h b/src/common/tags.h
new file mode 100644 (file)
index 0000000..62c5822
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __TAGS_H__
+#define __TAGS_H__
+
+#include <glib.h>
+
+void tags_read_tags(void);
+void tags_write_tags(void);
+gint tags_add_tag(const gchar *tag);
+void tags_remove_tag(gint id);
+void tags_update_tag(gint id, const gchar *tag);
+const gchar *tags_get_tag(gint id);
+gint tags_get_id_for_str(const gchar *str);
+GSList *tags_get_list(void);
+
+#endif 
index b8b1d2a..6e52a7d 100644 (file)
@@ -9805,6 +9805,8 @@ static void compose_reply_from_messageview_real(MessageView *msgview, GSList *ms
                                }
                                tmp_msginfo->folder = orig_msginfo->folder;
                                tmp_msginfo->msgnum = orig_msginfo->msgnum; 
+                               if (orig_msginfo->tags)
+                                       tmp_msginfo->tags = g_slist_copy(orig_msginfo->tags);
                        }
                }
        }
diff --git a/src/edittags.c b/src/edittags.c
new file mode 100644 (file)
index 0000000..6307fe3
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "defs.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "edittags.h"
+#include "prefs_gtk.h"
+#include "utils.h"
+#include "gtkutils.h"
+#include "inputdialog.h"
+#include "manage_window.h"
+#include "mainwindow.h"
+#include "prefs_common.h"
+#include "alertpanel.h"
+#include "summaryview.h"
+#include "tags.h"
+#include "gtkutils.h"
+#include "manual.h"
+
+enum {
+       PREFS_TAGS_STRING,      /*!< string pointer managed by list store, 
+                                *   and never touched or retrieved by 
+                                *   us */ 
+       PREFS_TAGS_ID,          /*!< pointer to string that is not managed by 
+                                *   the list store, and which is retrieved
+                                *   and touched by us */
+       N_PREFS_TAGS_COLUMNS
+};
+
+static struct Tags
+{
+       GtkWidget *window;
+
+       GtkWidget *ok_btn;
+
+       GtkWidget *name_entry;
+       GtkWidget *tags_list_view;
+} tags;
+
+static int modified = FALSE;
+
+/* widget creating functions */
+static void prefs_tags_create  (MainWindow *mainwin);
+static void prefs_tags_set_dialog      (void);
+static gint prefs_tags_clist_set_row   (GtkTreeIter *row);
+
+/* callback functions */
+static void prefs_tags_register_cb     (GtkWidget      *w,
+                                        gpointer        data);
+static void prefs_tags_substitute_cb   (GtkWidget      *w,
+                                        gpointer        data);
+static void prefs_tags_delete_cb       (GtkWidget      *w,
+                                        gpointer        data);
+static gint prefs_tags_deleted         (GtkWidget      *widget,
+                                        GdkEventAny    *event,
+                                        gpointer       *data);
+static gboolean prefs_tags_key_pressed(GtkWidget       *widget,
+                                         GdkEventKey   *event,
+                                         gpointer       data);
+static void prefs_tags_ok              (GtkWidget      *w,
+                                        gpointer        data);
+
+
+static GtkListStore* prefs_tags_create_data_store      (void);
+
+static void prefs_tags_list_view_insert_tag    (GtkWidget *list_view,
+                                                        GtkTreeIter *row_iter,
+                                                        gchar *tag,
+                                                        gint id);
+static GtkWidget *prefs_tags_list_view_create  (void);
+static void prefs_tags_create_list_view_columns        (GtkWidget *list_view);
+static gboolean prefs_tags_selected                    (GtkTreeSelection *selector,
+                                                        GtkTreeModel *model, 
+                                                        GtkTreePath *path,
+                                                        gboolean currently_selected,
+                                                        gpointer data);
+
+void prefs_tags_open(MainWindow *mainwin)
+{
+       if (!tags.window)
+               prefs_tags_create(mainwin);
+
+       manage_window_set_transient(GTK_WINDOW(tags.window));
+       gtk_widget_grab_focus(tags.ok_btn);
+
+       prefs_tags_set_dialog();
+
+       gtk_widget_show(tags.window);
+}
+
+/*!
+ *\brief       Save Gtk object size to prefs dataset
+ */
+static void prefs_tags_size_allocate_cb(GtkWidget *widget,
+                                        GtkAllocation *allocation)
+{
+       g_return_if_fail(allocation != NULL);
+
+       prefs_common.tagswin_width = allocation->width;
+       prefs_common.tagswin_height = allocation->height;
+}
+
+static void prefs_tags_create(MainWindow *mainwin)
+{
+       GtkWidget *window;
+       GtkWidget *vbox;
+       GtkWidget *help_btn;
+       GtkWidget *ok_btn;
+       GtkWidget *confirm_area;
+
+       GtkWidget *vbox1;
+       GtkWidget *table;
+
+       GtkWidget *name_label;
+       GtkWidget *name_entry;
+
+       GtkWidget *reg_hbox;
+       GtkWidget *btn_hbox;
+       GtkWidget *arrow;
+       GtkWidget *reg_btn;
+       GtkWidget *subst_btn;
+       GtkWidget *del_btn;
+
+       GtkWidget *cond_hbox;
+       GtkWidget *cond_scrolledwin;
+       GtkWidget *cond_list_view;
+
+
+       GtkWidget *btn_vbox;
+       static GdkGeometry geometry;
+
+       debug_print("Creating tags configuration window...\n");
+
+       window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_tags");
+
+       gtk_container_set_border_width(GTK_CONTAINER (window), 8);
+       gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+       gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+       gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
+
+       vbox = gtk_vbox_new(FALSE, 6);
+       gtk_widget_show(vbox);
+       gtk_container_add(GTK_CONTAINER(window), vbox);
+
+       gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
+                       &ok_btn, GTK_STOCK_OK,
+                       NULL, NULL,
+                       NULL, NULL);
+       gtk_widget_show(confirm_area);
+       gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
+       gtk_widget_grab_default(ok_btn);
+
+       gtk_window_set_title(GTK_WINDOW(window), _("Tags configuration"));
+       g_signal_connect(G_OBJECT(window), "delete_event",
+                        G_CALLBACK(prefs_tags_deleted), NULL);
+       g_signal_connect(G_OBJECT(window), "size_allocate",
+                        G_CALLBACK(prefs_tags_size_allocate_cb), NULL);
+       g_signal_connect(G_OBJECT(window), "key_press_event",
+                        G_CALLBACK(prefs_tags_key_pressed), NULL);
+       MANAGE_WINDOW_SIGNALS_CONNECT(window);
+       g_signal_connect(G_OBJECT(ok_btn), "clicked",
+                        G_CALLBACK(prefs_tags_ok), mainwin);
+       g_signal_connect(G_OBJECT(help_btn), "clicked",
+                        G_CALLBACK(manual_open_with_anchor_cb),
+                        MANUAL_ANCHOR_TAGS);
+
+       vbox1 = gtk_vbox_new(FALSE, VSPACING);
+       gtk_widget_show(vbox1);
+       gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
+       gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2);
+
+       table = gtk_table_new(2, 2, FALSE);
+       gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2);
+       gtk_table_set_col_spacings (GTK_TABLE (table), 4);
+       gtk_widget_show(table);
+       gtk_box_pack_start (GTK_BOX (vbox1), table, FALSE, FALSE, 0);
+
+       name_label = gtk_label_new (_("Tag name"));
+       gtk_widget_show (name_label);
+       gtk_misc_set_alignment (GTK_MISC (name_label), 1, 0.5);
+       gtk_table_attach (GTK_TABLE (table), name_label, 0, 1, 0, 1,
+                         (GtkAttachOptions) (GTK_FILL),
+                         (GtkAttachOptions) (0), 0, 0);
+
+       name_entry = gtk_entry_new ();
+       gtk_widget_show (name_entry);
+       gtk_table_attach (GTK_TABLE (table), name_entry, 1, 2, 0, 1,
+                         (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
+                         (GtkAttachOptions) (0), 0, 0);
+
+       /* register / delete */
+
+       reg_hbox = gtk_hbox_new(FALSE, 4);
+       gtk_widget_show(reg_hbox);
+       gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
+
+       arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+       gtk_widget_show(arrow);
+       gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
+       gtk_widget_set_size_request(arrow, -1, 16);
+
+       btn_hbox = gtk_hbox_new(TRUE, 4);
+       gtk_widget_show(btn_hbox);
+       gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
+
+       reg_btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
+       gtk_widget_show(reg_btn);
+       gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
+       g_signal_connect(G_OBJECT(reg_btn), "clicked",
+                        G_CALLBACK(prefs_tags_register_cb), NULL);
+
+       subst_btn = gtkut_get_replace_btn(_("Replace"));
+       gtk_widget_show(subst_btn);
+       gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
+       g_signal_connect(G_OBJECT(subst_btn), "clicked",
+                        G_CALLBACK(prefs_tags_substitute_cb),
+                        NULL);
+
+       del_btn = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+       gtk_widget_show(del_btn);
+       gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
+       g_signal_connect(G_OBJECT(del_btn), "clicked",
+                        G_CALLBACK(prefs_tags_delete_cb), NULL);
+
+       cond_hbox = gtk_hbox_new(FALSE, 8);
+       gtk_widget_show(cond_hbox);
+       gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
+
+       cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
+       gtk_widget_show(cond_scrolledwin);
+       gtk_widget_set_size_request(cond_scrolledwin, -1, 150);
+       gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
+                          TRUE, TRUE, 0);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin),
+                                      GTK_POLICY_AUTOMATIC,
+                                      GTK_POLICY_AUTOMATIC);
+
+       cond_list_view = prefs_tags_list_view_create();                                
+       gtk_widget_show(cond_list_view);
+       gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_list_view);
+
+       btn_vbox = gtk_vbox_new(FALSE, 8);
+       gtk_widget_show(btn_vbox);
+       gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
+
+       if (!geometry.min_height) {
+               geometry.min_width = 486;
+               geometry.min_height = 322;
+       }
+
+       gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
+                                     GDK_HINT_MIN_SIZE);
+       gtk_widget_set_size_request(window, prefs_common.tagswin_width,
+                                   prefs_common.tagswin_height);
+
+       gtk_widget_show(window);
+
+       tags.window = window;
+       tags.ok_btn = ok_btn;
+
+       tags.name_entry = name_entry;
+
+       tags.tags_list_view = cond_list_view;
+}
+
+static void prefs_tags_set_dialog(void)
+{
+       GtkListStore *store;
+       GSList *cur, *orig;
+       GtkTreeSelection *selection;
+       GtkTreeIter iter;
+
+       store = GTK_LIST_STORE(gtk_tree_view_get_model
+                               (GTK_TREE_VIEW(tags.tags_list_view)));
+       gtk_list_store_clear(store);
+
+       prefs_tags_list_view_insert_tag(tags.tags_list_view,
+                                             NULL, _("(New)"), -1);
+
+       for (orig = cur = tags_get_list(); cur != NULL; cur = cur->next) {
+               gint id = GPOINTER_TO_INT(cur->data);
+               gchar *tag = (gchar *) tags_get_tag(id);
+               
+               prefs_tags_list_view_insert_tag(tags.tags_list_view,
+                                                     NULL, tag, id);
+       }
+
+       g_slist_free(orig);
+       /* select first entry */
+       selection = gtk_tree_view_get_selection
+               (GTK_TREE_VIEW(tags.tags_list_view));
+       if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store),
+                                         &iter))
+               gtk_tree_selection_select_iter(selection, &iter);
+}
+
+static gint prefs_tags_clist_set_row(GtkTreeIter *row)
+{
+       gchar *tag;
+       GtkListStore *store;
+       gint id = -1;
+
+       store = GTK_LIST_STORE(gtk_tree_view_get_model
+                               (GTK_TREE_VIEW(tags.tags_list_view)));
+       
+
+       tag = gtk_editable_get_chars(GTK_EDITABLE(tags.name_entry), 0, -1);
+       if (tag[0] == '\0') {
+               alertpanel_error(_("Tag is not set."));
+               return -1;
+       }
+
+       g_strstrip(tag);
+       if (row == NULL) {
+               if ((id = tags_add_tag(tag)) != -1) {
+                       prefs_tags_list_view_insert_tag(tags.tags_list_view,
+                                             row, tag, id);
+                       tags_write_tags();
+               }
+       } else {
+               prefs_tags_list_view_insert_tag(tags.tags_list_view,
+                                     row, tag, -1);
+               tags_write_tags();
+       }
+       return 0;
+}
+
+/* callback functions */
+
+static void prefs_tags_register_cb(GtkWidget *w, gpointer data)
+{
+       prefs_tags_clist_set_row(NULL);
+       modified = FALSE;
+}
+
+static void prefs_tags_substitute_cb(GtkWidget *w, gpointer data)
+{
+       GtkTreeIter isel, inew;
+       GtkTreePath *path_sel, *path_new;
+       GtkTreeSelection *selection = gtk_tree_view_get_selection
+                                       (GTK_TREE_VIEW(tags.tags_list_view));
+       GtkTreeModel *model;                                    
+
+       if (!gtk_tree_selection_get_selected(selection, &model, &isel))
+               return;
+       if (!gtk_tree_model_get_iter_first(model, &inew))
+               return;
+
+       path_sel = gtk_tree_model_get_path(model, &isel);
+       path_new = gtk_tree_model_get_path(model, &inew);
+
+       if (path_sel && path_new 
+       &&  gtk_tree_path_compare(path_sel, path_new) != 0)
+               prefs_tags_clist_set_row(&isel);
+
+       gtk_tree_path_free(path_sel);
+       gtk_tree_path_free(path_new);
+       modified = FALSE;
+}
+
+static void prefs_tags_delete_cb(GtkWidget *w, gpointer data)
+{
+       GtkTreeIter sel;
+       GtkTreeModel *model;
+       gint id;
+
+       if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection
+                               (GTK_TREE_VIEW(tags.tags_list_view)),
+                               &model, &sel))
+               return;                         
+
+       if (alertpanel(_("Delete tag"),
+                      _("Do you really want to delete this tag?"),
+                      GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL) != G_ALERTALTERNATE)
+               return;
+
+       /* XXX: Here's the reason why we need to store the original 
+        * pointer: we search the slist for it. */
+       gtk_tree_model_get(model, &sel,
+                          PREFS_TAGS_ID, &id,
+                          -1);
+       gtk_list_store_remove(GTK_LIST_STORE(model), &sel);
+       tags_remove_tag(id);
+       tags_write_tags();
+}
+
+static gint prefs_tags_deleted(GtkWidget *widget, GdkEventAny *event,
+                                 gpointer *data)
+{
+       prefs_tags_ok(widget, data);
+       return TRUE;
+}
+
+static gboolean prefs_tags_key_pressed(GtkWidget *widget, GdkEventKey *event,
+                                         gpointer data)
+{
+       if (event && event->keyval == GDK_Escape)
+               prefs_tags_ok(widget, data);
+       else {
+               GtkWidget *focused = gtkut_get_focused_child(
+                                       GTK_CONTAINER(widget));
+               if (focused && GTK_IS_EDITABLE(focused)) {
+                       modified = TRUE;
+               }
+       }
+       return FALSE;
+}
+
+static void prefs_tags_ok(GtkWidget *widget, gpointer data)
+{
+       if (modified && alertpanel(_("Entry not saved"),
+                                _("The entry was not saved. Close anyway?"),
+                                GTK_STOCK_CLOSE, _("+_Continue editing"),
+                                NULL) != G_ALERTDEFAULT) {
+               return;
+       }
+       modified = FALSE;
+
+       main_window_reflect_tags_changes(mainwindow_get_mainwindow());
+       gtk_widget_hide(tags.window);
+}
+
+static GtkListStore* prefs_tags_create_data_store(void)
+{
+       return gtk_list_store_new(N_PREFS_TAGS_COLUMNS,
+                                 G_TYPE_STRING,        
+                                 G_TYPE_INT,
+                                 -1);
+}
+
+static void prefs_tags_list_view_insert_tag(GtkWidget *list_view,
+                                                 GtkTreeIter *row_iter,
+                                                 gchar *tag,
+                                                 gint id) 
+{
+       GtkTreeIter iter;
+       GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
+                                       (GTK_TREE_VIEW(list_view)));
+
+       if (row_iter == NULL) {
+               /* append new */
+               gtk_list_store_append(list_store, &iter);
+               gtk_list_store_set(list_store, &iter,
+                                  PREFS_TAGS_STRING, tag,
+                                  PREFS_TAGS_ID, id,
+                                  -1);
+       } else if (id == -1) {
+               /* change existing */
+               gtk_tree_model_get(GTK_TREE_MODEL(list_store), row_iter,
+                                  PREFS_TAGS_ID, &id,
+                                  -1);
+               gtk_list_store_set(list_store, row_iter,
+                                  PREFS_TAGS_STRING, tag,
+                                  PREFS_TAGS_ID, id,
+                                  -1);
+               tags_update_tag(id, tag);
+       }
+}
+
+static GtkWidget *prefs_tags_list_view_create(void)
+{
+       GtkTreeView *list_view;
+       GtkTreeSelection *selector;
+       GtkTreeModel *model;
+
+       model = GTK_TREE_MODEL(prefs_tags_create_data_store());
+       list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
+       g_object_unref(model);  
+       
+       gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
+
+       selector = gtk_tree_view_get_selection(list_view);
+       gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
+       gtk_tree_selection_set_select_function(selector, prefs_tags_selected,
+                                              NULL, NULL);
+
+       /* create the columns */
+       prefs_tags_create_list_view_columns(GTK_WIDGET(list_view));
+
+       return GTK_WIDGET(list_view);
+}
+
+static void prefs_tags_create_list_view_columns(GtkWidget *list_view)
+{
+       GtkTreeViewColumn *column;
+       GtkCellRenderer *renderer;
+
+       renderer = gtk_cell_renderer_text_new();
+       column = gtk_tree_view_column_new_with_attributes
+               (_("Current tags"),
+                renderer,
+                "text", PREFS_TAGS_STRING,
+                NULL);
+       gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
+}
+
+#define ENTRY_SET_TEXT(entry, str) \
+       gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
+
+static gboolean prefs_tags_selected(GtkTreeSelection *selector,
+                                      GtkTreeModel *model, 
+                                      GtkTreePath *path,
+                                      gboolean currently_selected,
+                                      gpointer data)
+{
+       gchar *tag;
+       GtkTreeIter iter;
+       gint id;
+
+       if (currently_selected)
+               return TRUE;
+
+       if (!gtk_tree_model_get_iter(model, &iter, path))
+               return TRUE;
+
+       gtk_tree_model_get(model, &iter, 
+                          PREFS_TAGS_ID,  &id,
+                          PREFS_TAGS_STRING, &tag,
+                          -1);
+       if (id == -1) {
+               ENTRY_SET_TEXT(tags.name_entry, "");
+               return TRUE;
+       }
+       
+       ENTRY_SET_TEXT(tags.name_entry, tag);
+
+       return TRUE;
+}
+
+gint prefs_tags_create_new(MainWindow *mainwin)
+{
+       gchar *new_tag = input_dialog(_("New tag"),
+                               _("New tag name:"), 
+                               ""); 
+       gint id = -1;
+       if (!new_tag || !(*new_tag))
+               return -1;
+       
+       id = tags_get_id_for_str(new_tag);
+       if (id != -1) {
+               g_free(new_tag);
+               return id;
+       }
+       id = tags_add_tag(new_tag);
+       g_free(new_tag);
+       
+       tags_write_tags();
+       return id;
+       
+}
diff --git a/src/edittags.h b/src/edittags.h
new file mode 100644 (file)
index 0000000..57b36e3
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __EDITTAGS_H__
+#define __EDITTAGS_H__
+
+#include "mainwindow.h"
+
+void prefs_tags_open(MainWindow *mainwin);
+gint prefs_tags_create_new(MainWindow *mainwin);
+
+#endif
index e8b0737..e8b5d3e 100644 (file)
@@ -36,6 +36,7 @@
 #include "prefs_common.h"
 #include "addrbook.h"
 #include "addr_compl.h"
+#include "tags.h"
 #include "log.h"
 
 #define PREFSBUFSIZE           1024
@@ -310,6 +311,22 @@ static gboolean filteringaction_apply(FilteringAction * action, MsgInfo * info)
                info->to_filter_folder = dest_folder;
                return TRUE;
 
+       case MATCHACTION_SET_TAG:
+       case MATCHACTION_UNSET_TAG:
+               val = tags_get_id_for_str(action->destination);
+               if (val == -1) {
+                       debug_print("*** tag '%s' not found\n",
+                               action->destination ?action->destination :"");
+                       return FALSE;
+               }
+               
+               procmsg_msginfo_update_tags(info, (action->type == MATCHACTION_SET_TAG), val);
+               return TRUE;
+
+       case MATCHACTION_CLEAR_TAGS:
+               procmsg_msginfo_clear_tags(info);
+               return TRUE;
+
        case MATCHACTION_DELETE:
                if (folder_item_remove_msg(info->folder, info->msgnum) == -1)
                        return FALSE;
@@ -875,6 +892,8 @@ gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *act
        case MATCHACTION_MOVE:
        case MATCHACTION_COPY:
        case MATCHACTION_EXECUTE:
+       case MATCHACTION_SET_TAG:
+       case MATCHACTION_UNSET_TAG:
                quoted_dest = matcher_quote_str(action->destination);
                g_snprintf(dest, destlen, "%s \"%s\"", command_str, quoted_dest);
                g_free(quoted_dest);
@@ -892,6 +911,7 @@ gchar *filteringaction_to_string(gchar *dest, gint destlen, FilteringAction *act
        case MATCHACTION_STOP:
        case MATCHACTION_HIDE:
        case MATCHACTION_IGNORE:
+       case MATCHACTION_CLEAR_TAGS:
                g_snprintf(dest, destlen, "%s", command_str);
                return dest;
 
index 665c5fa..f7dae89 100644 (file)
@@ -84,6 +84,7 @@ void folder_init              (Folder         *folder,
 
 static gchar *folder_item_get_cache_file       (FolderItem     *item);
 static gchar *folder_item_get_mark_file        (FolderItem     *item);
+static gchar *folder_item_get_tags_file        (FolderItem     *item);
 static gchar *folder_get_list_path     (void);
 static GNode *folder_get_xml_node      (Folder         *folder);
 static Folder *folder_get_from_xml     (GNode          *node);
@@ -2292,13 +2293,14 @@ void folder_clean_cache_memory(FolderItem *protected_item)
 
 static void folder_item_read_cache(FolderItem *item)
 {
-       gchar *cache_file, *mark_file;
+       gchar *cache_file, *mark_file, *tags_file;
        START_TIMING("");
        g_return_if_fail(item != NULL);
 
        if (item->path != NULL) {
                cache_file = folder_item_get_cache_file(item);
                mark_file = folder_item_get_mark_file(item);
+               tags_file = folder_item_get_tags_file(item);
                item->cache = msgcache_read_cache(item, cache_file);
                if (!item->cache) {
                        MsgInfoList *list, *cur;
@@ -2332,8 +2334,11 @@ static void folder_item_read_cache(FolderItem *item)
                } else
                        msgcache_read_mark(item->cache, mark_file);
 
+               msgcache_read_tags(item->cache, tags_file);
+
                g_free(cache_file);
                g_free(mark_file);
+               g_free(tags_file);
        } else {
                item->cache = msgcache_new();
        }
@@ -2343,7 +2348,7 @@ static void folder_item_read_cache(FolderItem *item)
 
 void folder_item_write_cache(FolderItem *item)
 {
-       gchar *cache_file, *mark_file;
+       gchar *cache_file, *mark_file, *tags_file;
        FolderItemPrefs *prefs;
        gint filemode = 0;
        gchar *id;
@@ -2364,7 +2369,8 @@ void folder_item_write_cache(FolderItem *item)
 
        cache_file = folder_item_get_cache_file(item);
        mark_file = folder_item_get_mark_file(item);
-       if (msgcache_write(cache_file, mark_file, item->cache) < 0) {
+       tags_file = folder_item_get_tags_file(item);
+       if (msgcache_write(cache_file, mark_file, tags_file, item->cache) < 0) {
                prefs = item->prefs;
                if (prefs && prefs->enable_folder_chmod && prefs->folder_chmod) {
                        /* for cache file */
@@ -2383,6 +2389,7 @@ void folder_item_write_cache(FolderItem *item)
 
        g_free(cache_file);
        g_free(mark_file);
+       g_free(tags_file);
 }
 
 MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
@@ -2697,6 +2704,11 @@ static void copy_msginfo_flags(MsgInfo *source, MsgInfo *dest)
                                  ~dest->flags.tmp_flags  & tmp_flags,
                                   dest->flags.perm_flags & ~perm_flags,
                                   dest->flags.tmp_flags  & ~tmp_flags);
+       
+       if (source && source->tags) {
+               g_slist_free(dest->tags);
+               dest->tags = g_slist_copy(source->tags);
+       }
 }
 
 static void add_msginfo_to_cache(FolderItem *item, MsgInfo *newmsginfo, MsgInfo *flagsource)
@@ -3487,6 +3499,38 @@ static gchar *folder_item_get_mark_file(FolderItem *item)
        return file;
 }
 
+static gchar *folder_item_get_tags_file(FolderItem *item)
+{
+       gchar *path;
+       gchar *identifier;
+       gchar *file;
+
+       /* we save tags files in rc_dir, because tagsrc is there too,
+        * and storing tags directly in the mailboxes would give strange
+        * result when using another Claws mailbox from another install
+        * with different tags. */
+
+       g_return_val_if_fail(item != NULL, NULL);
+
+       identifier = folder_item_get_identifier(item);
+       g_return_val_if_fail(identifier != NULL, NULL);
+       
+       path = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
+                          "tagsdb", G_DIR_SEPARATOR_S,
+                          identifier, NULL);
+       
+       g_free(identifier);
+                          
+       if (!is_dir_exist(path))
+               make_dir_hier(path);
+
+       file = g_strconcat(path, G_DIR_SEPARATOR_S, TAGS_FILE, NULL);
+       
+       g_free(path);
+
+       return file;
+}
+
 static gpointer xml_to_folder_item(gpointer nodedata, gpointer data)
 {
        XMLNode *xmlnode = (XMLNode *) nodedata;
index 48e4c13..c988e87 100644 (file)
@@ -80,7 +80,8 @@ typedef enum
        SORT_BY_STATUS,
        SORT_BY_MIME,
        SORT_BY_TO,
-       SORT_BY_LOCKED
+       SORT_BY_LOCKED,
+       SORT_BY_TAGS
 } FolderSortKey;
 
 typedef enum
index 64501ec..07877f5 100644 (file)
@@ -76,6 +76,7 @@ static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run);
 static void quicksearch_set_active(QuickSearch *quicksearch, gboolean active);
 static void quicksearch_reset_folder_items(QuickSearch *quicksearch, FolderItem *folder_item);
 static gchar *expand_search_string(const gchar *str);
+static gchar *expand_tag_search_string(const gchar *str);
 
 gboolean quicksearch_is_fast(QuickSearch *quicksearch)
 {
@@ -126,6 +127,16 @@ static void prepare_matcher(QuickSearch *quicksearch)
 
                        return;
                }
+       } else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_TAG) {
+               char *newstr = expand_tag_search_string(search_string);
+               quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
+               g_free(newstr);
+       } else if (prefs_common.summary_quicksearch_type == QUICK_SEARCH_MIXED) {
+               char *newstr = expand_tag_search_string(search_string);
+               quicksearch->matcher_list = matcher_parser_get_cond(newstr, &quicksearch->is_fast);
+               g_free(newstr);
+               g_free(quicksearch->search_string);
+               quicksearch->search_string = g_strdup(search_string);
        } else {
                quicksearch->is_fast = TRUE;
                g_free(quicksearch->search_string);
@@ -402,6 +413,8 @@ static gchar *search_descr_strings[] = {
        "Sg #",  N_("messages whose size is greater than #"),
        "Ss #",  N_("messages whose size is smaller than #"),
        "t S",   N_("messages which have been sent to S"),
+       "tg S",  N_("messages which tags contain S"),
+       "tagged",N_("messages which have tag(s)"),
        "T",     N_("marked messages"),
        "U",     N_("unread messages"),
        "x S",   N_("messages which contain S in References header"),
@@ -534,7 +547,12 @@ QuickSearch *quicksearch_new()
                         G_CALLBACK(searchtype_changed),
                         quicksearch);
        MENUITEM_ADD (search_type, menuitem,
-                       prefs_common_translated_header_name("From, To or Subject"), QUICK_SEARCH_MIXED);
+                       prefs_common_translated_header_name("Tag"), QUICK_SEARCH_TAG);
+       g_signal_connect(G_OBJECT(menuitem), "activate",
+                        G_CALLBACK(searchtype_changed),
+                        quicksearch);
+       MENUITEM_ADD (search_type, menuitem,
+                       _("From/To/Subject/Tag"), QUICK_SEARCH_MIXED);
        g_signal_connect(G_OBJECT(menuitem), "activate",
                         G_CALLBACK(searchtype_changed),
                         quicksearch);
@@ -888,6 +906,7 @@ gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
        quicksearch->matching = TRUE;
        if (prefs_common.summary_quicksearch_type != QUICK_SEARCH_EXTENDED &&
            prefs_common.summary_quicksearch_type != QUICK_SEARCH_MIXED &&
+           prefs_common.summary_quicksearch_type != QUICK_SEARCH_TAG &&
            quicksearch->search_string &&
             searched_header && strcasestr(searched_header, quicksearch->search_string) != NULL)
                result = TRUE;
@@ -895,7 +914,9 @@ gboolean quicksearch_match(QuickSearch *quicksearch, MsgInfo *msginfo)
                quicksearch->search_string && (
                (msginfo->to && strcasestr(msginfo->to, quicksearch->search_string) != NULL) ||
                (msginfo->from && strcasestr(msginfo->from, quicksearch->search_string) != NULL) ||
-               (msginfo->subject && strcasestr(msginfo->subject, quicksearch->search_string) != NULL)  ))
+               (msginfo->subject && strcasestr(msginfo->subject, quicksearch->search_string) != NULL) ||
+               ((quicksearch->matcher_list != NULL) &&
+                matcherlist_match(quicksearch->matcher_list, msginfo))  ))
                result = TRUE;
        else if ((quicksearch->matcher_list != NULL) &&
                 matcherlist_match(quicksearch->matcher_list, msginfo))
@@ -958,6 +979,7 @@ static gchar *expand_search_string(const gchar *search_string)
                { "Sg", "size_greater",                 1,      FALSE,  FALSE },
                { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
                { "t",  "to",                           1,      TRUE,   TRUE  },
+               { "tg", "tag",                          1,      TRUE,   TRUE  },
                { "T",  "marked",                       0,      FALSE,  FALSE },
                { "U",  "unread",                       0,      FALSE,  FALSE },
                { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
@@ -1088,6 +1110,30 @@ static gchar *expand_search_string(const gchar *search_string)
        return returnstr;
 }
 
+static gchar *expand_tag_search_string(const gchar *search_string)
+{
+       gchar *newstr = NULL;
+       gchar **words = search_string ? g_strsplit(search_string, " ", -1):NULL;
+       gint i = 0;
+       while (words && words[i] && *words[i]) {
+               g_strstrip(words[i]);
+               if (!newstr) {
+                       newstr = g_strdup_printf("tag regexpcase \"%s\"", words[i]);
+               } else {
+                       gint o_len = strlen(newstr);
+                       gint s_len = 18; /* strlen("|tag regexpcase \"\"") */
+                       gint n_len = s_len + strlen(words[i]);
+                       newstr = g_realloc(newstr,o_len+n_len+1);
+                       strcpy(newstr+o_len, "|tag regexpcase \"");
+                       strcpy(newstr+o_len+(s_len-1), words[i]);
+                       strcpy(newstr+o_len+s_len, "\"");
+               }
+               i++;
+       }
+       g_strfreev(words);
+       return newstr;
+}
+
 static void quicksearch_set_running(QuickSearch *quicksearch, gboolean run)
 {
        quicksearch->running = run;
index 650453a..61399d3 100644 (file)
@@ -27,7 +27,8 @@ typedef enum
        QUICK_SEARCH_FROM,
        QUICK_SEARCH_TO,
        QUICK_SEARCH_EXTENDED,
-       QUICK_SEARCH_MIXED
+       QUICK_SEARCH_MIXED,
+       QUICK_SEARCH_TAG
 } QuickSearchType;
 
 typedef struct _QuickSearch QuickSearch;
index 22b2321..4e06c3a 100644 (file)
@@ -69,6 +69,7 @@ HeaderView *headerview_create(void)
        GtkWidget *vbox;
        GtkWidget *hbox1;
        GtkWidget *hbox2;
+       GtkWidget *hbox3;
        GtkWidget *from_header_label;
        GtkWidget *from_body_label;
        GtkWidget *to_header_label;
@@ -77,6 +78,8 @@ HeaderView *headerview_create(void)
        GtkWidget *ng_body_label;
        GtkWidget *subject_header_label;
        GtkWidget *subject_body_label;
+       GtkWidget *tags_header_label;
+       GtkWidget *tags_body_label;
 
        debug_print("Creating header view...\n");
        headerview = g_new0(HeaderView, 1);
@@ -90,6 +93,8 @@ HeaderView *headerview_create(void)
        gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
        hbox2 = gtk_hbox_new(FALSE, 4);
        gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 0);
+       hbox3 = gtk_hbox_new(FALSE, 4);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox3, FALSE, FALSE, 0);
 
        from_header_label    = gtk_label_new(prefs_common_translated_header_name("From:"));
        from_body_label      = gtk_label_new("");
@@ -99,16 +104,20 @@ HeaderView *headerview_create(void)
        ng_body_label        = gtk_label_new("");
        subject_header_label = gtk_label_new(prefs_common_translated_header_name("Subject:"));
        subject_body_label   = gtk_label_new("");
+       tags_header_label = gtk_label_new(_("Tags:"));
+       tags_body_label   = gtk_label_new("");
 
        gtk_label_set_selectable(GTK_LABEL(from_body_label), TRUE);
        gtk_label_set_selectable(GTK_LABEL(to_body_label), TRUE);
        gtk_label_set_selectable(GTK_LABEL(ng_body_label), TRUE);
        gtk_label_set_selectable(GTK_LABEL(subject_body_label), TRUE);
+       gtk_label_set_selectable(GTK_LABEL(tags_body_label), TRUE);
 
        GTK_WIDGET_UNSET_FLAGS(from_body_label, GTK_CAN_FOCUS);
        GTK_WIDGET_UNSET_FLAGS(to_body_label, GTK_CAN_FOCUS);
        GTK_WIDGET_UNSET_FLAGS(ng_body_label, GTK_CAN_FOCUS);
        GTK_WIDGET_UNSET_FLAGS(subject_body_label, GTK_CAN_FOCUS);
+       GTK_WIDGET_UNSET_FLAGS(tags_body_label, GTK_CAN_FOCUS);
 
        gtk_box_pack_start(GTK_BOX(hbox1), from_header_label, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(hbox1), from_body_label, FALSE, FALSE, 0);
@@ -118,13 +127,17 @@ HeaderView *headerview_create(void)
        gtk_box_pack_start(GTK_BOX(hbox1), ng_body_label, TRUE, TRUE, 0);
        gtk_box_pack_start(GTK_BOX(hbox2), subject_header_label, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(hbox2), subject_body_label, TRUE, TRUE, 0);
+       gtk_box_pack_start(GTK_BOX(hbox3), tags_header_label, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(hbox3), tags_body_label, TRUE, TRUE, 0);
 
        gtk_misc_set_alignment(GTK_MISC(to_body_label), 0, 0.5);
        gtk_misc_set_alignment(GTK_MISC(ng_body_label), 0, 0.5);
        gtk_misc_set_alignment(GTK_MISC(subject_body_label), 0, 0.5);
+       gtk_misc_set_alignment(GTK_MISC(tags_body_label), 0, 0.5);
        gtk_label_set_ellipsize(GTK_LABEL(to_body_label), PANGO_ELLIPSIZE_END);
        gtk_label_set_ellipsize(GTK_LABEL(ng_body_label), PANGO_ELLIPSIZE_END);
        gtk_label_set_ellipsize(GTK_LABEL(subject_body_label), PANGO_ELLIPSIZE_END);
+       gtk_label_set_ellipsize(GTK_LABEL(tags_body_label), PANGO_ELLIPSIZE_END);
 
        headerview->hbox = hbox;
        headerview->from_header_label    = from_header_label;
@@ -135,6 +148,8 @@ HeaderView *headerview_create(void)
        headerview->ng_body_label        = ng_body_label;
        headerview->subject_header_label = subject_header_label;
        headerview->subject_body_label   = subject_body_label;
+       headerview->tags_header_label = tags_header_label;
+       headerview->tags_body_label   = tags_body_label;
        headerview->image = NULL;
 
        gtk_widget_show_all(hbox);
@@ -158,12 +173,14 @@ void headerview_set_font(HeaderView *headerview)
                gtk_widget_modify_font(headerview->to_header_label, boldfont);
                gtk_widget_modify_font(headerview->ng_header_label, boldfont);
                gtk_widget_modify_font(headerview->subject_header_label, boldfont);
+               gtk_widget_modify_font(headerview->tags_header_label, boldfont);
                pango_font_description_free(boldfont);
 
                gtk_widget_modify_font(headerview->from_body_label, normalfont);
                gtk_widget_modify_font(headerview->to_body_label, normalfont);
                gtk_widget_modify_font(headerview->ng_body_label, normalfont);
                gtk_widget_modify_font(headerview->subject_body_label, normalfont);
+               gtk_widget_modify_font(headerview->tags_body_label, normalfont);
                pango_font_description_free(normalfont);
        }
 }
@@ -188,6 +205,8 @@ void headerview_init(HeaderView *headerview)
 
 void headerview_show(HeaderView *headerview, MsgInfo *msginfo)
 {
+       gchar *tags = procmsg_msginfo_get_tags_str(msginfo);
+
        headerview_clear(headerview);
 
        gtk_label_set_text(GTK_LABEL(headerview->from_body_label),
@@ -207,7 +226,13 @@ void headerview_show(HeaderView *headerview, MsgInfo *msginfo)
        gtk_label_set_text(GTK_LABEL(headerview->subject_body_label),
                           msginfo->subject ? msginfo->subject :
                           _("(No Subject)"));
-
+       if (tags) {
+               gtk_label_set_text(GTK_LABEL(headerview->tags_body_label),
+                                  tags);
+               gtk_widget_show(headerview->tags_header_label);
+               gtk_widget_show(headerview->tags_body_label);
+               g_free(tags);
+       }
        if (!headerview_show_face(headerview, msginfo))
                return;
 
@@ -296,10 +321,13 @@ void headerview_clear(HeaderView *headerview)
        gtk_label_set_text(GTK_LABEL(headerview->to_body_label), "");
        gtk_label_set_text(GTK_LABEL(headerview->ng_body_label), "");
        gtk_label_set_text(GTK_LABEL(headerview->subject_body_label), "");
+       gtk_label_set_text(GTK_LABEL(headerview->tags_body_label), "");
        gtk_widget_hide(headerview->to_header_label);
        gtk_widget_hide(headerview->to_body_label);
        gtk_widget_hide(headerview->ng_header_label);
        gtk_widget_hide(headerview->ng_body_label);
+       gtk_widget_hide(headerview->tags_header_label);
+       gtk_widget_hide(headerview->tags_body_label);
 
        if (headerview->image && GTK_WIDGET_VISIBLE(headerview->image)) {
                gtk_widget_hide(headerview->image);
index 45795c5..d36adef 100644 (file)
@@ -39,6 +39,8 @@ struct _HeaderView
        GtkWidget *ng_body_label;
        GtkWidget *subject_header_label;
        GtkWidget *subject_body_label;
+       GtkWidget *tags_header_label;
+       GtkWidget *tags_body_label;
 
        GtkWidget *image;
 };
index f56bedb..ab83f0c 100644 (file)
@@ -97,6 +97,7 @@
 #include "imap_gtk.h"
 #include "news_gtk.h"
 #include "matcher.h"
+#include "tags.h"
 #ifdef HAVE_LIBETPAN
 #include "imap-thread.h"
 #endif
@@ -837,6 +838,7 @@ int main(int argc, char *argv[])
        prefs_logging_init();
        prefs_receive_init();
        prefs_send_init();
+       tags_read_tags();
 #ifdef USE_ASPELL
        gtkaspell_checkers_init();
        prefs_spelling_init();
@@ -1204,6 +1206,7 @@ static void exit_claws(MainWindow *mainwin)
        prefs_other_done();
        prefs_receive_done();
        prefs_send_done();
+       tags_write_tags();
 #ifdef USE_ASPELL       
        prefs_spelling_done();
        gtkaspell_checkers_quit();
index 3278346..f95ea4d 100644 (file)
@@ -61,6 +61,7 @@
 #include "procmsg.h"
 #include "import.h"
 #include "export.h"
+#include "edittags.h"
 #include "prefs_common.h"
 #include "prefs_actions.h"
 #include "prefs_filtering.h"
@@ -94,6 +95,7 @@
 #include "foldersort.h"
 #include "icon_legend.h"
 #include "colorlabel.h"
+#include "tags.h"
 #include "textview.h"
 #include "imap.h"
 #include "socket.h"
@@ -409,6 +411,9 @@ static void prefs_template_open_cb  (MainWindow     *mainwin,
 static void prefs_actions_open_cb      (MainWindow     *mainwin,
                                         guint           action,
                                         GtkWidget      *widget);
+static void prefs_tags_open_cb         (MainWindow     *mainwin,
+                                        guint           action,
+                                        GtkWidget      *widget);
 static void prefs_account_open_cb      (MainWindow     *mainwin,
                                         guint           action,
                                         GtkWidget      *widget);
@@ -575,6 +580,8 @@ static GtkItemFactoryEntry mainwin_entries[] =
        {N_("/_View/_Sort/by _To"),             NULL, sort_summary_cb, SORT_BY_TO, "/View/Sort/by number"},
        {N_("/_View/_Sort/by S_ubject"),        NULL, sort_summary_cb, SORT_BY_SUBJECT, "/View/Sort/by number"},
        {N_("/_View/_Sort/by _color label"),
+                                               NULL, sort_summary_cb, SORT_BY_TAGS, "/View/Sort/by number"},
+       {N_("/_View/_Sort/by tag"),
                                                NULL, sort_summary_cb, SORT_BY_LABEL, "/View/Sort/by number"},
        {N_("/_View/_Sort/by _mark"),           NULL, sort_summary_cb, SORT_BY_MARK, "/View/Sort/by number"},
        {N_("/_View/_Sort/by _status"),         NULL, sort_summary_cb, SORT_BY_STATUS, "/View/Sort/by number"},
@@ -808,6 +815,7 @@ static GtkItemFactoryEntry mainwin_entries[] =
        {N_("/_Message/_Mark/Lock"),            NULL, lock_msgs_cb, 0, NULL},
        {N_("/_Message/_Mark/Unlock"),          NULL, unlock_msgs_cb, 0, NULL},
        {N_("/_Message/Color la_bel"),          NULL, NULL,            0, NULL},
+       {N_("/_Message/T_ags"),                 NULL, NULL,            0, NULL},
        {N_("/_Message/---"),                   NULL, NULL, 0, "<Separator>"},
        {N_("/_Message/Re-_edit"),              NULL, reedit_cb, 0, NULL},
 
@@ -887,6 +895,8 @@ static GtkItemFactoryEntry mainwin_entries[] =
                                                NULL, prefs_filtering_open_cb, 0, NULL},
        {N_("/_Configuration/_Templates..."),   NULL, prefs_template_open_cb, 0, NULL},
        {N_("/_Configuration/_Actions..."),     NULL, prefs_actions_open_cb, 0, NULL},
+       {N_("/_Configuration/Tag_s..."),        NULL, prefs_tags_open_cb, 0, NULL},
+       {N_("/_Configuration/---"),             NULL, NULL, 0, "<Separator>"},
        {N_("/_Configuration/Plu_gins..."),     NULL, plugins_open_cb, 0, NULL},
 
        {N_("/_Help"),                          NULL, NULL, 0, "<Branch>"},
@@ -976,6 +986,7 @@ static void mainwindow_colorlabel_menu_item_activate_item_cb(GtkMenuItem *menu_i
        } else
                g_warning("invalid number of color elements (%d)\n", n);
 
+       g_slist_free(sel);
        /* reset "dont_toggle" state */
        g_object_set_data(G_OBJECT(menu), "dont_toggle",
                          GINT_TO_POINTER(0));
@@ -998,6 +1009,94 @@ static void mainwindow_colorlabel_menu_item_activate_cb(GtkWidget *widget,
        summary_set_colorlabel(mainwin->summaryview, color, NULL);
 }
 
+static void mainwindow_tags_menu_item_activate_item_cb(GtkMenuItem *menu_item,
+                                                         gpointer data)
+{
+       MainWindow *mainwin;
+       GtkMenuShell *menu;
+       GList *cur;
+       GSList *sel;
+       GHashTable *menu_table = g_hash_table_new_full(
+                                       g_direct_hash,
+                                       g_direct_equal,
+                                       NULL, NULL);
+
+       mainwin = (MainWindow *)data;
+       g_return_if_fail(mainwin != NULL);
+
+       sel = summary_get_selection(mainwin->summaryview);
+       if (!sel) return;
+
+       menu = GTK_MENU_SHELL(mainwin->tags_menu);
+       g_return_if_fail(menu != NULL);
+
+       /* NOTE: don't return prematurely because we set the "dont_toggle"
+        * state for check menu items */
+       g_object_set_data(G_OBJECT(menu), "dont_toggle",
+                         GINT_TO_POINTER(1));
+
+       /* clear items. get item pointers. */
+       for (cur = menu->children; cur != NULL && cur->data != NULL; cur = cur->next) {
+               if (GTK_IS_CHECK_MENU_ITEM(cur->data)) {
+                       gint id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cur->data),
+                               "tag_id"));
+                       gtk_check_menu_item_set_active
+                               (GTK_CHECK_MENU_ITEM(cur->data), FALSE);
+                               
+                       g_hash_table_insert(menu_table, GINT_TO_POINTER(id), GTK_CHECK_MENU_ITEM(cur->data));
+               }
+       }
+
+       /* iterate all messages and set the state of the appropriate
+        * items */
+       for (; sel != NULL; sel = sel->next) {
+               MsgInfo *msginfo;
+               GSList *tags = NULL;
+               gint id;
+               GtkCheckMenuItem *item;
+               msginfo = (MsgInfo *)sel->data;
+               if (msginfo) {
+                       tags =  msginfo->tags;
+                       if (!tags)
+                               continue;
+
+                       for (; tags; tags = tags->next) {
+                               id = GPOINTER_TO_INT(tags->data);
+                               item = g_hash_table_lookup(menu_table, GINT_TO_POINTER(tags->data));
+                               if (item && !item->active)
+                                       gtk_check_menu_item_set_active
+                                               (item, TRUE);
+                       }
+               }
+       }
+
+       g_slist_free(sel);
+       g_hash_table_destroy(menu_table);
+       /* reset "dont_toggle" state */
+       g_object_set_data(G_OBJECT(menu), "dont_toggle",
+                         GINT_TO_POINTER(0));
+}
+
+static void mainwindow_tags_menu_item_activate_cb(GtkWidget *widget,
+                                                    gpointer data)
+{
+       gint id = GPOINTER_TO_INT(data);
+       gboolean set = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
+       MainWindow *mainwin;
+
+       mainwin = g_object_get_data(G_OBJECT(widget), "mainwin");
+       g_return_if_fail(mainwin != NULL);
+
+       /* "dont_toggle" state set? */
+       if (g_object_get_data(G_OBJECT(mainwin->tags_menu),
+                               "dont_toggle"))
+               return;
+
+       if (!set)
+               id = -id;
+       summary_set_tag(mainwin->summaryview, id, NULL);
+}
+
 static void mainwindow_colorlabel_menu_create(MainWindow *mainwin, gboolean refresh)
 {
        GtkWidget *label_menuitem;
@@ -1058,6 +1157,82 @@ static void mainwindow_colorlabel_menu_create(MainWindow *mainwin, gboolean refr
        mainwin->colorlabel_menu = menu;
 }
 
+static void mainwindow_tags_menu_item_new_tag_activate_cb(GtkWidget *widget,
+                                                    gpointer data)
+{
+       MainWindow *mainwin;
+       gint id;
+       mainwin = g_object_get_data(G_OBJECT(widget), "mainwin");
+       g_return_if_fail(mainwin != NULL);
+
+       /* "dont_toggle" state set? */
+       if (g_object_get_data(G_OBJECT(mainwin->tags_menu),
+                               "dont_toggle"))
+               return;
+       
+       id = prefs_tags_create_new(mainwin);
+       if (id != -1) {
+               summary_set_tag(mainwin->summaryview, id, NULL);
+               main_window_reflect_tags_changes(mainwindow_get_mainwindow());
+       }
+}
+
+static void mainwindow_tags_menu_create(MainWindow *mainwin, gboolean refresh)
+{
+       GtkWidget *label_menuitem;
+       GtkWidget *menu;
+       GtkWidget *item;
+       GSList *cur = tags_get_list();
+       GSList *orig = cur;
+       gboolean existing_tags = FALSE;
+
+       label_menuitem = gtk_item_factory_get_item(mainwin->menu_factory,
+                                                  "/Message/Tags");
+       g_signal_connect(G_OBJECT(label_menuitem), "activate",
+                        G_CALLBACK(mainwindow_tags_menu_item_activate_item_cb),
+                          mainwin);
+
+       gtk_widget_show(label_menuitem);
+
+       menu = gtk_menu_new();
+
+       /* create tags menu items */
+       for (; cur; cur = cur->next) {
+               gint id = GPOINTER_TO_INT(cur->data);
+               const gchar *tag = tags_get_tag(id);
+
+               item = gtk_check_menu_item_new_with_label(tag);
+               gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+               g_signal_connect(G_OBJECT(item), "activate",
+                                G_CALLBACK(mainwindow_tags_menu_item_activate_cb),
+                                GINT_TO_POINTER(id));
+               g_object_set_data(G_OBJECT(item), "mainwin",
+                                 mainwin);
+               g_object_set_data(G_OBJECT(item), "tag_id",
+                                 GINT_TO_POINTER(id));
+               gtk_widget_show(item);
+               existing_tags = TRUE;
+       }
+       if (existing_tags) {
+               /* separator */
+               item = gtk_menu_item_new();
+               gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+               gtk_widget_show(item);
+       }
+       item = gtk_menu_item_new_with_label(_("New tag..."));
+       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       g_signal_connect(G_OBJECT(item), "activate",
+                        G_CALLBACK(mainwindow_tags_menu_item_new_tag_activate_cb),
+                        NULL);
+       g_object_set_data(G_OBJECT(item), "mainwin",
+                         mainwin);
+       gtk_widget_show(item);
+       g_slist_free(orig);
+       gtk_widget_show(menu);
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM(label_menuitem), menu);
+       mainwin->tags_menu = menu;
+}
+
 static gboolean warning_icon_pressed(GtkWidget *widget, GdkEventButton *evt,
                                    MainWindow *mainwindow)
 {
@@ -1505,6 +1680,7 @@ MainWindow *main_window_create()
                online_switch_clicked (GTK_BUTTON(online_switch), mainwin);
 
        mainwindow_colorlabel_menu_create(mainwin, FALSE);
+       mainwindow_tags_menu_create(mainwin, FALSE);
        
        return mainwin;
 }
@@ -1662,6 +1838,24 @@ void main_window_reflect_prefs_custom_colors(MainWindow *mainwin)
 
 }
 
+void main_window_reflect_tags_changes(MainWindow *mainwin)
+{
+       GtkMenuShell *menu;
+       GList *cur;
+
+       /* re-create tags submenu */
+       menu = GTK_MENU_SHELL(mainwin->tags_menu);
+       g_return_if_fail(menu != NULL);
+
+       /* clear items. get item pointers. */
+       for (cur = menu->children; cur != NULL && cur->data != NULL; cur = cur->next) {
+               gtk_menu_item_remove_submenu(GTK_MENU_ITEM(cur->data));
+       }
+       mainwindow_tags_menu_create(mainwin, TRUE);
+       summary_reflect_tags_changes(mainwin->summaryview);
+
+}
+
 void main_window_reflect_prefs_all_real(gboolean pixmap_theme_changed)
 {
        if (prefs_tag == 0 || pixmap_theme_changed) {
@@ -2173,6 +2367,7 @@ SensitiveCond main_window_get_current_state(MainWindow *mainwin)
        SummarySelection selection;
        FolderItem *item = mainwin->summaryview->folder_item;
        GList *account_list = account_get_list();
+       GSList *tmp;
        
        selection = summary_get_selection_type(mainwin->summaryview);
 
@@ -2210,6 +2405,11 @@ SensitiveCond main_window_get_current_state(MainWindow *mainwin)
        if (prefs_common.actions_list && g_slist_length(prefs_common.actions_list))
                state |= M_ACTIONS_EXIST;
 
+       tmp = tags_get_list();
+       if (tmp && g_slist_length(tmp))
+               state |= M_TAGS_EXIST;
+       g_slist_free(tmp);
+
        if (procmsg_have_queued_mails_fast() && !procmsg_is_sending())
                state |= M_HAVE_QUEUED_MAILS;
 
@@ -2327,6 +2527,7 @@ void main_window_set_menu_sensitive(MainWindow *mainwin)
                {"/Message/Mark/Lock"             , M_TARGET_EXIST},
                {"/Message/Mark/Unlock"           , M_TARGET_EXIST},
                {"/Message/Color label"           , M_TARGET_EXIST},
+               {"/Message/Tags"                  , M_TARGET_EXIST},
                {"/Message/Re-edit"               , M_HAVE_ACCOUNT|M_ALLOW_REEDIT},
 
                {"/Tools/Add sender to address book"   , M_SINGLE_TARGET_EXIST},
@@ -3808,6 +4009,12 @@ static void prefs_actions_open_cb(MainWindow *mainwin, guint action,
 {
        prefs_actions_open(mainwin);
 }
+
+static void prefs_tags_open_cb(MainWindow *mainwin, guint action,
+                                 GtkWidget *widget)
+{
+       prefs_tags_open(mainwin);
+}
 #ifdef USE_OPENSSL
 static void ssl_manager_open_cb(MainWindow *mainwin, guint action,
                                  GtkWidget *widget)
index ed9c941..1326fd9 100644 (file)
@@ -57,7 +57,8 @@ typedef enum
        M_CAN_LEARN_SPAM      = 1 << 16,
        M_ACTIONS_EXIST       = 1 << 17,
        M_HAVE_QUEUED_MAILS   = 1 << 18,
-       M_WANT_SYNC           = 1 << 19
+       M_WANT_SYNC           = 1 << 19,
+       M_TAGS_EXIST          = 1 << 20
 } SensitiveCond;
 
 typedef enum
@@ -129,6 +130,7 @@ struct _MainWindow
        
        GtkWidget       *colorlabel_menu;
        GtkWidget       *warning_btn;
+       GtkWidget       *tags_menu;
        
 #ifdef HAVE_LIBSM
        gpointer smc_conn;
@@ -152,6 +154,7 @@ void main_window_reflect_prefs_all_real     (gboolean        pixmap_theme_changed);
 void main_window_reflect_prefs_all     (void);
 void main_window_reflect_prefs_all_now (void);
 void main_window_reflect_prefs_custom_colors(MainWindow        *mainwindow);
+void main_window_reflect_tags_changes(MainWindow       *mainwindow);
 void main_window_set_summary_column    (void);
 void main_window_set_folder_column     (void);
 void main_window_set_account_menu      (GList          *account_list);
index 87581c0..ba3a34d 100644 (file)
@@ -35,6 +35,7 @@ typedef enum
 #define MANUAL_ANCHOR_TEMPLATES                "adv_templates"
 #define MANUAL_ANCHOR_PROCESSING       "adv_processing"
 #define MANUAL_ANCHOR_PLUGINS          "adv_plugins"
+#define MANUAL_ANCHOR_TAGS             "adv_tags"
 
 gboolean manual_available      (ManualType type);
 void    manual_open            (ManualType type, gchar *url_anchor);
index 5cc8f59..f9af36d 100644 (file)
@@ -45,6 +45,7 @@
 #include <ctype.h>
 #include "prefs_common.h"
 #include "log.h"
+#include "tags.h"
 
 /*!
  *\brief       Keyword lookup element
@@ -94,6 +95,10 @@ static const MatchParser matchparser_tab[] = {
        {MATCHCRITERIA_NOT_CC, "~cc"},
        {MATCHCRITERIA_TO_OR_CC, "to_or_cc"},
        {MATCHCRITERIA_NOT_TO_AND_NOT_CC, "~to_or_cc"},
+       {MATCHCRITERIA_TAG, "tag"},
+       {MATCHCRITERIA_NOT_TAG, "~tag"},
+       {MATCHCRITERIA_TAGGED, "tagged"},
+       {MATCHCRITERIA_NOT_TAGGED, "~tagged"},
        {MATCHCRITERIA_AGE_GREATER, "age_greater"},
        {MATCHCRITERIA_AGE_LOWER, "age_lower"},
        {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
@@ -155,7 +160,10 @@ static const MatchParser matchparser_tab[] = {
        {MATCHACTION_STOP, "stop"},
        {MATCHACTION_HIDE, "hide"},
        {MATCHACTION_IGNORE, "ignore"},
-       {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"}
+       {MATCHACTION_ADD_TO_ADDRESSBOOK, "add_to_addressbook"},
+       {MATCHACTION_SET_TAG, "set_tag"},
+       {MATCHACTION_UNSET_TAG, "unset_tag"},
+       {MATCHACTION_CLEAR_TAGS, "clear_tags"},
 };
 
 enum {
@@ -500,6 +508,36 @@ static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str,
        return ret;
 }
 
+/*!
+ *\brief       Find out if a tag matches a condition
+ *
+ *\param       prop Matcher structure
+ *\param       msginfo message to check
+ *
+ *\return      gboolean TRUE if msginfo matches the condition in the 
+ *             matcher structure
+ */
+static gboolean matcherprop_tag_match(MatcherProp *prop, MsgInfo *msginfo,
+                                        const gchar *debug_context)
+{
+       gboolean ret = FALSE;
+       GSList *cur;
+
+       if (msginfo == NULL || msginfo->tags == NULL)
+               return FALSE;
+
+       for (cur = msginfo->tags; cur; cur = cur->next) {
+               const gchar *str = tags_get_tag(GPOINTER_TO_INT(cur->data));
+               if (!str)
+                       continue;
+               if (matcherprop_string_match(prop, str, debug_context)) {
+                       ret = TRUE;
+                       break;
+               }
+       }
+       return ret;
+}
+
 /*!
  *\brief       Find out if the string-ed list matches a condition
  *
@@ -832,6 +870,22 @@ gboolean matcherprop_match(MatcherProp *prop,
                g_free(context2);
                return ret;
        }
+       case MATCHCRITERIA_TAG:
+       case MATCHCRITERIA_NOT_TAG:
+       {
+               gboolean ret;
+
+               ret = matcherprop_tag_match(prop, info, _("Tag"));
+               return (prop->criteria == MATCHCRITERIA_TAG)? ret : !ret;
+       }
+       case MATCHCRITERIA_TAGGED:
+       case MATCHCRITERIA_NOT_TAGGED:
+       {
+               gboolean ret;
+
+               ret = (info->tags != NULL);
+               return (prop->criteria == MATCHCRITERIA_TAGGED)? ret : !ret;
+       }
        case MATCHCRITERIA_AGE_GREATER:
        {
                gboolean ret;
@@ -1604,6 +1658,10 @@ gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
                case MATCHCRITERIA_NOT_CC:
                case MATCHCRITERIA_TO_OR_CC:
                case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
+               case MATCHCRITERIA_TAG:
+               case MATCHCRITERIA_NOT_TAG:
+               case MATCHCRITERIA_TAGGED:
+               case MATCHCRITERIA_NOT_TAGGED:
                case MATCHCRITERIA_AGE_GREATER:
                case MATCHCRITERIA_AGE_LOWER:
                case MATCHCRITERIA_NEWSGROUPS:
@@ -1783,6 +1841,8 @@ gchar *matcherprop_to_string(MatcherProp *matcher)
        case MATCHCRITERIA_NOT_PARTIAL:
        case MATCHCRITERIA_IGNORE_THREAD:
        case MATCHCRITERIA_NOT_IGNORE_THREAD:
+       case MATCHCRITERIA_TAGGED:
+       case MATCHCRITERIA_NOT_TAGGED:
                return g_strdup(criteria_str);
        case MATCHCRITERIA_TEST:
        case MATCHCRITERIA_NOT_TEST:
index 0f22d84..f341040 100644 (file)
@@ -103,6 +103,8 @@ enum {
        MC_(SIZE_SMALLER),
        MC_(SIZE_EQUAL),
        MC_(FOUND_IN_ADDRESSBOOK),MC_(NOT_FOUND_IN_ADDRESSBOOK),
+       MC_(TAG),MC_(NOT_TAG),
+       MC_(TAGGED),MC_(NOT_TAGGED),
 
        /* match type */
        MT_(MATCHCASE),
@@ -133,6 +135,9 @@ enum {
        MA_(HIDE),
        MA_(IGNORE),
        MA_(ADD_TO_ADDRESSBOOK),
+       MA_(SET_TAG),
+       MA_(UNSET_TAG),
+       MA_(CLEAR_TAGS),
        /* boolean operations */
        MB_(OR),
        MB_(AND)
index a077b0c..0b0d0d9 100644 (file)
@@ -336,6 +336,8 @@ int matcher_parserwrap(void)
 %token MATCHER_ADD_TO_ADDRESSBOOK
 %token MATCHER_STOP MATCHER_HIDE MATCHER_IGNORE
 %token MATCHER_SPAM MATCHER_NOT_SPAM
+%token MATCHER_TAG MATCHER_NOT_TAG MATCHER_SET_TAG MATCHER_UNSET_TAG
+%token MATCHER_TAGGED MATCHER_NOT_TAGGED MATCHER_CLEAR_TAGS
 
 %start file
 
@@ -848,6 +850,38 @@ MATCHER_ALL
        expr = $3;
        prop = matcherprop_new(criteria, NULL, match_type, expr, 0);
 }
+| MATCHER_TAG match_type MATCHER_STRING
+{
+       gint criteria = 0;
+       gchar *expr = NULL;
+
+       criteria = MATCHCRITERIA_TAG;
+       expr = $3;
+       prop = matcherprop_new(criteria, NULL, match_type, expr, 0);
+}
+| MATCHER_NOT_TAG match_type MATCHER_STRING
+{
+       gint criteria = 0;
+       gchar *expr = NULL;
+
+       criteria = MATCHCRITERIA_NOT_TAG;
+       expr = $3;
+       prop = matcherprop_new(criteria, NULL, match_type, expr, 0);
+}
+| MATCHER_TAGGED
+{
+       gint criteria = 0;
+
+       criteria = MATCHCRITERIA_TAGGED;
+       prop = matcherprop_new(criteria, NULL, 0, NULL, 0);
+}
+| MATCHER_NOT_TAGGED
+{
+       gint criteria = 0;
+
+       criteria = MATCHCRITERIA_NOT_TAGGED;
+       prop = matcherprop_new(criteria, NULL, 0, NULL, 0);
+}
 | MATCHER_AGE_GREATER MATCHER_INTEGER
 {
        gint criteria = 0;
@@ -1116,6 +1150,31 @@ MATCHER_EXECUTE MATCHER_STRING
        destination = $2;
        action = filteringaction_new(action_type, 0, destination, 0, 0, NULL);
 }
+| MATCHER_SET_TAG MATCHER_STRING
+{
+       gchar *destination = NULL;
+       gint action_type = 0;
+
+       action_type = MATCHACTION_SET_TAG;
+       destination = $2;
+       action = filteringaction_new(action_type, 0, destination, 0, 0, NULL);
+}
+| MATCHER_UNSET_TAG MATCHER_STRING
+{
+       gchar *destination = NULL;
+       gint action_type = 0;
+
+       action_type = MATCHACTION_UNSET_TAG;
+       destination = $2;
+       action = filteringaction_new(action_type, 0, destination, 0, 0, NULL);
+}
+| MATCHER_CLEAR_TAGS
+{
+       gint action_type = 0;
+
+       action_type = MATCHACTION_CLEAR_TAGS;
+       action = filteringaction_new(action_type, 0, NULL, 0, 0, NULL);
+}
 | MATCHER_COPY MATCHER_STRING
 {
        gchar *destination = NULL;
index cadfd84..6d087b3 100644 (file)
@@ -2399,6 +2399,15 @@ void mimeview_handle_cmd(MimeView *mimeview, const gchar *cmd, GdkEventButton *e
        } else if (!strcmp(cmd, "sc://menu_attachment") && data != NULL) {
                mimeview->spec_part = (MimeInfo *)data;
                part_button_pressed(mimeview, event, (MimeInfo *)data);
+       } else if (!strncmp(cmd, "sc://search_tags:", strlen("sc://search_tags:"))) {
+               const gchar *tagname = cmd + strlen("sc://search_tags:");
+               gchar *buf = g_strdup_printf("tag matchcase \"%s\"", tagname);
+               gtk_toggle_button_set_active(
+                               GTK_TOGGLE_BUTTON(mimeview->messageview->mainwin->summaryview->toggle_search), 
+                               TRUE);
+               quicksearch_set(mimeview->messageview->mainwin->summaryview->quicksearch, 
+                               QUICK_SEARCH_EXTENDED, buf);
+               g_free(buf);
        }
 }
 
index 932fa6d..0ee9da9 100644 (file)
@@ -39,6 +39,7 @@
 #include "procmsg.h"
 #include "codeconv.h"
 #include "timing.h"
+#include "tags.h"
 
 #ifdef HAVE_FWRITE_UNLOCKED
 #define SC_FWRITE fwrite_unlocked
@@ -818,6 +819,91 @@ void msgcache_read_mark(MsgCache *cache, const gchar *mark_file)
        fclose(fp);
 }
 
+void msgcache_read_tags(MsgCache *cache, const gchar *tags_file)
+{
+       FILE *fp;
+       MsgInfo *msginfo;
+       guint32 num;
+       gint map_len = -1;
+       char *cache_data = NULL;
+       struct stat st;
+       
+       swapping = TRUE;
+
+       /* In case we can't open the mark file with MARK_VERSION, check if we can open it with the
+        * swapped MARK_VERSION. As msgcache_open_data_file swaps it too, if this succeeds, 
+        * it means it's the old version (not little-endian) on a big-endian machine. The code has
+        * no effect on x86 as their file doesn't change. */
+
+       if ((fp = msgcache_open_data_file(tags_file, TAGS_VERSION, DATA_READ, NULL, 0)) == NULL) {
+               /* see if it isn't swapped ? */
+               if ((fp = msgcache_open_data_file(tags_file, bswap_32(TAGS_VERSION), DATA_READ, NULL, 0)) == NULL)
+                       return;
+               else
+                       swapping = FALSE; /* yay */
+       }
+       debug_print("reading %sswapped tags file.\n", swapping?"":"un");
+       
+       if (msgcache_use_mmap_read) {
+               if (fstat(fileno(fp), &st) >= 0)
+                       map_len = st.st_size;
+               else
+                       map_len = -1;
+               if (map_len > 0)
+                       cache_data = mmap(NULL, map_len, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
+       } else {
+               cache_data = NULL;
+       }
+       if (cache_data != NULL && cache_data != MAP_FAILED) {
+               int rem_len = map_len-ftell(fp);
+               char *walk_data = cache_data+ftell(fp);
+
+               while(rem_len > 0) {
+                       gint id = -1;
+                       GET_CACHE_DATA_INT(num);
+                       msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
+                       if(msginfo) {
+                               g_slist_free(msginfo->tags);
+                               msginfo->tags = NULL;
+                               do {
+                                       GET_CACHE_DATA_INT(id);
+                                       if (id > 0) {
+                                               msginfo->tags = g_slist_prepend(
+                                                       msginfo->tags, 
+                                                       GINT_TO_POINTER(id));
+                                       }
+                               } while (id > 0);
+                               msginfo->tags = g_slist_reverse(msginfo->tags);
+                       }
+               }
+               munmap(cache_data, map_len);
+       } else {
+               while (fread(&num, sizeof(num), 1, fp) == 1) {
+                       gint id = -1;
+                       if (swapping)
+                               num = bswap_32(num);
+                       msginfo = g_hash_table_lookup(cache->msgnum_table, &num);
+                       if(msginfo) {
+                               g_slist_free(msginfo->tags);
+                               msginfo->tags = NULL;
+                               do {
+                                       if (fread(&id, sizeof(id), 1, fp) != 1) 
+                                               id = -1;
+                                       if (swapping)
+                                               id = bswap_32(id);
+                                       if (id > 0) {
+                                               msginfo->tags = g_slist_prepend(
+                                                       msginfo->tags, 
+                                                       GINT_TO_POINTER(id));
+                                       }
+                               } while (id > 0);
+                               msginfo->tags = g_slist_reverse(msginfo->tags);
+                       }
+               }
+       }
+       fclose(fp);
+}
+
 static int msgcache_write_cache(MsgInfo *msginfo, FILE *fp)
 {
        MsgTmpFlags flags = msginfo->flags.tmp_flags & MSG_CACHED_FLAG_MASK;
@@ -894,6 +980,23 @@ static int msgcache_write_flags(MsgInfo *msginfo, FILE *fp)
        return w_err ? -1 : wrote;
 }
 
+static int msgcache_write_tags(MsgInfo *msginfo, FILE *fp)
+{
+       GSList *cur = msginfo->tags;
+       int w_err = 0, wrote = 0;
+
+       WRITE_CACHE_DATA_INT(msginfo->msgnum, fp);
+       for (; cur; cur = cur->next) {
+               gint id = GPOINTER_TO_INT(cur->data);
+               if (tags_get_tag(id) != NULL) {
+                       WRITE_CACHE_DATA_INT(id, fp);
+               }
+       }
+       WRITE_CACHE_DATA_INT(-1, fp);
+
+       return w_err ? -1 : wrote;
+}
+
 static int msgcache_write_mmap_flags(MsgInfo *msginfo, char *walk_data)
 {
        MsgPermFlags flags = msginfo->flags.perm_flags;
@@ -904,15 +1007,34 @@ static int msgcache_write_mmap_flags(MsgInfo *msginfo, char *walk_data)
        return wrote;
 }
 
+static int msgcache_write_mmap_tags(MsgInfo *msginfo, char *walk_data)
+{
+       GSList *cur = msginfo->tags;
+       int wrote = 0;
+       
+       PUT_CACHE_DATA_INT(msginfo->msgnum);
+       for (; cur; cur = cur->next) {
+               gint id = GPOINTER_TO_INT(cur->data);
+               if (tags_get_tag(id) != NULL) {
+                       PUT_CACHE_DATA_INT(id);
+               }
+       }
+       PUT_CACHE_DATA_INT(-1);
+       return wrote;
+}
+
 struct write_fps
 {
        FILE *cache_fp;
        FILE *mark_fp;
+       FILE *tags_fp;
        char *cache_data;
        char *mark_data;
+       char *tags_data;
        int error;
        guint cache_size;
        guint mark_size;
+       guint tags_size;
 };
 
 static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data)
@@ -934,6 +1056,11 @@ static void msgcache_write_func(gpointer key, gpointer value, gpointer user_data
                write_fps->error = 1;
        else
                write_fps->mark_size += tmp;
+       tmp = msgcache_write_tags(msginfo, write_fps->tags_fp);
+       if (tmp < 0)
+               write_fps->error = 1;
+       else
+               write_fps->tags_size += tmp;
 }
 
 static void msgcache_write_mmap_func(gpointer key, gpointer value, gpointer user_data)
@@ -951,27 +1078,36 @@ static void msgcache_write_mmap_func(gpointer key, gpointer value, gpointer user
        tmp = msgcache_write_mmap_flags(msginfo, write_fps->mark_data);
        write_fps->mark_size += tmp;
        write_fps->mark_data += tmp;
+       tmp = msgcache_write_mmap_tags(msginfo, write_fps->tags_data);
+       write_fps->tags_size += tmp;
+       write_fps->tags_data += tmp;
 }
 
-gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *cache)
+gint msgcache_write(const gchar *cache_file, const gchar *mark_file, const gchar *tags_file, MsgCache *cache)
 {
        struct write_fps write_fps;
-       gchar *new_cache, *new_mark;
+       gchar *new_cache, *new_mark, *new_tags;
        int w_err = 0, wrote = 0;
        gint map_len = -1;
        char *cache_data = NULL;
        char *mark_data = NULL;
+       char *tags_data = NULL;
+
        START_TIMING("");
        g_return_val_if_fail(cache_file != NULL, -1);
        g_return_val_if_fail(mark_file != NULL, -1);
+       g_return_val_if_fail(tags_file != NULL, -1);
        g_return_val_if_fail(cache != NULL, -1);
 
        new_cache = g_strconcat(cache_file, ".new", NULL);
        new_mark  = g_strconcat(mark_file, ".new", NULL);
+       new_tags  = g_strconcat(tags_file, ".new", NULL);
 
        write_fps.error = 0;
        write_fps.cache_size = 0;
        write_fps.mark_size = 0;
+       write_fps.tags_size = 0;
+
        write_fps.cache_fp = msgcache_open_data_file(new_cache, CACHE_VERSION,
                DATA_WRITE, NULL, 0);
        if (write_fps.cache_fp == NULL) {
@@ -988,6 +1124,7 @@ gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *c
                g_unlink(new_cache);
                g_free(new_cache);
                g_free(new_mark);
+               g_free(new_tags);
                return -1;
        }
 
@@ -998,6 +1135,20 @@ gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *c
                g_unlink(new_cache);
                g_free(new_cache);
                g_free(new_mark);
+               g_free(new_tags);
+               return -1;
+       }
+
+       write_fps.tags_fp = msgcache_open_data_file(new_tags, TAGS_VERSION,
+               DATA_WRITE, NULL, 0);
+       if (write_fps.tags_fp == NULL) {
+               fclose(write_fps.cache_fp);
+               fclose(write_fps.mark_fp);
+               g_unlink(new_cache);
+               g_unlink(new_mark);
+               g_free(new_cache);
+               g_free(new_mark);
+               g_free(new_tags);
                return -1;
        }
 
@@ -1008,6 +1159,8 @@ gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *c
 
        write_fps.cache_size = ftell(write_fps.cache_fp);
        write_fps.mark_size = ftell(write_fps.mark_fp);
+       write_fps.tags_size = ftell(write_fps.tags_fp);
+
        if (msgcache_use_mmap_write && cache->memusage > 0) {
                map_len = cache->memusage;
                if (ftruncate(fileno(write_fps.cache_fp), (off_t)map_len) == 0) {
@@ -1022,6 +1175,17 @@ gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *c
                        if (mark_data == NULL || mark_data == MAP_FAILED) {
                                munmap(cache_data, map_len);
                                cache_data = NULL;
+                       } else {
+                               if (ftruncate(fileno(write_fps.tags_fp), (off_t)map_len) == 0) {
+                                       tags_data = mmap(NULL, map_len, PROT_WRITE, MAP_SHARED, 
+                                               fileno(write_fps.tags_fp), 0);
+                               } 
+                               if (tags_data == NULL || tags_data == MAP_FAILED) {
+                                       munmap(cache_data, map_len);
+                                       cache_data = NULL;
+                                       munmap(mark_data, map_len);
+                                       mark_data = NULL;
+                               } 
                        }
                }
        }
@@ -1029,49 +1193,61 @@ gint msgcache_write(const gchar *cache_file, const gchar *mark_file, MsgCache *c
        if (cache_data != NULL && cache_data != MAP_FAILED) {
                write_fps.cache_data = cache_data + ftell(write_fps.cache_fp);
                write_fps.mark_data = mark_data + ftell(write_fps.mark_fp);
+               write_fps.tags_data = mark_data + ftell(write_fps.tags_fp);
                g_hash_table_foreach(cache->msgnum_table, msgcache_write_mmap_func, (gpointer)&write_fps);
                munmap(cache_data, map_len);
                munmap(mark_data, map_len);
+               munmap(tags_data, map_len);
                ftruncate(fileno(write_fps.cache_fp), write_fps.cache_size);
                ftruncate(fileno(write_fps.mark_fp), write_fps.mark_size);
+               ftruncate(fileno(write_fps.tags_fp), write_fps.tags_size);
        } else {
 #ifdef HAVE_FWRITE_UNLOCKED
                flockfile(write_fps.cache_fp);
                flockfile(write_fps.mark_fp);
+               flockfile(write_fps.tags_fp);
 #endif
                g_hash_table_foreach(cache->msgnum_table, msgcache_write_func, (gpointer)&write_fps);
 #ifdef HAVE_FWRITE_UNLOCKED
                funlockfile(write_fps.mark_fp);
                funlockfile(write_fps.cache_fp);
+               funlockfile(write_fps.tags_fp);
 #endif
        }
        
        fflush(write_fps.cache_fp);
        fflush(write_fps.mark_fp);
+       fflush(write_fps.tags_fp);
 
 #if 0
        fsync(fileno(write_fps.cache_fp));
        fsync(fileno(write_fps.mark_fp));
+       fsync(fileno(write_fps.tags_fp));
 #endif
 
        fclose(write_fps.cache_fp);
        fclose(write_fps.mark_fp);
+       fclose(write_fps.tags_fp);
 
 
        if (write_fps.error != 0) {
                g_unlink(new_cache);
                g_unlink(new_mark);
+               g_unlink(new_tags);
                g_free(new_cache);
                g_free(new_mark);
+               g_free(new_tags);
                return -1;
        } else {
                move_file(new_cache, cache_file, TRUE);
                move_file(new_mark, mark_file, TRUE);
+               move_file(new_tags, tags_file, TRUE);
                cache->last_access = time(NULL);
        }
 
        g_free(new_cache);
        g_free(new_mark);
+       g_free(new_tags);
        debug_print("done.\n");
        END_TIMING();
        return 0;
index d941bdb..51736fa 100644 (file)
@@ -37,8 +37,11 @@ MsgCache     *msgcache_read_cache                    (FolderItem *item,
                                                         const gchar *cache_file);
 void            msgcache_read_mark                     (MsgCache *cache,
                                                         const gchar *mark_file);
+void            msgcache_read_tags                     (MsgCache *cache,
+                                                        const gchar *tags_file);
 gint            msgcache_write                         (const gchar *cache_file,
                                                         const gchar *mark_file,
+                                                        const gchar *tags_file,
                                                         MsgCache *cache);
 void            msgcache_add_msg                       (MsgCache *cache,
                                                         MsgInfo *msginfo);
index 904f955..e806fc1 100644 (file)
@@ -457,6 +457,8 @@ static PrefParam param[] = {
         &prefs_common.summary_col_visible[S_COL_SCORE], P_BOOL, NULL, NULL, NULL},
        {"summary_col_show_locked", "FALSE",
         &prefs_common.summary_col_visible[S_COL_LOCKED], P_BOOL, NULL, NULL, NULL},
+       {"summary_col_show_tags", "FALSE",
+        &prefs_common.summary_col_visible[S_COL_TAGS], P_BOOL, NULL, NULL, NULL},
 
        {"summary_col_pos_mark", "0",
          &prefs_common.summary_col_pos[S_COL_MARK], P_INT, NULL, NULL, NULL},
@@ -480,6 +482,8 @@ static PrefParam param[] = {
         &prefs_common.summary_col_pos[S_COL_LOCKED], P_INT, NULL, NULL, NULL},
        {"summary_col_pos_to", "10",
          &prefs_common.summary_col_pos[S_COL_TO], P_INT, NULL, NULL, NULL},
+       {"summary_col_pos_tags", "11",
+         &prefs_common.summary_col_pos[S_COL_TAGS], P_INT, NULL, NULL, NULL},
 
        {"summary_col_size_mark", "10",
         &prefs_common.summary_col_size[S_COL_MARK], P_INT, NULL, NULL, NULL},
@@ -513,6 +517,8 @@ static PrefParam param[] = {
         &prefs_common.summary_col_size[S_COL_SCORE], P_INT, NULL, NULL, NULL},
        {"summary_col_size_locked", "13",
         &prefs_common.summary_col_size[S_COL_LOCKED], P_INT, NULL, NULL, NULL},
+       {"summary_col_size_tags", "150",
+        &prefs_common.summary_col_size[S_COL_TAGS], P_INT, NULL, NULL, NULL},
 
        /* Widget size */
        {"folderwin_x", "16", &prefs_common.folderwin_x, P_INT,
@@ -870,6 +876,11 @@ static PrefParam param[] = {
        {"actionswin_height", "-1", &prefs_common.actionswin_height, P_INT,
         NULL, NULL, NULL},
 
+       {"tagswin_width", "486", &prefs_common.tagswin_width, P_INT,
+        NULL, NULL, NULL},
+       {"tagswin_height", "-1", &prefs_common.tagswin_height, P_INT,
+        NULL, NULL, NULL},
+
        {"addressbookwin_width", "520", &prefs_common.addressbookwin_width, P_INT,
         NULL, NULL, NULL},
        {"addressbookwin_height", "-1", &prefs_common.addressbookwin_height, P_INT,
index cdd8ecb..89faf8c 100644 (file)
@@ -418,6 +418,8 @@ struct _PrefsCommon
        gint templateswin_height;
        gint actionswin_width;
        gint actionswin_height;
+       gint tagswin_width;
+       gint tagswin_height;
        gint addressbookwin_width;
        gint addressbookwin_height;
        gint addressbookeditpersonwin_width;
index 45e765a..c1dc05d 100644 (file)
@@ -47,7 +47,7 @@
 #include "folder.h"
 #include "description_window.h"
 #include "addr_compl.h"
-
+#include "tags.h"
 #include "matcher_parser.h"
 #include "colorlabel.h"
 
@@ -121,6 +121,9 @@ static struct FilteringAction_ {
        GtkWidget *header_entry;
        GtkWidget *addressbook_label;
        GtkWidget *addressbook_btn;
+       GtkWidget *tags_label;
+       GtkWidget *tags_list;
+       GtkWidget *tags_combo;
 
        gint current_action;
 } filtering_action;
@@ -145,6 +148,9 @@ typedef enum Action_ {
        ACTION_COLOR,
        ACTION_CHANGE_SCORE,
        ACTION_SET_SCORE,
+       ACTION_SET_TAG,
+       ACTION_UNSET_TAG,
+       ACTION_CLEAR_TAGS,
        ACTION_HIDE,
        ACTION_IGNORE,
        ACTION_ADD_TO_ADDRESSBOOK,
@@ -174,6 +180,9 @@ static struct {
        { N_("Color"),                  ACTION_COLOR    },
        { N_("Change score"),           ACTION_CHANGE_SCORE},
        { N_("Set score"),              ACTION_SET_SCORE},
+       { N_("Apply tag"),              ACTION_SET_TAG},
+       { N_("Unset tag"),              ACTION_UNSET_TAG},
+       { N_("Clear tags"),             ACTION_CLEAR_TAGS},
        { N_("Hide"),                   ACTION_HIDE     },
        { N_("Ignore thread"),          ACTION_IGNORE   },
        { N_("Add to address book"),    ACTION_ADD_TO_ADDRESSBOOK       },
@@ -287,6 +296,9 @@ static void prefs_filtering_action_create(void)
        GtkWidget *addressbook_btn;
        GtkWidget *dest_entry;
        GtkWidget *dest_btn;
+       GtkWidget *tags_label;
+       GtkWidget *tags_list;
+       GtkWidget *tags_combo;
         GList * cur;
 
        GtkWidget *reg_hbox;
@@ -313,6 +325,7 @@ static void prefs_filtering_action_create(void)
        static GdkGeometry geometry;
 
         GList * accounts;
+       GSList *tmp, *tags;
 
        debug_print("Creating matcher configuration window...\n");
 
@@ -485,6 +498,11 @@ static void prefs_filtering_action_create(void)
        gtk_misc_set_alignment(GTK_MISC(addressbook_label), 0, 0.5);
        gtk_box_pack_start(GTK_BOX(hbox1), addressbook_label, FALSE, FALSE, 0);
 
+       tags_label = gtk_label_new (_("Tag"));
+       gtk_widget_show (tags_label);
+       gtk_misc_set_alignment (GTK_MISC (tags_label), 0, 0.5);
+       gtk_box_pack_start (GTK_BOX (hbox1), tags_label, FALSE, FALSE, 0);
+
        dest_entry = gtk_entry_new ();
        gtk_widget_set_size_request (dest_entry, 150, -1);
        gtk_widget_show (dest_entry);
@@ -495,6 +513,31 @@ static void prefs_filtering_action_create(void)
                                 colorlabel_create_color_menu());
        gtk_box_pack_start(GTK_BOX(hbox1), color_optmenu, TRUE, TRUE, 0);
 
+       tags_combo = gtk_combo_new ();
+       gtk_widget_set_size_request (tags_combo, 150, -1);
+       gtk_widget_show (tags_combo);
+
+       combo_items = NULL;
+       for (tmp = tags = tags_get_list() ; tmp != NULL;
+            tmp = tmp->next) {
+               gchar *name = g_strdup(tags_get_tag(GPOINTER_TO_INT(tmp->data)));
+
+               combo_items = g_list_append(combo_items, (gpointer) name);
+       }
+
+       gtk_combo_set_popdown_strings(GTK_COMBO(tags_combo), combo_items);
+
+       for(cur = g_list_first(combo_items) ; cur != NULL ;
+           cur = g_list_next(cur))
+               g_free(cur->data);
+       g_list_free(combo_items);
+       g_slist_free(tags);
+
+       gtk_box_pack_start (GTK_BOX (hbox1), tags_combo,
+                           TRUE, TRUE, 0);
+       tags_list = GTK_COMBO(tags_combo)->list;
+       gtk_entry_set_editable(GTK_ENTRY(GTK_COMBO(tags_combo)->entry),
+                              FALSE);
        dest_btn = gtk_button_new_with_label (_("Select ..."));
        gtk_widget_show (dest_btn);
        gtk_box_pack_start (GTK_BOX (hbox1), dest_btn, FALSE, FALSE, 0);
@@ -605,6 +648,9 @@ static void prefs_filtering_action_create(void)
        filtering_action.account_label = account_label;
        filtering_action.account_list = account_list;
        filtering_action.account_combo = account_combo;
+       filtering_action.tags_label = tags_label;
+       filtering_action.tags_list = tags_list;
+       filtering_action.tags_combo = tags_combo;
        filtering_action.dest_entry = dest_entry;
        filtering_action.dest_btn = dest_btn;
        filtering_action.dest_label = dest_label;
@@ -773,6 +819,12 @@ static gint prefs_filtering_action_get_matching_from_action(Action action_id)
        switch (action_id) {
        case ACTION_MOVE:
                return MATCHACTION_MOVE;
+       case ACTION_SET_TAG:
+               return MATCHACTION_SET_TAG;
+       case ACTION_UNSET_TAG:
+               return MATCHACTION_UNSET_TAG;
+       case ACTION_CLEAR_TAGS:
+               return MATCHACTION_CLEAR_TAGS;
        case ACTION_COPY:
                return MATCHACTION_COPY;
        case ACTION_DELETE:
@@ -905,6 +957,16 @@ static FilteringAction * prefs_filtering_action_dialog_to_action(gboolean alert)
                        return NULL;
                }
                break;
+       case ACTION_SET_TAG:
+       case ACTION_UNSET_TAG:
+               destination = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(filtering_action.tags_combo)->entry), 0, -1);
+               if (*destination == '\0') {
+                       if (alert)
+                                alertpanel_error(_("Tag name is empty."));
+                       g_free(destination);
+                       return NULL;
+               }
+               break;
        case ACTION_STOP:
        case ACTION_HIDE:
        case ACTION_IGNORE:
@@ -917,6 +979,7 @@ static FilteringAction * prefs_filtering_action_dialog_to_action(gboolean alert)
         case ACTION_MARK_AS_UNREAD:
         case ACTION_MARK_AS_SPAM:
         case ACTION_MARK_AS_HAM:
+        case ACTION_CLEAR_TAGS:
        default:
                break;
        }
@@ -949,6 +1012,7 @@ static void prefs_filtering_action_register_cb(void)
         * list items to be unselectable)
         * prefs_filtering_action_reset_dialog(); */
        gtk_list_select_item(GTK_LIST(filtering_action.account_list), 0);
+       gtk_list_select_item(GTK_LIST(filtering_action.tags_list), 0);
        gtk_entry_set_text(GTK_ENTRY(filtering_action.dest_entry), "");
 }
 
@@ -1259,6 +1323,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1284,6 +1352,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1309,6 +1381,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, FALSE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1340,10 +1416,15 @@ static void prefs_filtering_action_type_select(GtkList *list,
         case ACTION_STOP:
         case ACTION_HIDE:
        case ACTION_IGNORE:
+       case ACTION_CLEAR_TAGS:
                gtk_widget_show(filtering_action.account_label);
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, FALSE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1369,6 +1450,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, TRUE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, TRUE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1394,6 +1479,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, TRUE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, TRUE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1419,6 +1508,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, TRUE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, TRUE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_show(filtering_action.dest_btn);
@@ -1444,6 +1537,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_hide(filtering_action.dest_btn);
@@ -1468,6 +1565,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_hide(filtering_action.dest_entry);
                gtk_widget_hide(filtering_action.dest_btn);
                gtk_widget_hide(filtering_action.dest_label);
@@ -1492,6 +1593,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_show(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_hide(filtering_action.dest_btn);
@@ -1516,6 +1621,10 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
                gtk_widget_hide(filtering_action.account_combo);
                gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_hide(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, FALSE);
+               gtk_widget_hide(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, FALSE);
                gtk_widget_show(filtering_action.dest_entry);
                gtk_widget_set_sensitive(filtering_action.dest_entry, TRUE);
                gtk_widget_hide(filtering_action.dest_btn);
@@ -1536,6 +1645,36 @@ static void prefs_filtering_action_type_select(GtkList *list,
                gtk_widget_show(filtering_action.addressbook_btn);
                gtk_widget_set_sensitive(filtering_action.addressbook_btn, TRUE);
                break;
+       case ACTION_SET_TAG:
+       case ACTION_UNSET_TAG:
+               gtk_widget_show(filtering_action.account_label);
+               gtk_widget_set_sensitive(filtering_action.account_label, FALSE);
+               gtk_widget_show(filtering_action.account_combo);
+               gtk_widget_set_sensitive(filtering_action.account_combo, FALSE);
+               gtk_widget_show(filtering_action.tags_label);
+               gtk_widget_set_sensitive(filtering_action.tags_label, TRUE);
+               gtk_widget_show(filtering_action.tags_combo);
+               gtk_widget_set_sensitive(filtering_action.tags_combo, TRUE);
+               gtk_widget_hide(filtering_action.dest_entry);
+               gtk_widget_set_sensitive(filtering_action.dest_entry, FALSE);
+               gtk_widget_hide(filtering_action.dest_btn);
+               gtk_widget_set_sensitive(filtering_action.dest_btn, FALSE);
+               gtk_widget_hide(filtering_action.dest_label);
+               gtk_widget_hide(filtering_action.recip_label);
+               gtk_widget_set_sensitive(filtering_action.recip_label, TRUE);
+               gtk_widget_hide(filtering_action.exec_label);
+               gtk_widget_hide(filtering_action.exec_btn);
+               gtk_widget_hide(filtering_action.color_optmenu);
+               gtk_widget_hide(filtering_action.color_label);
+               gtk_widget_hide(filtering_action.score_label);
+               gtk_widget_hide(filtering_action.header_label);
+               gtk_widget_hide(filtering_action.header_combo);
+               gtk_widget_hide(filtering_action.header_entry);
+               gtk_widget_set_sensitive(filtering_action.header_entry, FALSE);
+               gtk_widget_hide(filtering_action.addressbook_label);
+               gtk_widget_hide(filtering_action.addressbook_btn);
+               gtk_widget_set_sensitive(filtering_action.addressbook_btn, FALSE);
+               break;
        }
 }
 
index a2d6569..0b56021 100644 (file)
@@ -150,6 +150,9 @@ enum {
        CRITERIA_PARTIAL = 32,
 
        CRITERIA_FOUND_IN_ADDRESSBOOK = 33,
+       
+       CRITERIA_TAG = 34,
+       CRITERIA_TAGGED = 35
 };
 
 /*!
@@ -199,6 +202,8 @@ static struct_criteria_text criteria_text [] = {
        { N_("Size exactly"), FALSE },
        { N_("Partially downloaded"), FALSE },
        { N_("Found in addressbook"), FALSE },
+       { N_("Tags"), FALSE },
+       { N_("Tagged"), FALSE },
        { NULL, FALSE }
 };
 
@@ -1020,6 +1025,12 @@ static gint prefs_matcher_get_criteria_from_matching(gint matching_id)
        case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
        case MATCHCRITERIA_TO_OR_CC:
                return CRITERIA_TO_OR_CC;
+       case MATCHCRITERIA_NOT_TAG:
+       case MATCHCRITERIA_TAG:
+               return CRITERIA_TAG;
+       case MATCHCRITERIA_NOT_TAGGED:
+       case MATCHCRITERIA_TAGGED:
+               return CRITERIA_TAGGED;
        case MATCHCRITERIA_NOT_BODY_PART:
        case MATCHCRITERIA_BODY_PART:
                return CRITERIA_BODY_PART;
@@ -1104,6 +1115,10 @@ static gint prefs_matcher_get_matching_from_criteria(gint criteria_id)
                return MATCHCRITERIA_CC;
        case CRITERIA_TO_OR_CC:
                return MATCHCRITERIA_TO_OR_CC;
+       case CRITERIA_TAG:
+               return MATCHCRITERIA_TAG;
+       case CRITERIA_TAGGED:
+               return MATCHCRITERIA_TAGGED;
        case CRITERIA_NEWSGROUPS:
                return MATCHCRITERIA_NEWSGROUPS;
        case CRITERIA_INREPLYTO:
@@ -1186,6 +1201,10 @@ static gint prefs_matcher_not_criteria(gint matcher_criteria)
                return MATCHCRITERIA_NOT_CC;
        case MATCHCRITERIA_TO_OR_CC:
                return MATCHCRITERIA_NOT_TO_AND_NOT_CC;
+       case MATCHCRITERIA_TAG:
+               return MATCHCRITERIA_NOT_TAG;
+       case MATCHCRITERIA_TAGGED:
+               return MATCHCRITERIA_NOT_TAGGED;
        case MATCHCRITERIA_NEWSGROUPS:
                return MATCHCRITERIA_NOT_NEWSGROUPS;
        case MATCHCRITERIA_INREPLYTO:
@@ -1254,6 +1273,7 @@ static MatcherProp *prefs_matcher_dialog_to_matcher(void)
        case CRITERIA_COLORLABEL:
        case CRITERIA_IGNORE_THREAD:
        case CRITERIA_FOUND_IN_ADDRESSBOOK:
+       case CRITERIA_TAGGED:
                if (value_pred_flag == PREDICATE_FLAG_DISABLED)
                        criteria = prefs_matcher_not_criteria(criteria);
                break;
@@ -1262,6 +1282,7 @@ static MatcherProp *prefs_matcher_dialog_to_matcher(void)
        case CRITERIA_TO:
        case CRITERIA_CC:
        case CRITERIA_TO_OR_CC:
+       case CRITERIA_TAG:
        case CRITERIA_NEWSGROUPS:
        case CRITERIA_INREPLYTO:
        case CRITERIA_REFERENCES:
@@ -1305,6 +1326,7 @@ static MatcherProp *prefs_matcher_dialog_to_matcher(void)
        case CRITERIA_SPAM:
        case CRITERIA_PARTIAL:
        case CRITERIA_IGNORE_THREAD:
+       case CRITERIA_TAGGED:
                break;
 
        case CRITERIA_SUBJECT:
@@ -1312,6 +1334,7 @@ static MatcherProp *prefs_matcher_dialog_to_matcher(void)
        case CRITERIA_TO:
        case CRITERIA_CC:
        case CRITERIA_TO_OR_CC:
+       case CRITERIA_TAG:
        case CRITERIA_NEWSGROUPS:
        case CRITERIA_INREPLYTO:
        case CRITERIA_REFERENCES:
@@ -1655,6 +1678,7 @@ static void prefs_matcher_criteria_select(GtkList *list,
        case CRITERIA_SPAM:
        case CRITERIA_PARTIAL:
        case CRITERIA_IGNORE_THREAD:
+       case CRITERIA_TAGGED:
                prefs_matcher_disable_widget(matcher.header_combo);
                prefs_matcher_disable_widget(matcher.header_label);
                prefs_matcher_disable_widget(matcher.header_addr_combo);
@@ -1695,6 +1719,7 @@ static void prefs_matcher_criteria_select(GtkList *list,
        case CRITERIA_TO:
        case CRITERIA_CC:
        case CRITERIA_TO_OR_CC:
+       case CRITERIA_TAG:
        case CRITERIA_NEWSGROUPS:
        case CRITERIA_INREPLYTO:
        case CRITERIA_REFERENCES:
@@ -2087,6 +2112,8 @@ static gboolean prefs_matcher_selected(GtkTreeSelection *selector,
        case MATCHCRITERIA_NOT_TO:
        case MATCHCRITERIA_NOT_CC:
        case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
+       case MATCHCRITERIA_NOT_TAG:
+       case MATCHCRITERIA_NOT_TAGGED:
        case MATCHCRITERIA_NOT_NEWSGROUPS:
        case MATCHCRITERIA_NOT_INREPLYTO:
        case MATCHCRITERIA_NOT_REFERENCES:
@@ -2109,6 +2136,7 @@ static gboolean prefs_matcher_selected(GtkTreeSelection *selector,
        case MATCHCRITERIA_NOT_TO:
        case MATCHCRITERIA_NOT_CC:
        case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
+       case MATCHCRITERIA_NOT_TAG:
        case MATCHCRITERIA_NOT_NEWSGROUPS:
        case MATCHCRITERIA_NOT_INREPLYTO:
        case MATCHCRITERIA_NOT_REFERENCES:
@@ -2121,6 +2149,7 @@ static gboolean prefs_matcher_selected(GtkTreeSelection *selector,
        case MATCHCRITERIA_TO:
        case MATCHCRITERIA_CC:
        case MATCHCRITERIA_TO_OR_CC:
+       case MATCHCRITERIA_TAG:
        case MATCHCRITERIA_NEWSGROUPS:
        case MATCHCRITERIA_INREPLYTO:
        case MATCHCRITERIA_REFERENCES:
index c5d5a32..503e00f 100644 (file)
@@ -87,6 +87,7 @@ static const gchar *const col_name[N_SUMMARY_COLS] = {
        N_("Number"),           /* S_COL_NUMBER  */
         N_("Score"),           /* S_COL_SCORE   */
        N_("Locked"),           /* S_COL_LOCKED  */
+       N_("Tags"),             /* S_COL_TAGS  */
 };
 
 static SummaryColumnState default_state[N_SUMMARY_COLS] = {
@@ -101,6 +102,7 @@ static SummaryColumnState default_state[N_SUMMARY_COLS] = {
        { S_COL_NUMBER , FALSE },
         { S_COL_SCORE  , FALSE },
        { S_COL_LOCKED , FALSE },
+       { S_COL_TAGS   , FALSE },
 };
 
 static void prefs_summary_column_create        (void);
index 04b4b76..b8b7056 100644 (file)
@@ -45,6 +45,7 @@
 #include "mainwindow.h"
 #include "summaryview.h"
 #include "log.h"
+#include "tags.h"
 #include "timing.h"
 #include "inc.h"
 
@@ -1326,6 +1327,7 @@ void procmsg_msginfo_free(MsgInfo *msginfo)
        }
        slist_free_strings(msginfo->references);
        g_slist_free(msginfo->references);
+       g_slist_free(msginfo->tags);
 
        g_free(msginfo->plaintext_file);
 
@@ -1335,7 +1337,7 @@ void procmsg_msginfo_free(MsgInfo *msginfo)
 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
 {
        guint memusage = 0;
-       GSList *refs;
+       GSList *tmp;
        
        memusage += sizeof(MsgInfo);
        if (msginfo->fromname)
@@ -1356,13 +1358,17 @@ guint procmsg_msginfo_memusage(MsgInfo *msginfo)
                memusage += strlen(msginfo->msgid);
        if (msginfo->inreplyto)
                memusage += strlen(msginfo->inreplyto);
-       for (refs = msginfo->references; refs; refs=refs->next) {
-               gchar *r = (gchar *)refs->data;
-               memusage += r?strlen(r):0;
+
+       for (tmp = msginfo->references; tmp; tmp=tmp->next) {
+               gchar *r = (gchar *)tmp->data;
+               memusage += r?strlen(r):0 + sizeof(GSList);
        }
        if (msginfo->fromspace)
                memusage += strlen(msginfo->fromspace);
 
+       for (tmp = msginfo->tags; tmp; tmp=tmp->next) {
+               memusage += sizeof(GSList);
+       }
        if (msginfo->extradata) {
                memusage += sizeof(MsgInfoExtraData);
                if (msginfo->extradata->xface)
@@ -2356,3 +2362,53 @@ gboolean procmsg_have_trashed_mails_fast (void)
        folder_func_to_all_folders(item_has_trashed_mails, &result);
        return result;
 }
+
+gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
+{
+       GSList *cur = NULL;
+       gchar *tags = NULL;
+       
+       if (!msginfo)
+               return NULL;
+
+       if (msginfo->tags == NULL)
+               return NULL;
+       for (cur = msginfo->tags; cur; cur = cur->next) {
+               const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
+               if (!tag)
+                       continue;
+               if (!tags)
+                       tags = g_strdup(tag);
+               else {
+                       int olen = strlen(tags);
+                       int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
+                       tags = g_realloc(tags, nlen+1);
+                       if (!tags)
+                               return NULL;
+                       strcpy(tags+olen, ", ");
+                       strcpy(tags+olen+2, tag);
+                       tags[nlen]='\0';
+               }
+       }
+       return tags;
+}
+
+void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
+{
+       if (!set) {
+               msginfo->tags = g_slist_remove(
+                                       msginfo->tags,
+                                       GINT_TO_POINTER(id));
+       } else {
+               if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id)))
+                       msginfo->tags = g_slist_append(
+                                       msginfo->tags,
+                                       GINT_TO_POINTER(id));
+       }
+}
+
+void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
+{
+       g_slist_free(msginfo->tags);
+       msginfo->tags = NULL;
+}
index e461bb7..b042f27 100644 (file)
@@ -231,6 +231,8 @@ struct _MsgInfo
        gint total_size;
        gint planned_download;
 
+       GSList *tags;
+
        MsgInfoExtraData *extradata;
 };
 
@@ -369,5 +371,7 @@ int procmsg_spam_learner_learn      (MsgInfo *msginfo, GSList *msglist, gboolean spa
 gboolean procmsg_have_queued_mails_fast (void);
 gboolean procmsg_have_trashed_mails_fast (void);
 gboolean procmsg_is_sending(void);
-
+gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo);
+void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id);
+void procmsg_msginfo_clear_tags(MsgInfo *msginfo);
 #endif /* __PROCMSG_H__ */
index 81139a8..2d00b67 100644 (file)
@@ -62,6 +62,7 @@ static gchar *quote_desc_strings[] = {
        "%quoted_msg (%Q)",                     N_("quoted message body"), /* quoted message */
        "%msg_no_sig (%m)",                     N_("message body without signature"), /* message with no signature */
        "%quoted_msg_no_sig (%q)",      N_("quoted message body without signature"), /* quoted message with no signature */
+       "%tags",                                N_("message tags"), /* message tags */
        "%dict (%T)",                           N_("current dictionary"), /* current dictionary */
        "%cursor (%X)",                         N_("cursor position"), /* X marks the cursor spot */
        "%account_fullname (%af)",      N_("account property: your name"), /* full name in compose account */
index b84f162..964fcf2 100644 (file)
@@ -88,6 +88,7 @@ int line = -1;
 <S_NORMAL>("%s"|"%subject") /* subject */ return SHOW_SUBJECT;
 <S_NORMAL>("%t"|"%to") /* to */ return SHOW_TO;
 <S_NORMAL>("%T"|"%dict") /* current dictionary */ return SHOW_DICT;
+<S_NORMAL>("%tags") /* tags */ return SHOW_TAGS;
 <S_NORMAL>("%Q"|"%quoted_msg") /* quoted message */ return SHOW_QUOTED_MESSAGE;
 <S_NORMAL>("%q"|"%quoted_msg_no_sig") /* quoted message with no signature */ return SHOW_QUOTED_MESSAGE_NO_SIGNATURE;
 <S_NORMAL>("%af"|"%account_fullname") /* full name in compose account */ return SHOW_ACCOUNT_FULL_NAME;
index fa72cd9..d4a7e99 100644 (file)
@@ -558,7 +558,7 @@ static gchar *quote_fmt_complete_address(const gchar *addr)
 %token SHOW_EOL SHOW_QUESTION_MARK SHOW_EXCLAMATION_MARK SHOW_PIPE SHOW_OPARENT SHOW_CPARENT
 %token SHOW_ACCOUNT_FULL_NAME SHOW_ACCOUNT_MAIL_ADDRESS SHOW_ACCOUNT_NAME SHOW_ACCOUNT_ORGANIZATION
 %token SHOW_ACCOUNT_DICT
-%token SHOW_DICT
+%token SHOW_DICT SHOW_TAGS
 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_CC
 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_FROM
 %token SHOW_ADDRESSBOOK_COMPLETION_FOR_TO
@@ -776,6 +776,14 @@ special:
                INSERT(default_dictionary);
 #endif
        }
+       | SHOW_TAGS
+       {
+               gchar *tags = procmsg_msginfo_get_tags_str(msginfo);
+               if (tags) {
+                       INSERT(tags);
+               }
+               g_free(tags);
+       }
        | SHOW_BACKSLASH
        {
                INSERT("\\");
index cfabad9..3072f46 100644 (file)
 #include "folderutils.h"
 #include "quicksearch.h"
 #include "partial_download.h"
+#include "tags.h"
 #include "timing.h"
 #include "gedit-print.h"
 #include "log.h"
+#include "edittags.h"
 #include "manual.h"
 
 #define SUMMARY_COL_MARK_WIDTH         10
@@ -183,7 +185,7 @@ static void summary_status_show             (SummaryView            *summaryview);
 static void summary_set_column_titles  (SummaryView            *summaryview);
 static void summary_set_ctree_from_list        (SummaryView            *summaryview,
                                         GSList                 *mlist);
-static void summary_set_header         (SummaryView            *summaryview,
+static inline void summary_set_header  (SummaryView            *summaryview,
                                         gchar                  *text[],
                                         MsgInfo                *msginfo);
 static void summary_display_msg                (SummaryView            *summaryview,
@@ -195,6 +197,11 @@ static void summary_display_msg_full       (SummaryView            *summaryview,
 static void summary_set_row_marks      (SummaryView            *summaryview,
                                         GtkCTreeNode           *row);
 
+static gboolean summary_set_row_tag    (SummaryView            *summaryview, 
+                                        GtkCTreeNode           *row, 
+                                        gboolean                refresh,
+                                        gboolean                set, 
+                                        gint                    id);
 /* message handling */
 static void summary_mark_row           (SummaryView            *summaryview,
                                         GtkCTreeNode           *row);
@@ -250,6 +257,14 @@ static void summary_colorlabel_menu_item_activate_item_cb
                                           gpointer      data);
 static void summary_colorlabel_menu_create(SummaryView *summaryview,
                                           gboolean  refresh);
+static void summary_tags_menu_item_activate_cb
+                                         (GtkWidget    *widget,
+                                          gpointer      data);
+static void summary_tags_menu_item_activate_item_cb
+                                         (GtkMenuItem  *label_menu_item,
+                                          gpointer      data);
+static void summary_tags_menu_create(SummaryView       *summaryview,
+                                          gboolean  refresh);
 
 static GtkWidget *summary_ctree_create (SummaryView    *summaryview);
 
@@ -334,6 +349,8 @@ static void summary_score_clicked   (GtkWidget              *button,
                                         SummaryView            *summaryview);
 static void summary_locked_clicked     (GtkWidget              *button,
                                         SummaryView            *summaryview);
+static void summary_tags_clicked       (GtkWidget              *button,
+                                        SummaryView            *summaryview);
 
 static void summary_start_drag         (GtkWidget        *widget, 
                                         int button,
@@ -404,6 +421,9 @@ static gint summary_cmp_by_subject  (GtkCList               *clist,
 static gint summary_cmp_by_locked      (GtkCList               *clist,
                                         gconstpointer           ptr1, 
                                         gconstpointer           ptr2);
+static gint summary_cmp_by_tags                (GtkCList               *clist,
+                                        gconstpointer           ptr1, 
+                                        gconstpointer           ptr2);
 
 static void quicksearch_execute_cb     (QuickSearch    *quicksearch,
                                         gpointer        data);
@@ -457,6 +477,7 @@ static GtkItemFactoryEntry summary_popup_entries[] =
        {N_("/_Mark/Lock"),             NULL, summary_msgs_lock, 0, NULL},
        {N_("/_Mark/Unlock"),           NULL, summary_msgs_unlock, 0, NULL},
        {N_("/Color la_bel"),           NULL, NULL,             0, NULL},
+       {N_("/Ta_gs"),                  NULL, NULL,             0, NULL},
 
        {"/---",                        NULL, NULL,             0, "<Separator>"},
        {N_("/Add sender to address boo_k"),
@@ -502,6 +523,7 @@ static const gchar *const col_label[N_SUMMARY_COLS] = {
        N_("#"),        /* S_COL_NUMBER  */
        N_("Score"),    /* S_COL_SCORE   */
        "",             /* S_COL_LOCKED  */
+       N_("Tags"),     /* S_COL_TAGS    */
 };
 
 void summary_freeze(SummaryView *summaryview)
@@ -924,6 +946,7 @@ void summary_init(SummaryView *summaryview)
        summary_clear_list(summaryview);
        summary_set_column_titles(summaryview);
        summary_colorlabel_menu_create(summaryview, FALSE);
+       summary_tags_menu_create(summaryview, FALSE);
        main_create_mailing_list_menu (summaryview->mainwin, NULL);     
        summary_set_menu_sensitive(summaryview);
 
@@ -1552,6 +1575,7 @@ void summary_set_menu_sensitive(SummaryView *summaryview)
                {"/Mark/Mark as spam"           , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
                {"/Mark/Mark as ham"            , M_TARGET_EXIST|M_CAN_LEARN_SPAM},
                {"/Color label"                 , M_TARGET_EXIST},
+               {"/Tags"                        , M_TARGET_EXIST},
 
                {"/Add sender to address book"  , M_SINGLE_TARGET_EXIST},
                {"/Create filter rule"          , M_SINGLE_TARGET_EXIST|M_UNLOCKED},
@@ -2424,7 +2448,8 @@ static void summary_set_column_titles(SummaryView *summaryview)
                SORT_BY_SIZE,
                SORT_BY_NUMBER,
                SORT_BY_SCORE,
-               SORT_BY_LOCKED
+               SORT_BY_LOCKED,
+               SORT_BY_TAGS
        };
 
        for (pos = 0; pos < N_SUMMARY_COLS; pos++) {
@@ -2505,6 +2530,37 @@ static void summary_set_column_titles(SummaryView *summaryview)
        }
 }
 
+void summary_reflect_tags_changes(SummaryView *summaryview)
+{
+       GtkMenuShell *menu;
+       GList *cur;
+       GtkCTreeNode *node;
+       GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
+       gboolean froze = FALSE;
+       gboolean redisplay = FALSE;
+
+       /* re-create colorlabel submenu */
+       menu = GTK_MENU_SHELL(summaryview->tags_menu);
+       g_return_if_fail(menu != NULL);
+
+       /* clear items. get item pointers. */
+       for (cur = menu->children; cur != NULL && cur->data != NULL; cur = cur->next) {
+               gtk_menu_item_remove_submenu(GTK_MENU_ITEM(cur->data));
+       }
+       summary_tags_menu_create(summaryview, TRUE);
+
+       START_LONG_OPERATION(summaryview, TRUE);
+       for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list); node != NULL;
+            node = gtkut_ctree_node_next(ctree, node)) {
+               redisplay |= summary_set_row_tag(summaryview,
+                                          node, TRUE, FALSE, 0);
+       }
+       END_LONG_OPERATION(summaryview);
+       if (redisplay)
+               summary_redisplay_msg(summaryview);
+}
+
+
 void summary_reflect_prefs(void)
 {
        static gchar *last_font = NULL;
@@ -2583,6 +2639,9 @@ void summary_sort(SummaryView *summaryview,
        case SORT_BY_LOCKED:
                cmp_func = (GtkCListCompareFunc)summary_cmp_by_locked;
                break;
+       case SORT_BY_TAGS:
+               cmp_func = (GtkCListCompareFunc)summary_cmp_by_tags;
+               break;
        case SORT_BY_NONE:
                break;
        default:
@@ -2655,6 +2714,8 @@ static gboolean summary_insert_gnode_func(GtkCTree *ctree, guint depth, GNode *g
                SET_TEXT(S_COL_FROM);
        if (summaryview->col_state[summaryview->col_pos[S_COL_TO]].visible)
                SET_TEXT(S_COL_TO);
+       if (summaryview->col_state[summaryview->col_pos[S_COL_TAGS]].visible)
+               SET_TEXT(S_COL_TAGS);
 
 #undef SET_TEXT
 
@@ -2816,14 +2877,14 @@ static gchar *summary_complete_address(const gchar *addr)
        return res;
 }
 
-static void summary_set_header(SummaryView *summaryview, gchar *text[],
+static inline void summary_set_header(SummaryView *summaryview, gchar *text[],
                               MsgInfo *msginfo)
 {
        static gchar date_modified[80];
        static gchar col_score[11];
-       static gchar buf[BUFFSIZE];
+       static gchar buf[BUFFSIZE], tmp1[BUFFSIZE], tmp2[BUFFSIZE], tmp3[BUFFSIZE];
        gint *col_pos = summaryview->col_pos;
-       gchar *from_text = NULL, *to_text = NULL;
+       gchar *from_text = NULL, *to_text = NULL, *tags_text = NULL;
        gboolean should_swap = FALSE;
 
        text[col_pos[S_COL_FROM]]   = "";
@@ -2834,6 +2895,7 @@ static void summary_set_header(SummaryView *summaryview, gchar *text[],
        text[col_pos[S_COL_MIME]]   = "";
        text[col_pos[S_COL_LOCKED]] = "";
        text[col_pos[S_COL_DATE]]   = "";
+       text[col_pos[S_COL_TAGS]]   = "";
        if (summaryview->col_state[summaryview->col_pos[S_COL_NUMBER]].visible)
                text[col_pos[S_COL_NUMBER]] = itos(msginfo->msgnum);
        else
@@ -2850,6 +2912,19 @@ static void summary_set_header(SummaryView *summaryview, gchar *text[],
        else
                text[col_pos[S_COL_SCORE]] = "";
 
+       if (summaryview->col_state[summaryview->col_pos[S_COL_TAGS]].visible) {
+               tags_text = procmsg_msginfo_get_tags_str(msginfo);
+               if (!tags_text) {
+                       text[col_pos[S_COL_TAGS]] = "-";
+               } else {
+                       strncpy2(tmp1, tags_text, sizeof(tmp1));
+                       tmp1[sizeof(tmp1)-1]='\0';
+                       g_free(tags_text);
+                       text[col_pos[S_COL_TAGS]] = tmp1;
+               }
+       } else
+               text[col_pos[S_COL_TAGS]] = "";
+
        /* slow! */
        if (summaryview->col_state[summaryview->col_pos[S_COL_DATE]].visible) {
                if (msginfo->date_t) {
@@ -2883,7 +2958,6 @@ static void summary_set_header(SummaryView *summaryview, gchar *text[],
                                msginfo->fromname :
                                _("(No From)");
        } else {
-               gchar buf[BUFFSIZE];
                gchar *tmp = summary_complete_address(msginfo->from);
                if (tmp) {
                        strncpy2(buf, tmp, sizeof(buf));
@@ -2906,15 +2980,14 @@ static void summary_set_header(SummaryView *summaryview, gchar *text[],
        if (!should_swap) {
                text[col_pos[S_COL_FROM]] = from_text;
        } else {
-               gchar tmp[BUFFSIZE];
-               snprintf(tmp, BUFFSIZE-1, "--> %s", to_text);
-               tmp[BUFFSIZE-1]='\0';
-               text[col_pos[S_COL_FROM]] = tmp;
+               snprintf(tmp2, BUFFSIZE-1, "--> %s", to_text);
+               tmp2[BUFFSIZE-1]='\0';
+               text[col_pos[S_COL_FROM]] = tmp2;
        }
        
        if (summaryview->simplify_subject_preg != NULL)
                text[col_pos[S_COL_SUBJECT]] = msginfo->subject ? 
-                       string_remove_match(buf, BUFFSIZE, msginfo->subject, 
+                       string_remove_match(tmp3, BUFFSIZE, msginfo->subject, 
                                        summaryview->simplify_subject_preg) : 
                        _("(No Subject)");
        else 
@@ -5052,6 +5125,71 @@ void summary_set_colorlabel(SummaryView *summaryview, guint labelcolor,
        END_LONG_OPERATION(summaryview);
 }
 
+static gboolean summary_set_row_tag(SummaryView *summaryview, GtkCTreeNode *row, gboolean refresh, gboolean set, gint id)
+{
+       GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
+       MsgInfo *msginfo;
+       gchar *tags_str = NULL;
+       msginfo = gtk_ctree_node_get_row_data(ctree, row);
+       g_return_val_if_fail(msginfo, FALSE);
+
+       procmsg_msginfo_update_tags(msginfo, set, id);
+       
+       
+       if (summaryview->col_state[summaryview->col_pos[S_COL_TAGS]].visible) {
+               tags_str = procmsg_msginfo_get_tags_str(msginfo);
+               gtk_ctree_node_set_text(ctree, row, 
+                               summaryview->col_pos[S_COL_TAGS],
+                               tags_str?tags_str:"-");
+               g_free(tags_str);
+       }
+
+       summary_set_row_marks(summaryview, row);
+       if (row == summaryview->displayed) {
+               return TRUE;
+       }
+       return FALSE;
+}
+
+void summary_set_tag(SummaryView *summaryview, gint tag_id,
+                           GtkWidget *widget)
+{
+       GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
+       GList *cur;
+       gboolean set = tag_id > 0;
+       gint real_id = set? tag_id:-tag_id;
+       gboolean froze = FALSE;
+       gboolean redisplay = FALSE;
+       START_LONG_OPERATION(summaryview, FALSE);
+       for (cur = GTK_CLIST(ctree)->selection; cur != NULL && cur->data != NULL; cur = cur->next) {
+               redisplay |= summary_set_row_tag(summaryview,
+                                          GTK_CTREE_NODE(cur->data), FALSE, set, real_id);
+       }
+       END_LONG_OPERATION(summaryview);
+       if (redisplay)
+               summary_redisplay_msg(summaryview);
+}
+
+static void summary_tags_menu_item_activate_cb(GtkWidget *widget,
+                                                    gpointer data)
+{
+       gint id = GPOINTER_TO_INT(data);
+       gboolean set = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
+       SummaryView *summaryview;
+
+       summaryview = g_object_get_data(G_OBJECT(widget), "summaryview");
+       g_return_if_fail(summaryview != NULL);
+
+       /* "dont_toggle" state set? */
+       if (g_object_get_data(G_OBJECT(summaryview->tags_menu),
+                               "dont_toggle"))
+               return;
+
+       if (!set)
+               id = -id;
+       summary_set_tag(summaryview, id, NULL);
+}
+
 static void summary_colorlabel_menu_item_activate_item_cb(GtkMenuItem *menu_item,
                                                          gpointer data)
 {
@@ -5169,6 +5307,151 @@ static void summary_colorlabel_menu_create(SummaryView *summaryview, gboolean re
        summaryview->colorlabel_menu = menu;
 }
 
+static void summary_tags_menu_item_activate_item_cb(GtkMenuItem *menu_item,
+                                                         gpointer data)
+{
+       GtkMenuShell *menu;
+       GList *cur;
+       GList *sel;
+       GHashTable *menu_table = g_hash_table_new_full(
+                                       g_direct_hash,
+                                       g_direct_equal,
+                                       NULL, NULL);
+       SummaryView *summaryview = (SummaryView *)data;
+       g_return_if_fail(summaryview != NULL);
+
+       sel = GTK_CLIST(summaryview->ctree)->selection;
+       if (!sel) return;
+
+       menu = GTK_MENU_SHELL(summaryview->tags_menu);
+       g_return_if_fail(menu != NULL);
+
+       /* NOTE: don't return prematurely because we set the "dont_toggle"
+        * state for check menu items */
+       g_object_set_data(G_OBJECT(menu), "dont_toggle",
+                         GINT_TO_POINTER(1));
+
+       /* clear items. get item pointers. */
+       for (cur = menu->children; cur != NULL && cur->data != NULL; cur = cur->next) {
+               if (GTK_IS_CHECK_MENU_ITEM(cur->data)) {
+                       gint id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cur->data),
+                               "tag_id"));
+                       gtk_check_menu_item_set_active
+                               (GTK_CHECK_MENU_ITEM(cur->data), FALSE);
+                               
+                       g_hash_table_insert(menu_table, GINT_TO_POINTER(id), GTK_CHECK_MENU_ITEM(cur->data));
+               }
+       }
+
+       /* iterate all messages and set the state of the appropriate
+        * items */
+       for (; sel != NULL; sel = sel->next) {
+               MsgInfo *msginfo;
+               GSList *tags = NULL;
+               gint id;
+               GtkCheckMenuItem *item;
+               msginfo = gtk_ctree_node_get_row_data
+                       (GTK_CTREE(summaryview->ctree),
+                        GTK_CTREE_NODE(sel->data));
+               if (msginfo) {
+                       tags =  msginfo->tags;
+                       if (!tags)
+                               continue;
+
+                       for (; tags; tags = tags->next) {
+                               id = GPOINTER_TO_INT(tags->data);
+                               item = g_hash_table_lookup(menu_table, GINT_TO_POINTER(tags->data));
+                               if (item && !item->active)
+                                       gtk_check_menu_item_set_active
+                                               (item, TRUE);
+                       }
+               }
+       }
+
+       g_hash_table_destroy(menu_table);
+       /* reset "dont_toggle" state */
+       g_object_set_data(G_OBJECT(menu), "dont_toggle",
+                         GINT_TO_POINTER(0));
+
+}
+
+static void summary_tags_menu_item_new_tag_activate_cb(GtkWidget *widget,
+                                                    gpointer data)
+{
+       SummaryView *summaryview;
+       gint id;
+
+       summaryview = g_object_get_data(G_OBJECT(widget), "summaryview");
+       g_return_if_fail(summaryview != NULL);
+
+       /* "dont_toggle" state set? */
+       if (g_object_get_data(G_OBJECT(summaryview->tags_menu),
+                               "dont_toggle"))
+               return;
+
+       id = prefs_tags_create_new(summaryview->mainwin);
+       if (id != -1) {
+               summary_set_tag(summaryview, id, NULL);
+               main_window_reflect_tags_changes(mainwindow_get_mainwindow());
+       }
+}
+
+static void summary_tags_menu_create(SummaryView *summaryview, gboolean refresh)
+{
+       GtkWidget *label_menuitem;
+       GtkWidget *menu;
+       GtkWidget *item;
+       GSList *cur = tags_get_list();
+       GSList *orig = cur;
+       gboolean existing_tags = FALSE;
+
+       label_menuitem = gtk_item_factory_get_item(summaryview->popupfactory,
+                                                  "/Tags");
+       g_signal_connect(G_OBJECT(label_menuitem), "activate",
+                        G_CALLBACK(summary_tags_menu_item_activate_item_cb),
+                          summaryview);
+
+       gtk_widget_show(label_menuitem);
+
+       menu = gtk_menu_new();
+
+       /* create tags menu items */
+       for (; cur; cur = cur->next) {
+               gint id = GPOINTER_TO_INT(cur->data);
+               const gchar *tag = tags_get_tag(id);
+
+               item = gtk_check_menu_item_new_with_label(tag);
+               gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+               g_signal_connect(G_OBJECT(item), "activate",
+                                G_CALLBACK(summary_tags_menu_item_activate_cb),
+                                GINT_TO_POINTER(id));
+               g_object_set_data(G_OBJECT(item), "summaryview",
+                                 summaryview);
+               g_object_set_data(G_OBJECT(item), "tag_id",
+                                 GINT_TO_POINTER(id));
+               gtk_widget_show(item);
+               existing_tags = TRUE;
+       }
+       if (existing_tags) {
+               /* separator */
+               item = gtk_menu_item_new();
+               gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+               gtk_widget_show(item);
+       }
+       item = gtk_menu_item_new_with_label(_("New tag..."));
+       gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+       g_signal_connect(G_OBJECT(item), "activate",
+                        G_CALLBACK(summary_tags_menu_item_new_tag_activate_cb),
+                        NULL);
+       g_object_set_data(G_OBJECT(item), "summaryview",
+                         summaryview);
+       gtk_widget_show(item);
+       g_slist_free(orig);
+       gtk_widget_show(menu);
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM(label_menuitem), menu);
+       summaryview->tags_menu = menu;
+}
+
 static gboolean summary_popup_menu(GtkWidget *widget, gpointer data)
 {
        SummaryView *summaryview = (SummaryView *)data;
@@ -5239,6 +5522,8 @@ static GtkWidget *summary_ctree_create(SummaryView *summaryview)
                                   prefs_common.summary_col_size[S_COL_NUMBER]);
        gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_SCORE],
                                   prefs_common.summary_col_size[S_COL_SCORE]);
+       gtk_clist_set_column_width(GTK_CLIST(ctree), col_pos[S_COL_TAGS],
+                                  prefs_common.summary_col_size[S_COL_TAGS]);
 
        if (prefs_common.enable_dotted_lines) {
                gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_DOTTED);
@@ -5281,6 +5566,7 @@ static GtkWidget *summary_ctree_create(SummaryView *summaryview)
        CLIST_BUTTON_SIGNAL_CONNECT(S_COL_SUBJECT, summary_subject_clicked);
        CLIST_BUTTON_SIGNAL_CONNECT(S_COL_SCORE,   summary_score_clicked);
        CLIST_BUTTON_SIGNAL_CONNECT(S_COL_LOCKED,  summary_locked_clicked);
+       CLIST_BUTTON_SIGNAL_CONNECT(S_COL_TAGS,    summary_tags_clicked);
 
 #undef CLIST_BUTTON_SIGNAL_CONNECT
 
@@ -5928,6 +6214,12 @@ static void summary_locked_clicked(GtkWidget *button,
        summary_sort_by_column_click(summaryview, SORT_BY_LOCKED);
 }
 
+static void summary_tags_clicked(GtkWidget *button,
+                                  SummaryView *summaryview)
+{
+       summary_sort_by_column_click(summaryview, SORT_BY_TAGS);
+}
+
 static void summary_start_drag(GtkWidget *widget, gint button, GdkEvent *event,
                               SummaryView *summaryview)
 {
@@ -6171,6 +6463,28 @@ static gint summary_cmp_by_to(GtkCList *clist, gconstpointer ptr1,
        return g_utf8_collate(str1, str2);
 }
  
+static gint summary_cmp_by_tags(GtkCList *clist, gconstpointer ptr1,
+                               gconstpointer ptr2)
+{
+       const gchar *str1, *str2;
+       const GtkCListRow *r1 = (const GtkCListRow *) ptr1;
+       const GtkCListRow *r2 = (const GtkCListRow *) ptr2;
+       const SummaryView *sv = g_object_get_data(G_OBJECT(clist), "summaryview");
+       
+       g_return_val_if_fail(sv, -1);
+       
+       str1 = GTK_CELL_TEXT(r1->cell[sv->col_pos[S_COL_TAGS]])->text;
+       str2 = GTK_CELL_TEXT(r2->cell[sv->col_pos[S_COL_TAGS]])->text;
+
+       if (!str1)
+               return str2 != NULL;
+       if (!str2)
+               return -1;
+       return g_utf8_collate(str1, str2);
+}
 static gint summary_cmp_by_simplified_subject
        (GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2)
 {
index d403123..1dd3709 100644 (file)
@@ -47,10 +47,11 @@ typedef enum
        S_COL_SIZE,
        S_COL_NUMBER,
        S_COL_SCORE,
-       S_COL_LOCKED
+       S_COL_LOCKED,
+       S_COL_TAGS
 } SummaryColumnType;
 
-#define N_SUMMARY_COLS 11
+#define N_SUMMARY_COLS 12
 
 typedef enum
 {
@@ -109,6 +110,7 @@ struct _SummaryView
        GtkWidget *quick_search_pixmap;
        GtkWidget *popupmenu;
        GtkWidget *colorlabel_menu;
+       GtkWidget *tags_menu;
 
        GtkItemFactory *popupfactory;
 
@@ -290,6 +292,9 @@ void summary_select_thread    (SummaryView          *summaryview,
 void summary_set_colorlabel      (SummaryView          *summaryview,
                                   guint                 labelcolor,
                                   GtkWidget            *widget);
+void summary_set_tag             (SummaryView          *summaryview,
+                                  gint                  tag_id,
+                                  GtkWidget            *widget);
 void summary_set_colorlabel_color (GtkCTree            *ctree,
                                   GtkCTreeNode         *node,
                                   guint                 labelcolor);
@@ -304,6 +309,7 @@ void summary_toggle_view_real         (SummaryView  *summaryview);
 void summary_reflect_prefs_pixmap_theme
                                   (SummaryView *summaryview);
 void summary_reflect_prefs_custom_colors(SummaryView *summaryview);
+void summary_reflect_tags_changes(SummaryView *summaryview);
 void summary_harvest_address      (SummaryView *summaryview);
 void summary_set_prefs_from_folderitem
                                   (SummaryView *summaryview, FolderItem *item);
index 70ba036..6b87997 100644 (file)
@@ -68,6 +68,7 @@
 #include "base64.h"
 #include "inputdialog.h"
 #include "timing.h"
+#include "tags.h"
 
 static GdkColor quote_colors[3] = {
        {(gulong)0, (gushort)0, (gushort)0, (gushort)0},
@@ -358,8 +359,19 @@ static void textview_create_tags(GtkTextView *text, TextView *textview)
 {
        GtkTextBuffer *buffer;
        GtkTextTag *tag, *qtag;
+       static GdkColor yellow, black;
+       static gboolean color_init = FALSE;
        static PangoFontDescription *font_desc, *bold_font_desc;
        
+       if (!color_init) {
+               gdk_color_parse("#f5f6be", &yellow);
+               gdk_color_parse("#000000", &black);
+               color_init = gdk_colormap_alloc_color(
+                       gdk_colormap_get_system(), &yellow, FALSE, TRUE);
+               color_init &= gdk_colormap_alloc_color(
+                       gdk_colormap_get_system(), &black, FALSE, TRUE);
+       }
+
        if (!font_desc)
                font_desc = pango_font_description_from_string
                        (NORMAL_FONT);
@@ -421,6 +433,16 @@ static void textview_create_tags(GtkTextView *text, TextView *textview)
                                "foreground-gdk", &quote_colors[2],
                                NULL);
        }
+#if GTK_CHECK_VERSION(2, 8, 0)
+       gtk_text_buffer_create_tag(buffer, "tags",
+                       "foreground-gdk", &black,
+                       "paragraph-background-gdk", &yellow,
+                       NULL);
+#else
+       gtk_text_buffer_create_tag(buffer, "tags",
+                       "foreground-gdk", &emphasis_color,
+                       NULL);
+#endif
        gtk_text_buffer_create_tag(buffer, "emphasis",
                        "foreground-gdk", &emphasis_color,
                        NULL);
@@ -1856,6 +1878,57 @@ bail:
 }
 #endif
 
+static void textview_show_tags(TextView *textview)
+{
+       MsgInfo *msginfo = textview->messageview->msginfo;
+       GtkTextView *text = GTK_TEXT_VIEW(textview->text);
+       GtkTextBuffer *buffer = gtk_text_view_get_buffer(text);
+       GtkTextIter iter;
+       ClickableText *uri;
+       GSList *cur;
+       gboolean found_tag = FALSE;
+       
+       if (!msginfo->tags)
+               return;
+       
+       for (cur = msginfo->tags; cur; cur = cur->next) {
+               if (tags_get_tag(GPOINTER_TO_INT(cur->data)) != NULL) {
+                       found_tag = TRUE;
+                       break;
+               }
+       }
+       if (!found_tag) 
+               return;
+
+       gtk_text_buffer_get_end_iter (buffer, &iter);
+       gtk_text_buffer_insert_with_tags_by_name(buffer,
+               &iter, _("Tags: "), -1,
+               "header_title", "header", "tags", NULL);
+
+       for (cur = msginfo->tags; cur; cur = cur->next) {
+               uri = g_new0(ClickableText, 1);
+               uri->uri = g_strdup("");
+               uri->start = gtk_text_iter_get_offset(&iter);
+               gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, 
+                       tags_get_tag(GPOINTER_TO_INT(cur->data)), -1,
+                       "link", "header", "tags", NULL);
+               uri->end = gtk_text_iter_get_offset(&iter);
+               uri->filename = g_strdup_printf("sc://search_tags:%s", tags_get_tag(GPOINTER_TO_INT(cur->data)));
+               uri->data = NULL;
+               textview->uri_list =
+                       g_slist_prepend(textview->uri_list, uri);
+               if (cur->next)
+                       gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, ", ", 2,
+                               "header", "tags", NULL);
+               else
+                       gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, " ", 1,
+                               "header", "tags", NULL);
+       }
+
+       gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, "\n", 1,
+               "header", "tags", NULL);
+}
+
 static void textview_show_header(TextView *textview, GPtrArray *headers)
 {
        GtkTextView *text = GTK_TEXT_VIEW(textview->text);
@@ -1866,6 +1939,8 @@ static void textview_show_header(TextView *textview, GPtrArray *headers)
 
        g_return_if_fail(headers != NULL);
 
+       textview_show_tags(textview);
+
        for (i = 0; i < headers->len; i++) {
                header = g_ptr_array_index(headers, i);
                g_return_if_fail(header->name != NULL);