evolution-3.6.4/filter/e-filter-datespec.c

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 }