+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
( 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
MICRO_VERSION=0
INTERFACE_AGE=0
BINARY_AGE=0
-EXTRA_VERSION=0
+EXTRA_VERSION=1
EXTRA_RELEASE=
EXTRA_GTK2_VERSION=
editjpilot.c \
editldap_basedn.c \
editldap.c \
+ edittags.c \
editvcard.c \
enriched.c \
exphtmldlg.c \
editjpilot.h \
editldap_basedn.h \
editldap.h \
+ edittags.h \
editvcard.h \
enriched.h \
exphtmldlg.h \
string_match.c \
stringtable.c \
claws.c \
+ tags.c \
template.c \
utils.c \
uuencode.c \
string_match.h \
stringtable.h \
claws.h \
+ tags.h \
template.h \
timing.h \
utils.h \
#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"
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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
}
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);
}
}
}
--- /dev/null
+/*
+ * 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;
+
+}
--- /dev/null
+/*
+ * 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
#include "prefs_common.h"
#include "addrbook.h"
#include "addr_compl.h"
+#include "tags.h"
#include "log.h"
#define PREFSBUFSIZE 1024
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;
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);
case MATCHACTION_STOP:
case MATCHACTION_HIDE:
case MATCHACTION_IGNORE:
+ case MATCHACTION_CLEAR_TAGS:
g_snprintf(dest, destlen, "%s", command_str);
return dest;
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);
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;
} 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();
}
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;
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 */
g_free(cache_file);
g_free(mark_file);
+ g_free(tags_file);
}
MsgInfo *folder_item_get_msginfo(FolderItem *item, gint num)
~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)
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;
SORT_BY_STATUS,
SORT_BY_MIME,
SORT_BY_TO,
- SORT_BY_LOCKED
+ SORT_BY_LOCKED,
+ SORT_BY_TAGS
} FolderSortKey;
typedef enum
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)
{
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);
"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"),
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);
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;
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))
{ "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 },
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;
QUICK_SEARCH_FROM,
QUICK_SEARCH_TO,
QUICK_SEARCH_EXTENDED,
- QUICK_SEARCH_MIXED
+ QUICK_SEARCH_MIXED,
+ QUICK_SEARCH_TAG
} QuickSearchType;
typedef struct _QuickSearch QuickSearch;
GtkWidget *vbox;
GtkWidget *hbox1;
GtkWidget *hbox2;
+ GtkWidget *hbox3;
GtkWidget *from_header_label;
GtkWidget *from_body_label;
GtkWidget *to_header_label;
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);
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("");
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);
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;
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);
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);
}
}
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),
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;
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);
GtkWidget *ng_body_label;
GtkWidget *subject_header_label;
GtkWidget *subject_body_label;
+ GtkWidget *tags_header_label;
+ GtkWidget *tags_body_label;
GtkWidget *image;
};
#include "imap_gtk.h"
#include "news_gtk.h"
#include "matcher.h"
+#include "tags.h"
#ifdef HAVE_LIBETPAN
#include "imap-thread.h"
#endif
prefs_logging_init();
prefs_receive_init();
prefs_send_init();
+ tags_read_tags();
#ifdef USE_ASPELL
gtkaspell_checkers_init();
prefs_spelling_init();
prefs_other_done();
prefs_receive_done();
prefs_send_done();
+ tags_write_tags();
#ifdef USE_ASPELL
prefs_spelling_done();
gtkaspell_checkers_quit();
#include "procmsg.h"
#include "import.h"
#include "export.h"
+#include "edittags.h"
#include "prefs_common.h"
#include "prefs_actions.h"
#include "prefs_filtering.h"
#include "foldersort.h"
#include "icon_legend.h"
#include "colorlabel.h"
+#include "tags.h"
#include "textview.h"
#include "imap.h"
#include "socket.h"
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);
{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"},
{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},
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>"},
} 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));
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;
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)
{
online_switch_clicked (GTK_BUTTON(online_switch), mainwin);
mainwindow_colorlabel_menu_create(mainwin, FALSE);
+ mainwindow_tags_menu_create(mainwin, FALSE);
return 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) {
SummarySelection selection;
FolderItem *item = mainwin->summaryview->folder_item;
GList *account_list = account_get_list();
+ GSList *tmp;
selection = summary_get_selection_type(mainwin->summaryview);
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;
{"/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},
{
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)
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
GtkWidget *colorlabel_menu;
GtkWidget *warning_btn;
+ GtkWidget *tags_menu;
#ifdef HAVE_LIBSM
gpointer smc_conn;
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);
#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);
#include <ctype.h>
#include "prefs_common.h"
#include "log.h"
+#include "tags.h"
/*!
*\brief Keyword lookup element
{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"},
{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 {
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
*
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;
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:
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:
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),
MA_(HIDE),
MA_(IGNORE),
MA_(ADD_TO_ADDRESSBOOK),
+ MA_(SET_TAG),
+ MA_(UNSET_TAG),
+ MA_(CLEAR_TAGS),
/* boolean operations */
MB_(OR),
MB_(AND)
%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
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;
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;
} 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);
}
}
#include "procmsg.h"
#include "codeconv.h"
#include "timing.h"
+#include "tags.h"
#ifdef HAVE_FWRITE_UNLOCKED
#define SC_FWRITE fwrite_unlocked
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;
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;
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)
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)
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) {
g_unlink(new_cache);
g_free(new_cache);
g_free(new_mark);
+ g_free(new_tags);
return -1;
}
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;
}
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) {
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;
+ }
}
}
}
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;
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);
&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},
&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},
&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,
{"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,
gint templateswin_height;
gint actionswin_width;
gint actionswin_height;
+ gint tagswin_width;
+ gint tagswin_height;
gint addressbookwin_width;
gint addressbookwin_height;
gint addressbookeditpersonwin_width;
#include "folder.h"
#include "description_window.h"
#include "addr_compl.h"
-
+#include "tags.h"
#include "matcher_parser.h"
#include "colorlabel.h"
GtkWidget *header_entry;
GtkWidget *addressbook_label;
GtkWidget *addressbook_btn;
+ GtkWidget *tags_label;
+ GtkWidget *tags_list;
+ GtkWidget *tags_combo;
gint current_action;
} filtering_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,
{ 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 },
GtkWidget *addressbook_btn;
GtkWidget *dest_entry;
GtkWidget *dest_btn;
+ GtkWidget *tags_label;
+ GtkWidget *tags_list;
+ GtkWidget *tags_combo;
GList * cur;
GtkWidget *reg_hbox;
static GdkGeometry geometry;
GList * accounts;
+ GSList *tmp, *tags;
debug_print("Creating matcher configuration window...\n");
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);
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);
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;
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:
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:
case ACTION_MARK_AS_UNREAD:
case ACTION_MARK_AS_SPAM:
case ACTION_MARK_AS_HAM:
+ case ACTION_CLEAR_TAGS:
default:
break;
}
* 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), "");
}
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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;
}
}
CRITERIA_PARTIAL = 32,
CRITERIA_FOUND_IN_ADDRESSBOOK = 33,
+
+ CRITERIA_TAG = 34,
+ CRITERIA_TAGGED = 35
};
/*!
{ N_("Size exactly"), FALSE },
{ N_("Partially downloaded"), FALSE },
{ N_("Found in addressbook"), FALSE },
+ { N_("Tags"), FALSE },
+ { N_("Tagged"), FALSE },
{ NULL, FALSE }
};
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;
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:
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:
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;
case CRITERIA_TO:
case CRITERIA_CC:
case CRITERIA_TO_OR_CC:
+ case CRITERIA_TAG:
case CRITERIA_NEWSGROUPS:
case CRITERIA_INREPLYTO:
case CRITERIA_REFERENCES:
case CRITERIA_SPAM:
case CRITERIA_PARTIAL:
case CRITERIA_IGNORE_THREAD:
+ case CRITERIA_TAGGED:
break;
case CRITERIA_SUBJECT:
case CRITERIA_TO:
case CRITERIA_CC:
case CRITERIA_TO_OR_CC:
+ case CRITERIA_TAG:
case CRITERIA_NEWSGROUPS:
case CRITERIA_INREPLYTO:
case CRITERIA_REFERENCES:
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);
case CRITERIA_TO:
case CRITERIA_CC:
case CRITERIA_TO_OR_CC:
+ case CRITERIA_TAG:
case CRITERIA_NEWSGROUPS:
case CRITERIA_INREPLYTO:
case CRITERIA_REFERENCES:
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:
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:
case MATCHCRITERIA_TO:
case MATCHCRITERIA_CC:
case MATCHCRITERIA_TO_OR_CC:
+ case MATCHCRITERIA_TAG:
case MATCHCRITERIA_NEWSGROUPS:
case MATCHCRITERIA_INREPLYTO:
case MATCHCRITERIA_REFERENCES:
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] = {
{ S_COL_NUMBER , FALSE },
{ S_COL_SCORE , FALSE },
{ S_COL_LOCKED , FALSE },
+ { S_COL_TAGS , FALSE },
};
static void prefs_summary_column_create (void);
#include "mainwindow.h"
#include "summaryview.h"
#include "log.h"
+#include "tags.h"
#include "timing.h"
#include "inc.h"
}
slist_free_strings(msginfo->references);
g_slist_free(msginfo->references);
+ g_slist_free(msginfo->tags);
g_free(msginfo->plaintext_file);
guint procmsg_msginfo_memusage(MsgInfo *msginfo)
{
guint memusage = 0;
- GSList *refs;
+ GSList *tmp;
memusage += sizeof(MsgInfo);
if (msginfo->fromname)
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)
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;
+}
gint total_size;
gint planned_download;
+ GSList *tags;
+
MsgInfoExtraData *extradata;
};
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__ */
"%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 */
<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;
%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
INSERT(default_dictionary);
#endif
}
+ | SHOW_TAGS
+ {
+ gchar *tags = procmsg_msginfo_get_tags_str(msginfo);
+ if (tags) {
+ INSERT(tags);
+ }
+ g_free(tags);
+ }
| SHOW_BACKSLASH
{
INSERT("\\");
#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
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,
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);
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);
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,
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);
{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"),
N_("#"), /* S_COL_NUMBER */
N_("Score"), /* S_COL_SCORE */
"", /* S_COL_LOCKED */
+ N_("Tags"), /* S_COL_TAGS */
};
void summary_freeze(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);
{"/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},
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++) {
}
}
+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;
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:
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
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]] = "";
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
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) {
msginfo->fromname :
_("(No From)");
} else {
- gchar buf[BUFFSIZE];
gchar *tmp = summary_complete_address(msginfo->from);
if (tmp) {
strncpy2(buf, tmp, sizeof(buf));
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
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)
{
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;
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);
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
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)
{
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)
{
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
{
GtkWidget *quick_search_pixmap;
GtkWidget *popupmenu;
GtkWidget *colorlabel_menu;
+ GtkWidget *tags_menu;
GtkItemFactory *popupfactory;
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);
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);
#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},
{
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);
"foreground-gdk", "e_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);
}
#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);
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);