No issues found
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU Lesser General Public
4 * License as published by the Free Software Foundation; either
5 * version 2 of the License, or (at your option) version 3.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public
13 * License along with the program; if not, see <http://www.gnu.org/licenses/>
14 *
15 *
16 * Authors:
17 * Not Zed <notzed@lostzed.mmc.com.au>
18 * Jeffrey Stedfast <fejj@ximian.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29 #include <stdlib.h>
30 #include <time.h>
31 #include <math.h>
32
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35
36 #include "libevolution-utils/e-alert.h"
37 #include "libevolution-utils/evolution-util.h"
38
39 #include "e-filter-datespec.h"
40 #include "e-filter-part.h"
41
42 #ifdef G_OS_WIN32
43 #ifdef localtime_r
44 #undef localtime_r
45 #endif
46 #define localtime_r(tp,tmp) memcpy(tmp,localtime(tp),sizeof(struct tm))
47 #endif
48
49 #define E_FILTER_DATESPEC_GET_PRIVATE(obj) \
50 (G_TYPE_INSTANCE_GET_PRIVATE \
51 ((obj), E_TYPE_FILTER_DATESPEC, EFilterDatespecPrivate))
52
53 #define d(x)
54
55 typedef struct {
56 guint32 seconds;
57 const gchar *past_singular;
58 const gchar *past_plural;
59 const gchar *future_singular;
60 const gchar *future_plural;
61 gfloat max;
62 } timespan;
63
64 #if 0
65
66 /* Don't delete this code, since it is needed so that xgettext can extract the translations.
67 * Please, keep these strings in sync with the strings in the timespans array */
68
69 ngettext ("1 second ago", "%d seconds ago", 1);
70 ngettext ("1 second in the future", "%d seconds in the future", 1);
71 ngettext ("1 minute ago", "%d minutes ago", 1);
72 ngettext ("1 minute in the future", "%d minutes in the future", 1);
73 ngettext ("1 hour ago", "%d hours ago", 1);
74 ngettext ("1 hour in the future", "%d hours in the future", 1);
75 ngettext ("1 day ago", "%d days ago", 1);
76 ngettext ("1 day in the future", "%d days in the future", 1);
77 ngettext ("1 week ago", "%d weeks ago", 1);
78 ngettext ("1 week in the future", "%d weeks in the future", 1)
79 ngettext ("1 month ago", "%d months ago", 1);
80 ngettext ("1 month in the future", "%d months in the future", 1);
81 ngettext ("1 year ago", "%d years ago", 1);
82 ngettext ("1 year in the future", "%d years in the future", 1);
83
84 #endif
85
86 static const timespan timespans[] = {
87 { 1, "1 second ago", "%d seconds ago", "1 second in the future", "%d seconds in the future", 59.0 },
88 { 60, "1 minute ago", "%d minutes ago", "1 minute in the future", "%d minutes in the future", 59.0 },
89 { 3600, "1 hour ago", "%d hours ago", "1 hour in the future", "%d hours in the future", 23.0 },
90 { 86400, "1 day ago", "%d days ago", "1 day in the future", "%d days in the future", 31.0 },
91 { 604800, "1 week ago", "%d weeks ago", "1 week in the future", "%d weeks in the future", 52.0 },
92 { 2419200, "1 month ago", "%d months ago", "1 month in the future", "%d months in the future", 12.0 },
93 { 31557600, "1 year ago", "%d years ago", "1 year in the future", "%d years in the future", 1000.0 },
94 };
95
96 #define DAY_INDEX 3
97
98 struct _EFilterDatespecPrivate {
99 GtkWidget *label_button;
100 GtkWidget *notebook_type, *combobox_type, *calendar_specify, *spin_relative, *combobox_relative, *combobox_past_future;
101 EFilterDatespecType type;
102 gint span;
103 };
104
105 G_DEFINE_TYPE (
106 EFilterDatespec,
107 e_filter_datespec,
108 E_TYPE_FILTER_ELEMENT)
109
110 static gint
111 get_best_span (time_t val)
112 {
113 gint i;
114
115 for (i = G_N_ELEMENTS (timespans) - 1; i >= 0; i--) {
116 if (val % timespans[i].seconds == 0)
117 return i;
118 }
119
120 return 0;
121 }
122
123 /* sets button label */
124 static void
125 set_button (EFilterDatespec *fds)
126 {
127 gchar buf[128];
128 gchar *label = buf;
129
130 switch (fds->type) {
131 case FDST_UNKNOWN:
132 label = _("<click here to select a date>");
133 break;
134 case FDST_NOW:
135 label = _("now");
136 break;
137 case FDST_SPECIFIED: {
138 struct tm tm;
139
140 localtime_r (&fds->value, &tm);
141 /* strftime for date filter display, only needs to show a day date (i.e. no time) */
142 strftime (buf, sizeof (buf), _("%d-%b-%Y"), &tm);
143 break; }
144 case FDST_X_AGO:
145 if (fds->value == 0)
146 label = _("now");
147 else {
148 gint span, count;
149
150 span = get_best_span (fds->value);
151 count = fds->value / timespans[span].seconds;
152 sprintf (buf, ngettext (timespans[span].past_singular, timespans[span].past_plural, count), count);
153 }
154 break;
155 case FDST_X_FUTURE:
156 if (fds->value == 0)
157 label = _("now");
158 else {
159 gint span, count;
160
161 span = get_best_span (fds->value);
162 count = fds->value / timespans[span].seconds;
163 sprintf (buf, ngettext (timespans[span].future_singular, timespans[span].future_plural, count), count);
164 }
165 break;
166 }
167
168 gtk_label_set_text ((GtkLabel *) fds->priv->label_button, label);
169 }
170
171 static void
172 get_values (EFilterDatespec *fds)
173 {
174 EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
175
176 switch (fds->priv->type) {
177 case FDST_SPECIFIED: {
178 guint year, month, day;
179 struct tm tm;
180
181 gtk_calendar_get_date ((GtkCalendar *) p->calendar_specify, &year, &month, &day);
182 memset (&tm, 0, sizeof (tm));
183 tm.tm_mday = day;
184 tm.tm_year = year - 1900;
185 tm.tm_mon = month;
186 fds->value = mktime (&tm);
187 /* what about timezone? */
188 break; }
189 case FDST_X_FUTURE:
190 case FDST_X_AGO: {
191 gint val;
192
193 val = gtk_spin_button_get_value_as_int ((GtkSpinButton *) p->spin_relative);
194 fds->value = timespans[p->span].seconds * val;
195 break; }
196 case FDST_NOW:
197 default:
198 break;
199 }
200
201 fds->type = p->type;
202 }
203
204 static void
205 set_values (EFilterDatespec *fds)
206 {
207 gint note_type;
208
209 EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
210
211 p->type = fds->type == FDST_UNKNOWN ? FDST_NOW : fds->type;
212
213 note_type = p->type==FDST_X_FUTURE ? FDST_X_AGO : p->type; /* FUTURE and AGO use the same notebook pages/etc. */
214
215 switch (p->type) {
216 case FDST_NOW:
217 case FDST_UNKNOWN:
218 /* noop */
219 break;
220 case FDST_SPECIFIED:
221 {
222 struct tm tm;
223
224 localtime_r (&fds->value, &tm);
225 gtk_calendar_select_month ((GtkCalendar *) p->calendar_specify, tm.tm_mon, tm.tm_year + 1900);
226 gtk_calendar_select_day ((GtkCalendar *) p->calendar_specify, tm.tm_mday);
227 break;
228 }
229 case FDST_X_AGO:
230 p->span = get_best_span (fds->value);
231 gtk_spin_button_set_value ((GtkSpinButton *) p->spin_relative, fds->value / timespans[p->span].seconds);
232 gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_relative), p->span);
233 gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_past_future), 0);
234 break;
235 case FDST_X_FUTURE:
236 p->span = get_best_span (fds->value);
237 gtk_spin_button_set_value ((GtkSpinButton *) p->spin_relative, fds->value / timespans[p->span].seconds);
238 gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_relative), p->span);
239 gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_past_future), 1);
240 break;
241 }
242
243 gtk_notebook_set_current_page ((GtkNotebook *) p->notebook_type, note_type);
244 gtk_combo_box_set_active (GTK_COMBO_BOX (p->combobox_type), note_type);
245 }
246
247 static void
248 set_combobox_type (GtkComboBox *combobox,
249 EFilterDatespec *fds)
250 {
251 fds->priv->type = gtk_combo_box_get_active (combobox);
252 gtk_notebook_set_current_page ((GtkNotebook *) fds->priv->notebook_type, fds->priv->type);
253 }
254
255 static void
256 set_combobox_relative (GtkComboBox *combobox,
257 EFilterDatespec *fds)
258 {
259 fds->priv->span = gtk_combo_box_get_active (combobox);
260 }
261
262 static void
263 set_combobox_past_future (GtkComboBox *combobox,
264 EFilterDatespec *fds)
265 {
266 if (gtk_combo_box_get_active (combobox) == 0)
267 fds->type = fds->priv->type = FDST_X_AGO;
268 else
269 fds->type = fds->priv->type = FDST_X_FUTURE;
270 }
271
272 static void
273 button_clicked (GtkButton *button,
274 EFilterDatespec *fds)
275 {
276 EFilterDatespecPrivate *p = E_FILTER_DATESPEC_GET_PRIVATE (fds);
277 GtkWidget *content_area;
278 GtkWidget *toplevel;
279 GtkDialog *dialog;
280 GtkBuilder *builder;
281
282 /* XXX I think we're leaking the GtkBuilder. */
283 builder = gtk_builder_new ();
284 e_load_ui_builder_definition (builder, "filter.ui");
285
286 toplevel = e_builder_get_widget (builder, "filter_datespec");
287
288 dialog = (GtkDialog *) gtk_dialog_new ();
289 gtk_window_set_title (
290 GTK_WINDOW (dialog),
291 _("Select a time to compare against"));
292 gtk_dialog_add_buttons (
293 dialog,
294 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
295 GTK_STOCK_OK, GTK_RESPONSE_OK,
296 NULL);
297
298 p->notebook_type = e_builder_get_widget (builder, "notebook_type");
299 p->combobox_type = e_builder_get_widget (builder, "combobox_type");
300 p->calendar_specify = e_builder_get_widget (builder, "calendar_specify");
301 p->spin_relative = e_builder_get_widget (builder, "spin_relative");
302 p->combobox_relative = e_builder_get_widget (builder, "combobox_relative");
303 p->combobox_past_future = e_builder_get_widget (builder, "combobox_past_future");
304
305 set_values (fds);
306
307 g_signal_connect (
308 p->combobox_type, "changed",
309 G_CALLBACK (set_combobox_type), fds);
310 g_signal_connect (
311 p->combobox_relative, "changed",
312 G_CALLBACK (set_combobox_relative), fds);
313 g_signal_connect (
314 p->combobox_past_future, "changed",
315 G_CALLBACK (set_combobox_past_future), fds);
316
317 content_area = gtk_dialog_get_content_area (dialog);
318 gtk_box_pack_start (GTK_BOX (content_area), toplevel, TRUE, TRUE, 3);
319
320 if (gtk_dialog_run (dialog) == GTK_RESPONSE_OK) {
321 get_values (fds);
322 set_button (fds);
323 }
324
325 gtk_widget_destroy ((GtkWidget *) dialog);
326 }
327
328 static gboolean
329 filter_datespec_validate (EFilterElement *element,
330 EAlert **alert)
331 {
332 EFilterDatespec *fds = E_FILTER_DATESPEC (element);
333 gboolean valid;
334
335 g_warn_if_fail (alert == NULL || *alert == NULL);
336
337 valid = fds->type != FDST_UNKNOWN;
338 if (!valid) {
339 if (alert)
340 *alert = e_alert_new ("filter:no-date", NULL);
341 }
342
343 return valid;
344 }
345
346 static gint
347 filter_datespec_eq (EFilterElement *element_a,
348 EFilterElement *element_b)
349 {
350 EFilterDatespec *datespec_a = E_FILTER_DATESPEC (element_a);
351 EFilterDatespec *datespec_b = E_FILTER_DATESPEC (element_b);
352
353 /* Chain up to parent's eq() method. */
354 if (!E_FILTER_ELEMENT_CLASS (e_filter_datespec_parent_class)->
355 eq (element_a, element_b))
356 return FALSE;
357
358 return (datespec_a->type == datespec_b->type) &&
359 (datespec_a->value == datespec_b->value);
360 }
361
362 static xmlNodePtr
363 filter_datespec_xml_encode (EFilterElement *element)
364 {
365 xmlNodePtr value, work;
366 EFilterDatespec *fds = E_FILTER_DATESPEC (element);
367 gchar str[32];
368
369 d (printf ("Encoding datespec as xml\n"));
370
371 value = xmlNewNode (NULL, (xmlChar *)"value");
372 xmlSetProp (value, (xmlChar *)"name", (xmlChar *) element->name);
373 xmlSetProp (value, (xmlChar *)"type", (xmlChar *)"datespec");
374
375 work = xmlNewChild (value, NULL, (xmlChar *)"datespec", NULL);
376 sprintf (str, "%d", fds->type);
377 xmlSetProp (work, (xmlChar *)"type", (xmlChar *) str);
378 sprintf (str, "%d", (gint) fds->value);
379 xmlSetProp (work, (xmlChar *)"value", (xmlChar *) str);
380
381 return value;
382 }
383
384 static gint
385 filter_datespec_xml_decode (EFilterElement *element,
386 xmlNodePtr node)
387 {
388 EFilterDatespec *fds = E_FILTER_DATESPEC (element);
389 xmlNodePtr n;
390 xmlChar *val;
391
392 d (printf ("Decoding datespec from xml %p\n", element));
393
394 xmlFree (element->name);
395 element->name = (gchar *) xmlGetProp (node, (xmlChar *)"name");
396
397 n = node->children;
398 while (n) {
399 if (!strcmp ((gchar *) n->name, "datespec")) {
400 val = xmlGetProp (n, (xmlChar *)"type");
401 fds->type = atoi ((gchar *) val);
402 xmlFree (val);
403 val = xmlGetProp (n, (xmlChar *)"value");
404 fds->value = atoi ((gchar *) val);
405 xmlFree (val);
406 break;
407 }
408 n = n->next;
409 }
410
411 return 0;
412 }
413
414 static GtkWidget *
415 filter_datespec_get_widget (EFilterElement *element)
416 {
417 EFilterDatespec *fds = E_FILTER_DATESPEC (element);
418 GtkWidget *button;
419
420 fds->priv->label_button = gtk_label_new ("");
421 gtk_misc_set_alignment (GTK_MISC (fds->priv->label_button), 0.5, 0.5);
422 set_button (fds);
423
424 button = gtk_button_new ();
425 gtk_container_add (GTK_CONTAINER (button), fds->priv->label_button);
426 g_signal_connect (
427 button, "clicked",
428 G_CALLBACK (button_clicked), fds);
429
430 gtk_widget_show (button);
431 gtk_widget_show (fds->priv->label_button);
432
433 return button;
434 }
435
436 static void
437 filter_datespec_format_sexp (EFilterElement *element,
438 GString *out)
439 {
440 EFilterDatespec *fds = E_FILTER_DATESPEC (element);
441
442 switch (fds->type) {
443 case FDST_UNKNOWN:
444 g_warning ("user hasn't selected a datespec yet!");
445 /* fall through */
446 case FDST_NOW:
447 g_string_append (out, "(get-current-date)");
448 break;
449 case FDST_SPECIFIED:
450 g_string_append_printf (out, "%d", (gint) fds->value);
451 break;
452 case FDST_X_AGO:
453 switch (get_best_span (fds->value)) {
454 case 5: /* months */
455 g_string_append_printf (out, "(get-relative-months (- 0 %d))", (gint) (fds->value / timespans[5].seconds));
456 break;
457 case 6: /* years */
458 g_string_append_printf (out, "(get-relative-months (- 0 %d))", (gint) (12 * fds->value / timespans[6].seconds));
459 break;
460 default:
461 g_string_append_printf (out, "(- (get-current-date) %d)", (gint) fds->value);
462 break;
463 }
464 break;
465 case FDST_X_FUTURE:
466 switch (get_best_span (fds->value)) {
467 case 5: /* months */
468 g_string_append_printf (out, "(get-relative-months %d)", (gint) (fds->value / timespans[5].seconds));
469 break;
470 case 6: /* years */
471 g_string_append_printf (out, "(get-relative-months %d)", (gint) (12 * fds->value / timespans[6].seconds));
472 break;
473 default:
474 g_string_append_printf (out, "(+ (get-current-date) %d)", (gint) fds->value);
475 break;
476 }
477 break;
478 }
479 }
480
481 static void
482 e_filter_datespec_class_init (EFilterDatespecClass *class)
483 {
484 EFilterElementClass *filter_element_class;
485
486 g_type_class_add_private (class, sizeof (EFilterDatespecPrivate));
487
488 filter_element_class = E_FILTER_ELEMENT_CLASS (class);
489 filter_element_class->validate = filter_datespec_validate;
490 filter_element_class->eq = filter_datespec_eq;
491 filter_element_class->xml_encode = filter_datespec_xml_encode;
492 filter_element_class->xml_decode = filter_datespec_xml_decode;
493 filter_element_class->get_widget = filter_datespec_get_widget;
494 filter_element_class->format_sexp = filter_datespec_format_sexp;
495 }
496
497 static void
498 e_filter_datespec_init (EFilterDatespec *datespec)
499 {
500 datespec->priv = E_FILTER_DATESPEC_GET_PRIVATE (datespec);
501 datespec->type = FDST_UNKNOWN;
502 }
503
504 /**
505 * filter_datespec_new:
506 *
507 * Create a new EFilterDatespec object.
508 *
509 * Return value: A new #EFilterDatespec object.
510 **/
511 EFilterDatespec *
512 e_filter_datespec_new (void)
513 {
514 return g_object_new (E_TYPE_FILTER_DATESPEC, NULL);
515 }