/* * Copyright (C) 2000 Helix Code Inc. * * Authors: Not Zed * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter-datespec.h" #include "e-util/e-sexp.h" #define d(x) static gboolean validate (FilterElement *fe); static void xml_create (FilterElement *fe, xmlNodePtr node); static xmlNodePtr xml_encode (FilterElement *fe); static int xml_decode (FilterElement *fe, xmlNodePtr node); static GtkWidget *get_widget (FilterElement *fe); static void build_code (FilterElement *fe, GString *out, struct _FilterPart *fds); static void format_sexp (FilterElement *, GString *); static void filter_datespec_class_init (FilterDatespecClass *class); static void filter_datespec_init (FilterDatespec *gspaper); static void filter_datespec_finalise (GtkObject *obj); static void make_span_editor (FilterDatespec *fds); static void adj_value_changed (GtkAdjustment *adj, gpointer user_data); static gchar *describe_button (FilterDatespec *fds); static gchar *stringify_agoness (FilterDatespec *fds); static void set_adjustments (FilterDatespec *fds); static void cal_day_selected (GtkCalendar *cal, gpointer user_data); static void cal_day_selected_double_click (GtkCalendar *cal, gpointer user_data); #define PRIV(x) (((FilterDatespec *)(x))->priv) typedef struct _timespan { guint32 seconds; const gchar *singular; const gchar *plural; gfloat max; } timespan; static const timespan timespans[] = { { 31557600, N_("year"), N_("years"), 1000.0 }, { 2419200, N_("month"), N_("months"), 12.0 }, { 604800, N_("week"), N_("weeks"), 52.0 }, { 86400, N_("day"), N_("days"), 31.0 }, { 3600, N_("hour"), N_("hours"), 23.0 }, { 60, N_("minute"), N_("minutes"), 59.0 }, { 1, N_("second"), N_("seconds"), 59.0 } }; #define N_TIMECHUNKS 3 static const guint timechunks[N_TIMECHUNKS] = { 2, 2, 3 }; #define MAX_CHUNK 3 #define N_TIMESPANS (sizeof (timespans) / sizeof (timespans[0])) struct _FilterDatespecPrivate { GnomeDialog *gd; GtkWidget *descriptive_label; GtkWidget *cur_extra_widget; FilterDatespec_type selected_type; GtkWidget *date_chooser; GtkWidget *span_chooser; gboolean double_click; GtkWidget **spinbuttons; }; static FilterElementClass *parent_class; guint filter_datespec_get_type (void) { static guint type = 0; if (!type) { GtkTypeInfo type_info = { "FilterDatespec", sizeof (FilterDatespec), sizeof (FilterDatespecClass), (GtkClassInitFunc) filter_datespec_class_init, (GtkObjectInitFunc) filter_datespec_init, (GtkArgSetFunc) NULL, (GtkArgGetFunc) NULL }; type = gtk_type_unique (filter_element_get_type (), &type_info); } return type; } static void filter_datespec_class_init (FilterDatespecClass *class) { GtkObjectClass *object_class; FilterElementClass *filter_element = (FilterElementClass *)class; object_class = (GtkObjectClass *)class; parent_class = gtk_type_class (filter_element_get_type ()); object_class->finalize = filter_datespec_finalise; /* override methods */ filter_element->validate = validate; filter_element->xml_create = xml_create; filter_element->xml_encode = xml_encode; filter_element->xml_decode = xml_decode; filter_element->get_widget = get_widget; filter_element->build_code = build_code; filter_element->format_sexp = format_sexp; } static void filter_datespec_init (FilterDatespec *o) { o->priv = g_malloc0 (sizeof (*o->priv)); o->type = FDST_UNKNOWN; PRIV(o)->selected_type = FDST_UNKNOWN; PRIV(o)->spinbuttons = g_new (GtkWidget *, N_TIMESPANS); } static void filter_datespec_finalise(GtkObject *obj) { FilterDatespec *o = (FilterDatespec *)obj; if (o->priv) { g_free (PRIV(o)->spinbuttons); g_free (o->priv); } ((GtkObjectClass *)(parent_class))->finalize(obj); } /** * filter_datespec_new: * * Create a new FilterDatespec object. * * Return value: A new #FilterDatespec object. **/ FilterDatespec * filter_datespec_new (void) { FilterDatespec *o = (FilterDatespec *)gtk_type_new (filter_datespec_get_type ()); return o; } static gboolean validate (FilterElement *fe) { FilterDatespec *fds = (FilterDatespec *) fe; gboolean valid = TRUE; if (fds->value <= 0) { GtkWidget *gd; valid = FALSE; if (fds->type == FDST_UNKNOWN) gd = gnome_ok_dialog (_("Oops. You have forgotten to choose a date.")); else gd = gnome_ok_dialog (_("Oops. You have chosen an invalid date.")); gnome_dialog_run_and_close (GNOME_DIALOG (gd)); } return valid; } static void xml_create (FilterElement *fe, xmlNodePtr node) { /* parent implementation */ ((FilterElementClass *)(parent_class))->xml_create(fe, node); } static xmlNodePtr xml_encode (FilterElement *fe) { xmlNodePtr value, work; FilterDatespec *fds = (FilterDatespec *)fe; gchar str[32]; d(printf ("Encoding datespec as xml\n")); value = xmlNewNode (NULL, "value"); xmlSetProp (value, "name", fe->name); xmlSetProp (value, "type", "datespec"); work = xmlNewChild (value, NULL, "datespec", NULL); sprintf (str, "%d", fds->type); xmlSetProp (work, "type", str); sprintf (str, "%d", (int)fds->value); xmlSetProp (work, "value", str); return value; } static int xml_decode (FilterElement *fe, xmlNodePtr node) { FilterDatespec *fds = (FilterDatespec *)fe; xmlNodePtr n; gchar *val; d(printf ("Decoding datespec from xml %p\n", fe)); xmlFree (fe->name); fe->name = xmlGetProp (node, "name"); n = node->childs; while (n) { if (!strcmp (n->name, "datespec")) { val = xmlGetProp (n, "type"); fds->type = atoi (val); xmlFree (val); val = xmlGetProp (n, "value"); fds->value = atoi (val); xmlFree (val); break; } n = n->next; } return 0; } static void activate_now (GtkMenuItem *item, FilterDatespec *fds) { if (PRIV (fds)->cur_extra_widget) { gtk_container_remove (GTK_CONTAINER (PRIV(fds)->gd->vbox), PRIV (fds)->cur_extra_widget); PRIV (fds)->cur_extra_widget = NULL; } gtk_label_set_text (GTK_LABEL (PRIV (fds)->descriptive_label), _("The message's date will be compared against\n" "whatever the time is when the filter is run\n" "or vfolder is opened.")); PRIV (fds)->selected_type = FDST_NOW; } static void activate_specified (GtkMenuItem *item, FilterDatespec *fds) { struct tm *seltime; /* Remove other widget if it exists */ if (PRIV (fds)->cur_extra_widget) { gtk_container_remove (GTK_CONTAINER (PRIV (fds)->gd->vbox), PRIV (fds)->cur_extra_widget); PRIV (fds)->cur_extra_widget = NULL; } /* Set description */ gtk_label_set_text (GTK_LABEL (PRIV (fds)->descriptive_label), _("The message's date will be compared against\n" "the time that you specify here.")); /* Reset if going from one type to another */ if (PRIV (fds)->selected_type != FDST_SPECIFIED) fds->value = 0; PRIV (fds)->selected_type = FDST_SPECIFIED; /* Set the calendar's time */ if (fds->value > 0) { /* gmtime? */ seltime = localtime (&(fds->value)); gtk_calendar_select_month (GTK_CALENDAR (PRIV (fds)->date_chooser), seltime->tm_mon, seltime->tm_year + 1900); gtk_calendar_select_day (GTK_CALENDAR (PRIV (fds)->date_chooser), seltime->tm_mday); /* free seltime?? */ } gtk_box_pack_start (GTK_BOX (PRIV (fds)->gd->vbox), PRIV (fds)->date_chooser, TRUE, TRUE, 3); gtk_widget_show (PRIV (fds)->date_chooser); PRIV (fds)->cur_extra_widget = PRIV (fds)->date_chooser; } static void activate_x_ago (GtkMenuItem *item, FilterDatespec *fds) { if (PRIV (fds)->cur_extra_widget) { gtk_container_remove (GTK_CONTAINER (PRIV (fds)->gd->vbox), PRIV (fds)->cur_extra_widget); PRIV (fds)->cur_extra_widget = NULL; } gtk_label_set_text (GTK_LABEL (PRIV (fds)->descriptive_label), _("The message's date will be compared against\n" "a time relative to when the filter is run;\n" "\"a week ago\", for example.")); /* Reset if going from one type to another */ if (PRIV (fds)->selected_type != FDST_X_AGO) fds->value = 0; PRIV (fds)->selected_type = FDST_X_AGO; if (fds->value > 0) set_adjustments (fds); gtk_box_pack_start (GTK_BOX (PRIV (fds)->gd->vbox), PRIV (fds)->span_chooser, TRUE, TRUE, 3); gtk_widget_show (PRIV (fds)->span_chooser); PRIV (fds)->cur_extra_widget = PRIV (fds)->span_chooser; } typedef void (*my_menu_callback) (GtkMenuItem *, FilterDatespec *); static void button_clicked (GtkButton *button, FilterDatespec *fds) { GnomeDialog *gd; GtkWidget *box; GtkWidget *label; GtkWidget *menu; GtkWidget *selectomatic; GtkWidget *sep; int i; gchar *desc; /* keep in sync with FilterDatespec_type! */ const char *items[] = { N_("the current time"), N_("a time you specify"), N_("a time relative to the current time"), NULL }; const my_menu_callback callbacks[] = { activate_now, activate_specified, activate_x_ago }; PRIV (fds)->descriptive_label = gtk_label_new(""); PRIV (fds)->cur_extra_widget = NULL; PRIV (fds)->double_click = FALSE; /* The calendar */ PRIV (fds)->date_chooser = gtk_calendar_new (); gtk_object_ref (GTK_OBJECT (PRIV (fds)->date_chooser)); gtk_signal_connect (GTK_OBJECT (PRIV (fds)->date_chooser), "day_selected", cal_day_selected, fds); gtk_signal_connect (GTK_OBJECT (PRIV (fds)->date_chooser), "day_selected_double_click", cal_day_selected_double_click, fds); /* The span editor thingie */ make_span_editor (fds); gtk_object_ref (GTK_OBJECT (PRIV (fds)->span_chooser)); /* The dialog */ gd = (GnomeDialog *) gnome_dialog_new ("Select a time to compare against", GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL, NULL); PRIV (fds)->gd = gd; /* The menu */ menu = gtk_menu_new (); for (i = 0; items[i]; i++) { GtkWidget *item; item = gtk_menu_item_new_with_label (gettext (items[i])); gtk_signal_connect (GTK_OBJECT (item), "activate", callbacks[i], fds); gtk_menu_append (GTK_MENU (menu), item); gtk_widget_show (item); } gtk_widget_show (menu); /* The selector */ selectomatic = gtk_option_menu_new(); gtk_option_menu_set_menu (GTK_OPTION_MENU (selectomatic), GTK_WIDGET (menu)); if (fds->type != FDST_UNKNOWN) /* Keep in sync with FilterDatespec_type! */ gtk_option_menu_set_history (GTK_OPTION_MENU (selectomatic), fds->type); gtk_widget_show ((GtkWidget *)selectomatic); /* The label */ label = gtk_label_new (_("Compare against")); gtk_widget_show (label); /* The hbox */ box = gtk_hbox_new (FALSE, 3); gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (box), selectomatic, TRUE, TRUE, 2); gtk_widget_show (box); gtk_box_pack_start ((GtkBox *)gd->vbox, (GtkWidget *)box, TRUE, TRUE, 3); /* The separator */ sep = gtk_hseparator_new (); gtk_widget_show (sep); gtk_box_pack_start (GTK_BOX (gd->vbox), sep, TRUE, TRUE, 3); /* The descriptive label */ gtk_box_pack_start (GTK_BOX (gd->vbox), PRIV (fds)->descriptive_label, TRUE, TRUE, 3); gtk_misc_set_alignment (GTK_MISC (PRIV (fds)->descriptive_label), 0.5, 0.5); gtk_widget_show (PRIV (fds)->descriptive_label); /* Set up the current view */ if (fds->type == FDST_UNKNOWN) fds->type = FDST_NOW; (callbacks[fds->type]) (NULL, fds); /* go go gadget gnomedialog! */ switch (gnome_dialog_run_and_close(gd)) { case -1: /*wm close*/ if (PRIV (fds)->double_click == FALSE) break; /* else fall */ case 0: fds->type = PRIV (fds)->selected_type; PRIV (fds)->descriptive_label = NULL; desc = describe_button (fds); gtk_label_set_text (GTK_LABEL (GTK_BIN (button)->child), desc); g_free (desc); /* falllllll */ case 1: /* cancel */ break; } gtk_widget_destroy (PRIV (fds)->date_chooser); gtk_widget_destroy (PRIV (fds)->span_chooser); } static GtkWidget * get_widget (FilterElement *fe) { FilterDatespec *fds = (FilterDatespec *)fe; GtkWidget *button; GtkWidget *label; gchar *desc; desc = describe_button (fds); label = gtk_label_new (desc); gtk_misc_set_alignment (GTK_MISC (label), 0.5, 0.5); g_free (desc); button = gtk_button_new(); gtk_container_add (GTK_CONTAINER (button), label); gtk_signal_connect (GTK_OBJECT (button), "clicked", button_clicked, fds); gtk_widget_show (button); gtk_widget_show (label); return button; } static void build_code (FilterElement *fe, GString *out, struct _FilterPart *fp) { return; } static void format_sexp (FilterElement *fe, GString *out) { FilterDatespec *fds = (FilterDatespec *)fe; switch (fds->type) { case FDST_UNKNOWN: g_warning ("user hasn't selected a datespec yet!"); /* fall through */ case FDST_NOW: g_string_append (out, "(get-current-date)"); break; case FDST_SPECIFIED: g_string_sprintfa (out, "%d", (int) fds->value); break; case FDST_X_AGO: g_string_sprintfa (out, "(- (get-current-date) %d)", (int) fds->value); break; } } static gchar * stringify_agoness (FilterDatespec *fds) { time_t val; GString *str; gchar *ret; str = g_string_new(""); val = fds->value; if (val == 0) { g_string_append (str, _("now")); } else { int where; where = 0; while (val) { int count; count = 0; while (timespans[where].seconds <= val) { count++; val -= timespans[where].seconds; } if (count != 0 ) { if (count > 1) g_string_sprintfa (str, "%d %s", (int) count, gettext (timespans[where].plural)); else g_string_sprintfa (str, "%d %s", (int) count, gettext (timespans[where].singular)); if (val) g_string_append (str, ", "); } where++; } g_string_append (str, " ago"); } ret = str->str; g_string_free (str, FALSE); return ret; } static void make_span_editor (FilterDatespec *fds) { int i; int chunk; int delta; GtkWidget *table; /*PRIV (fds)->span_chooser = gtk_vbox_new (TRUE, 3);*/ table = gtk_table_new (N_TIMECHUNKS, MAX_CHUNK * 2, FALSE); i = 0; for (chunk = 0; chunk < N_TIMECHUNKS; chunk++ ) { /*GtkWidget *hbox;*/ /*hbox = gtk_hbox_new (FALSE, 1);*/ /*gtk_box_pack_start (GTK_BOX (PRIV (fds)->span_chooser), * hbox, TRUE, TRUE, 1); */ /*gtk_table_attach (GTK_TABLE (PRIV (fds)->span_chooser), * hbox, * 0, 1, chunk, chunk + 1, * 0, GTK_EXPAND | GTK_FILL, * 3, 3); *gtk_widget_show (hbox); */ for (delta = 0; delta < timechunks[chunk]; delta++, i++ ) { gchar *text; GtkObject *adj; GtkWidget *sb; GtkWidget *label; adj = gtk_adjustment_new (0.0, 0.0, timespans[i].max, 1.0, 10.0, 0.0); sb = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0); /*gtk_box_pack_start (GTK_BOX (hbox), sb, FALSE, FALSE, 1);*/ gtk_table_attach (GTK_TABLE (table), sb, delta * 2, delta * 2 + 1, chunk, chunk + 1, 0, GTK_EXPAND | GTK_FILL, 2, 4); PRIV (fds)->spinbuttons[i] = sb; gtk_widget_show (GTK_WIDGET (sb)); if (delta + 1 < timechunks[chunk]) text = g_strdup_printf ("%s, ", gettext (timespans[i].plural)); else text = g_strdup_printf ("%s ago", gettext (timespans[i].plural)); label = gtk_label_new (text); g_free (text); /*gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 3);*/ gtk_table_attach (GTK_TABLE (table), label, delta * 2 + 1, (delta + 1) * 2, chunk, chunk + 1, 0, GTK_EXPAND | GTK_FILL, 2, 4); gtk_widget_show (label); gtk_signal_connect (adj, "value_changed", adj_value_changed, fds); } } PRIV (fds)->span_chooser = table; } static void adj_value_changed (GtkAdjustment *adj, gpointer user_data) { FilterDatespec *fds = (FilterDatespec *) user_data; int i; fds->value = 0; for (i = 0; i < N_TIMESPANS; i++) fds->value += timespans[i].seconds * (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (PRIV (fds)->spinbuttons[i]))); } static void set_adjustments (FilterDatespec *fds) { time_t val; int where; val = fds->value; where = 0; while (val) { int count; count = 0; while (timespans[where].seconds <= val) { count++; val -= timespans[where].seconds; } gtk_spin_button_set_value (GTK_SPIN_BUTTON (PRIV (fds)->spinbuttons[where]), (gfloat) count); where++; } } static gchar * describe_button (FilterDatespec *fds) { gchar *desc = NULL; switch (fds->type) { case FDST_UNKNOWN: desc = g_strdup (_("")); break; case FDST_NOW: desc = g_strdup (_("now")); break; case FDST_SPECIFIED: desc = g_strdup (ctime (&(fds->value))); break; case FDST_X_AGO: desc = stringify_agoness (fds); break; } return desc; } static void cal_day_selected (GtkCalendar *cal, gpointer user_data) { FilterDatespec *fds = (FilterDatespec *)user_data; struct tm seltime; seltime.tm_sec = 0; seltime.tm_min = 0; seltime.tm_hour = 0; seltime.tm_mday = cal->selected_day; seltime.tm_mon = cal->month; seltime.tm_year = cal->year - 1900; seltime.tm_isdst = -1; fds->value = mktime (&seltime); } static void cal_day_selected_double_click (GtkCalendar *cal, gpointer user_data) { FilterDatespec *fds = (FilterDatespec *)user_data; cal_day_selected (cal, user_data); PRIV (fds)->double_click = TRUE; gnome_dialog_close (PRIV (fds)->gd); }