1 /*
2  * ECellDateEditText - a subclass of ECellText used to show and edit the text
3  * representation of the date, from a ECalComponentDateTime* model value.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors:
18  *		Damon Chaplin <damon@ximian.com>
19  *
20  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21  */
22 
23 #include "evolution-config.h"
24 
25 #include <sys/time.h>
26 #include <time.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <glib/gi18n.h>
30 #include <libecal/libecal.h>
31 
32 #include "e-cell-date-edit-text.h"
33 
34 #define E_CELL_DATE_EDIT_TEXT_GET_PRIVATE(obj) \
35 	(G_TYPE_INSTANCE_GET_PRIVATE \
36 	((obj), E_TYPE_CELL_DATE_EDIT_TEXT, ECellDateEditTextPrivate))
37 
38 struct _ECellDateEditTextPrivate {
39 
40 	/* The timezone to display the date in. */
41 	ICalTimezone *timezone;
42 
43 	/* Whether to display in 24-hour format. */
44 	gboolean use_24_hour_format;
45 };
46 
47 enum {
48 	PROP_0,
49 	PROP_TIMEZONE,
50 	PROP_USE_24_HOUR_FORMAT
51 };
52 
G_DEFINE_TYPE(ECellDateEditText,e_cell_date_edit_text,E_TYPE_CELL_TEXT)53 G_DEFINE_TYPE (
54 	ECellDateEditText,
55 	e_cell_date_edit_text,
56 	E_TYPE_CELL_TEXT)
57 
58 static void
59 cell_date_edit_text_set_property (GObject *object,
60                                   guint property_id,
61                                   const GValue *value,
62                                   GParamSpec *pspec)
63 {
64 	switch (property_id) {
65 		case PROP_TIMEZONE:
66 			e_cell_date_edit_text_set_timezone (
67 				E_CELL_DATE_EDIT_TEXT (object),
68 				g_value_get_object (value));
69 			return;
70 
71 		case PROP_USE_24_HOUR_FORMAT:
72 			e_cell_date_edit_text_set_use_24_hour_format (
73 				E_CELL_DATE_EDIT_TEXT (object),
74 				g_value_get_boolean (value));
75 			return;
76 	}
77 
78 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
79 }
80 
81 static void
cell_date_edit_text_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)82 cell_date_edit_text_get_property (GObject *object,
83                                   guint property_id,
84                                   GValue *value,
85                                   GParamSpec *pspec)
86 {
87 	switch (property_id) {
88 		case PROP_TIMEZONE:
89 			g_value_set_object (
90 				value,
91 				e_cell_date_edit_text_get_timezone (
92 				E_CELL_DATE_EDIT_TEXT (object)));
93 			return;
94 
95 		case PROP_USE_24_HOUR_FORMAT:
96 			g_value_set_boolean (
97 				value,
98 				e_cell_date_edit_text_get_use_24_hour_format (
99 				E_CELL_DATE_EDIT_TEXT (object)));
100 			return;
101 	}
102 
103 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
104 }
105 
106 static void
cell_date_edit_text_finalize(GObject * object)107 cell_date_edit_text_finalize (GObject *object)
108 {
109 	ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (object);
110 
111 	g_clear_object (&ecd->priv->timezone);
112 
113 	/* Chain up to parent's method. */
114 	G_OBJECT_CLASS (e_cell_date_edit_text_parent_class)->finalize (object);
115 }
116 
117 static gchar *
cell_date_edit_text_get_text(ECellText * cell,ETableModel * model,gint col,gint row)118 cell_date_edit_text_get_text (ECellText *cell,
119                               ETableModel *model,
120                               gint col,
121                               gint row)
122 {
123 	ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (cell);
124 	ECellDateEditValue *dv = e_table_model_value_at (model, col, row);
125 	ICalTimezone *timezone;
126 	ICalTime *tt;
127 	struct tm tmp_tm;
128 	gchar *res;
129 
130 	if (!dv)
131 		return g_strdup ("");
132 
133 	timezone = e_cell_date_edit_text_get_timezone (ecd);
134 
135 	tt = e_cell_date_edit_value_get_time (dv);
136 
137 	/* Note that although the property may be in a different
138 	 * timezone, we convert it to the current timezone to display
139 	 * it in the table. If the user actually edits the value,
140 	 * it will be set to the current timezone. See set_value (). */
141 	tmp_tm = e_cal_util_icaltime_to_tm_with_zone (tt, e_cell_date_edit_value_get_zone (dv), timezone);
142 
143 	res = e_datetime_format_format_tm (
144 		"calendar", "table", i_cal_time_is_date (tt) ?
145 		DTFormatKindDate : DTFormatKindDateTime, &tmp_tm);
146 
147 	e_table_model_free_value (model, col, dv);
148 
149 	return res;
150 }
151 
152 static void
cell_date_edit_text_free_text(ECellText * cell,ETableModel * model,gint col,gchar * text)153 cell_date_edit_text_free_text (ECellText *cell,
154 			       ETableModel *model,
155 			       gint col,
156                                gchar *text)
157 {
158 	g_free (text);
159 }
160 
161 /* FIXME: We need to set the "transient_for" property for the dialog. */
162 static void
show_date_warning(ECellDateEditText * ecd)163 show_date_warning (ECellDateEditText *ecd)
164 {
165 	GtkWidget *dialog;
166 	gchar buffer[64], *format;
167 	time_t t;
168 	struct tm *tmp_tm;
169 
170 	t = time (NULL);
171 	/* We are only using this as an example, so the timezone doesn't
172 	 * matter. */
173 	tmp_tm = localtime (&t);
174 
175 	if (e_cell_date_edit_text_get_use_24_hour_format (ecd))
176 		/* strftime format of a weekday, a date and a time, 24-hour. */
177 		format = _("%a %m/%d/%Y %H:%M:%S");
178 	else
179 		/* strftime format of a weekday, a date and a time, 12-hour. */
180 		format = _("%a %m/%d/%Y %I:%M:%S %p");
181 
182 	e_utf8_strftime (buffer, sizeof (buffer), format, tmp_tm);
183 
184 	dialog = gtk_message_dialog_new (
185 		NULL, 0,
186 		GTK_MESSAGE_ERROR,
187 		GTK_BUTTONS_OK,
188 		_("The date must be entered in the format: \n%s"),
189 		buffer);
190 	gtk_dialog_run (GTK_DIALOG (dialog));
191 	gtk_widget_destroy (dialog);
192 }
193 
194 static void
cell_date_edit_text_set_value(ECellText * cell,ETableModel * model,gint col,gint row,const gchar * text)195 cell_date_edit_text_set_value (ECellText *cell,
196                                ETableModel *model,
197                                gint col,
198                                gint row,
199                                const gchar *text)
200 {
201 	ECellDateEditText *ecd = E_CELL_DATE_EDIT_TEXT (cell);
202 	ETimeParseStatus status;
203 	struct tm tmp_tm;
204 	ECellDateEditValue *dv = NULL;
205 	ECellDateEditValue *value;
206 	gboolean is_date = TRUE;
207 
208 	/* Try to parse just a date first. If the value is only a date, we
209 	 * use a DATE value. */
210 	status = e_time_parse_date (text, &tmp_tm);
211 	if (status == E_TIME_PARSE_INVALID) {
212 		is_date = FALSE;
213 		status = e_time_parse_date_and_time (text, &tmp_tm);
214 
215 		if (status == E_TIME_PARSE_INVALID) {
216 			show_date_warning (ecd);
217 			return;
218 		}
219 	}
220 
221 	if (status == E_TIME_PARSE_NONE) {
222 		value = NULL;
223 	} else {
224 		ICalTime *tt;
225 		ICalTimezone *zone;
226 
227 		tt = e_cal_util_tm_to_icaltime (&tmp_tm, is_date);
228 
229 		if (is_date) {
230 			zone = NULL;
231 		} else {
232 			zone = e_cell_date_edit_text_get_timezone (ecd);
233 		}
234 
235 		dv = e_cell_date_edit_value_new (tt, zone);
236 		value = dv;
237 
238 		g_clear_object (&tt);
239 	}
240 
241 	e_table_model_set_value_at (model, col, row, value);
242 
243 	e_cell_date_edit_value_free (dv);
244 }
245 
246 static void
e_cell_date_edit_text_class_init(ECellDateEditTextClass * class)247 e_cell_date_edit_text_class_init (ECellDateEditTextClass *class)
248 {
249 	GObjectClass *object_class;
250 	ECellTextClass *cell_text_class;
251 
252 	g_type_class_add_private (class, sizeof (ECellDateEditTextPrivate));
253 
254 	object_class = G_OBJECT_CLASS (class);
255 	object_class->set_property = cell_date_edit_text_set_property;
256 	object_class->get_property = cell_date_edit_text_get_property;
257 	object_class->finalize = cell_date_edit_text_finalize;
258 
259 	cell_text_class = E_CELL_TEXT_CLASS (class);
260 	cell_text_class->get_text = cell_date_edit_text_get_text;
261 	cell_text_class->free_text = cell_date_edit_text_free_text;
262 	cell_text_class->set_value = cell_date_edit_text_set_value;
263 
264 	g_object_class_install_property (
265 		object_class,
266 		PROP_TIMEZONE,
267 		g_param_spec_object (
268 			"timezone",
269 			"Time Zone",
270 			NULL,
271 			I_CAL_TYPE_TIMEZONE,
272 			G_PARAM_READWRITE));
273 
274 	g_object_class_install_property (
275 		object_class,
276 		PROP_USE_24_HOUR_FORMAT,
277 		g_param_spec_boolean (
278 			"use-24-hour-format",
279 			"Use 24-Hour Format",
280 			NULL,
281 			TRUE,
282 			G_PARAM_READWRITE));
283 }
284 
285 static void
e_cell_date_edit_text_init(ECellDateEditText * ecd)286 e_cell_date_edit_text_init (ECellDateEditText *ecd)
287 {
288 	ecd->priv = E_CELL_DATE_EDIT_TEXT_GET_PRIVATE (ecd);
289 
290 	ecd->priv->timezone = e_cal_util_copy_timezone (i_cal_timezone_get_utc_timezone ());
291 	ecd->priv->use_24_hour_format = TRUE;
292 	g_object_set (ecd, "use-tabular-numbers", TRUE, NULL);
293 }
294 
295 /**
296  * e_cell_date_edit_text_new:
297  *
298  * Creates a new ECell renderer that can be used to render and edit dates that
299  * that come from the model.  The value returned from the model is
300  * interpreted as being a ECalComponentDateTime*.
301  *
302  * Returns: an ECell object that can be used to render dates.
303  */
304 ECell *
e_cell_date_edit_text_new(const gchar * fontname,GtkJustification justify)305 e_cell_date_edit_text_new (const gchar *fontname,
306                            GtkJustification justify)
307 {
308 	ECell *cell;
309 
310 	cell = g_object_new (E_TYPE_CELL_DATE_EDIT_TEXT, NULL);
311 	e_cell_text_construct (E_CELL_TEXT (cell), fontname, justify);
312 
313 	return cell;
314 }
315 
316 ICalTimezone *
e_cell_date_edit_text_get_timezone(ECellDateEditText * ecd)317 e_cell_date_edit_text_get_timezone (ECellDateEditText *ecd)
318 {
319 	g_return_val_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd), NULL);
320 
321 	return ecd->priv->timezone;
322 }
323 
324 void
e_cell_date_edit_text_set_timezone(ECellDateEditText * ecd,const ICalTimezone * timezone)325 e_cell_date_edit_text_set_timezone (ECellDateEditText *ecd,
326 				    const ICalTimezone *timezone)
327 {
328 	g_return_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd));
329 
330 	if (ecd->priv->timezone == timezone)
331 		return;
332 
333 	g_clear_object (&ecd->priv->timezone);
334 	ecd->priv->timezone = timezone ? e_cal_util_copy_timezone (timezone) : NULL;
335 
336 	g_object_notify (G_OBJECT (ecd), "timezone");
337 }
338 
339 gboolean
e_cell_date_edit_text_get_use_24_hour_format(ECellDateEditText * ecd)340 e_cell_date_edit_text_get_use_24_hour_format (ECellDateEditText *ecd)
341 {
342 	g_return_val_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd), FALSE);
343 
344 	return ecd->priv->use_24_hour_format;
345 }
346 
347 void
e_cell_date_edit_text_set_use_24_hour_format(ECellDateEditText * ecd,gboolean use_24_hour)348 e_cell_date_edit_text_set_use_24_hour_format (ECellDateEditText *ecd,
349                                               gboolean use_24_hour)
350 {
351 	g_return_if_fail (E_IS_CELL_DATE_EDIT_TEXT (ecd));
352 
353 	if (ecd->priv->use_24_hour_format == use_24_hour)
354 		return;
355 
356 	ecd->priv->use_24_hour_format = use_24_hour;
357 
358 	g_object_notify (G_OBJECT (ecd), "use-24-hour-format");
359 }
360 
361 gint
e_cell_date_edit_compare_cb(gconstpointer a,gconstpointer b,gpointer cmp_cache)362 e_cell_date_edit_compare_cb (gconstpointer a,
363                              gconstpointer b,
364                              gpointer cmp_cache)
365 {
366 	ECellDateEditValue *dv1 = (ECellDateEditValue *) a;
367 	ECellDateEditValue *dv2 = (ECellDateEditValue *) b;
368 	ICalTime *tt;
369 	gint res;
370 
371 	/* First check if either is NULL. NULL dates sort last. */
372 	if (!dv1 || !dv2) {
373 		if (dv1 == dv2)
374 			return 0;
375 		else if (dv1)
376 			return -1;
377 		else
378 			return 1;
379 	}
380 
381 	/* Copy the 2nd value and convert it to the same timezone as the first. */
382 	tt = i_cal_time_clone (e_cell_date_edit_value_get_time (dv2));
383 	i_cal_time_convert_timezone (tt, e_cell_date_edit_value_get_zone (dv2), e_cell_date_edit_value_get_zone (dv1));
384 
385 	/* Now we can compare them. */
386 	res = i_cal_time_compare (e_cell_date_edit_value_get_time (dv1), tt);
387 
388 	g_clear_object (&tt);
389 
390 	return res;
391 }
392 
393 struct _ECellDateEditValue {
394 	ICalTime *tt;
395 	ICalTimezone *zone;
396 };
397 
398 ECellDateEditValue *
e_cell_date_edit_value_new(const ICalTime * tt,const ICalTimezone * zone)399 e_cell_date_edit_value_new (const ICalTime *tt,
400 			    const ICalTimezone *zone)
401 {
402 	g_return_val_if_fail (I_CAL_IS_TIME ((ICalTime *) tt), NULL);
403 	if (zone)
404 		g_return_val_if_fail (I_CAL_IS_TIMEZONE ((ICalTimezone *) zone), NULL);
405 
406 	return e_cell_date_edit_value_new_take (i_cal_time_clone (tt),
407 		zone ? e_cal_util_copy_timezone (zone) : NULL);
408 }
409 
410 ECellDateEditValue *
e_cell_date_edit_value_new_take(ICalTime * tt,ICalTimezone * zone)411 e_cell_date_edit_value_new_take (ICalTime *tt,
412 				 ICalTimezone *zone)
413 {
414 	ECellDateEditValue *value;
415 
416 	g_return_val_if_fail (I_CAL_IS_TIME (tt), NULL);
417 	if (zone)
418 		g_return_val_if_fail (I_CAL_IS_TIMEZONE (zone), NULL);
419 
420 	value = g_new0 (ECellDateEditValue, 1);
421 	value->tt = tt;
422 	value->zone = zone;
423 
424 	return value;
425 }
426 
427 ECellDateEditValue *
e_cell_date_edit_value_copy(const ECellDateEditValue * src)428 e_cell_date_edit_value_copy (const ECellDateEditValue *src)
429 {
430 	if (!src)
431 		return NULL;
432 
433 	return e_cell_date_edit_value_new (src->tt, src->zone);
434 }
435 
436 void
e_cell_date_edit_value_free(ECellDateEditValue * value)437 e_cell_date_edit_value_free (ECellDateEditValue *value)
438 {
439 	if (value) {
440 		g_clear_object (&value->tt);
441 		g_clear_object (&value->zone);
442 		g_free (value);
443 	}
444 }
445 
446 ICalTime *
e_cell_date_edit_value_get_time(const ECellDateEditValue * value)447 e_cell_date_edit_value_get_time (const ECellDateEditValue *value)
448 {
449 	g_return_val_if_fail (value != NULL, NULL);
450 
451 	return value->tt;
452 }
453 
454 void
e_cell_date_edit_value_set_time(ECellDateEditValue * value,const ICalTime * tt)455 e_cell_date_edit_value_set_time (ECellDateEditValue *value,
456 				 const ICalTime *tt)
457 {
458 	g_return_if_fail (value != NULL);
459 	g_return_if_fail (I_CAL_IS_TIME ((ICalTime *) tt));
460 
461 	e_cell_date_edit_value_take_time (value, i_cal_time_clone (tt));
462 }
463 
464 void
e_cell_date_edit_value_take_time(ECellDateEditValue * value,ICalTime * tt)465 e_cell_date_edit_value_take_time (ECellDateEditValue *value,
466 				  ICalTime *tt)
467 {
468 	g_return_if_fail (value != NULL);
469 	g_return_if_fail (I_CAL_IS_TIME (tt));
470 
471 	if (value->tt != tt) {
472 		g_clear_object (&value->tt);
473 		value->tt = tt;
474 	} else {
475 		g_clear_object (&tt);
476 	}
477 }
478 
479 ICalTimezone *
e_cell_date_edit_value_get_zone(const ECellDateEditValue * value)480 e_cell_date_edit_value_get_zone (const ECellDateEditValue *value)
481 {
482 	g_return_val_if_fail (value != NULL, NULL);
483 
484 	return value->zone;
485 }
486 
487 void
e_cell_date_edit_value_set_zone(ECellDateEditValue * value,const ICalTimezone * zone)488 e_cell_date_edit_value_set_zone (ECellDateEditValue *value,
489 				 const ICalTimezone *zone)
490 {
491 	g_return_if_fail (value != NULL);
492 	if (zone)
493 		g_return_if_fail (I_CAL_IS_TIMEZONE ((ICalTimezone *) zone));
494 
495 	e_cell_date_edit_value_take_zone (value, zone ? e_cal_util_copy_timezone (zone) : NULL);
496 }
497 
498 void
e_cell_date_edit_value_take_zone(ECellDateEditValue * value,ICalTimezone * zone)499 e_cell_date_edit_value_take_zone (ECellDateEditValue *value,
500 				  ICalTimezone *zone)
501 {
502 	g_return_if_fail (value != NULL);
503 	if (zone)
504 		g_return_if_fail (I_CAL_IS_TIMEZONE (zone));
505 
506 	if (zone != value->zone) {
507 		g_clear_object (&value->zone);
508 		value->zone = zone;
509 	} else {
510 		g_clear_object (&zone);
511 	}
512 }
513