1 /*
2 * EDateTimeList - list of calendar dates/times with GtkTreeModel interface.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 *
17 * Authors:
18 * Hans Petter Jansson <hpj@ximian.com>
19 *
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
21 *
22 */
23
24 #include "evolution-config.h"
25
26 #include "comp-util.h"
27
28 #include "e-date-time-list.h"
29
30 #include <string.h>
31 #include <libecal/libecal.h>
32
33 /* XXX Was it really necessary to implement a custom GtkTreeModel for a
34 * one-column list store? There's no mention of why this was done. */
35
36 #define G_LIST(x) ((GList *) x)
37 #define E_DATE_TIME_LIST_IS_SORTED(list) \
38 (E_DATE_TIME_LIST (list)->sort_column_id != -2)
39 #define IS_VALID_ITER(dt_list, iter) \
40 (iter != NULL && iter->user_data != NULL && \
41 dt_list->priv->stamp == iter->stamp)
42
43 struct _EDateTimeListPrivate {
44 gint stamp;
45 GList *list;
46
47 guint columns_dirty : 1;
48
49 gboolean use_24_hour_format;
50 ICalTimezone *zone;
51 };
52
53 enum {
54 PROP_0,
55 PROP_USE_24_HOUR_FORMAT,
56 PROP_TIMEZONE
57 };
58
59 static GType column_types[E_DATE_TIME_LIST_NUM_COLUMNS];
60
61 static void e_date_time_list_tree_model_init (GtkTreeModelIface *iface);
62
G_DEFINE_TYPE_WITH_CODE(EDateTimeList,e_date_time_list,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,e_date_time_list_tree_model_init))63 G_DEFINE_TYPE_WITH_CODE (
64 EDateTimeList, e_date_time_list, G_TYPE_OBJECT,
65 G_IMPLEMENT_INTERFACE (
66 GTK_TYPE_TREE_MODEL, e_date_time_list_tree_model_init))
67
68 static gint
69 compare_datetime (const ICalTime *itt1,
70 const ICalTime *itt2)
71 {
72 return i_cal_time_compare ((ICalTime *) itt1, (ICalTime *) itt2);
73 }
74
75 static void
all_rows_deleted(EDateTimeList * date_time_list)76 all_rows_deleted (EDateTimeList *date_time_list)
77 {
78 GtkTreePath *path;
79 gint i;
80
81 if (!date_time_list->priv->list)
82 return;
83
84 path = gtk_tree_path_new ();
85 i = g_list_length (date_time_list->priv->list);
86 gtk_tree_path_append_index (path, i);
87
88 for (; i >= 0; i--) {
89 gtk_tree_model_row_deleted (GTK_TREE_MODEL (date_time_list), path);
90 gtk_tree_path_prev (path);
91 }
92
93 gtk_tree_path_free (path);
94 }
95
96 static void
row_deleted(EDateTimeList * date_time_list,gint n)97 row_deleted (EDateTimeList *date_time_list,
98 gint n)
99 {
100 GtkTreePath *path;
101
102 path = gtk_tree_path_new ();
103 gtk_tree_path_append_index (path, n);
104 gtk_tree_model_row_deleted (GTK_TREE_MODEL (date_time_list), path);
105 gtk_tree_path_free (path);
106 }
107
108 static void
row_added(EDateTimeList * date_time_list,gint n)109 row_added (EDateTimeList *date_time_list,
110 gint n)
111 {
112 GtkTreePath *path;
113 GtkTreeIter iter;
114
115 path = gtk_tree_path_new ();
116 gtk_tree_path_append_index (path, n);
117
118 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (date_time_list), &iter, path))
119 gtk_tree_model_row_inserted (GTK_TREE_MODEL (date_time_list), path, &iter);
120
121 gtk_tree_path_free (path);
122 }
123
124 static void
row_updated(EDateTimeList * date_time_list,gint n)125 row_updated (EDateTimeList *date_time_list,
126 gint n)
127 {
128 GtkTreePath *path;
129 GtkTreeIter iter;
130
131 path = gtk_tree_path_new ();
132 gtk_tree_path_append_index (path, n);
133
134 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (date_time_list), &iter, path))
135 gtk_tree_model_row_changed (GTK_TREE_MODEL (date_time_list), path, &iter);
136
137 gtk_tree_path_free (path);
138 }
139
140 /* Builds a static string out of an exception date */
141 static gchar *
get_exception_string(EDateTimeList * date_time_list,ICalTime * itt)142 get_exception_string (EDateTimeList *date_time_list,
143 ICalTime *itt)
144 {
145 static gchar buf[256];
146 ICalTime *tt;
147 ICalTimezone *zone;
148
149 zone = e_date_time_list_get_timezone (date_time_list);
150
151 if (zone)
152 tt = i_cal_time_convert_to_zone (itt, zone);
153 else
154 tt = g_object_ref (itt);
155
156 cal_comp_util_format_itt (tt, buf, sizeof (buf));
157
158 g_clear_object (&tt);
159
160 return buf;
161 }
162
163 static void
date_time_list_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)164 date_time_list_set_property (GObject *object,
165 guint property_id,
166 const GValue *value,
167 GParamSpec *pspec)
168 {
169 switch (property_id) {
170 case PROP_USE_24_HOUR_FORMAT:
171 e_date_time_list_set_use_24_hour_format (
172 E_DATE_TIME_LIST (object),
173 g_value_get_boolean (value));
174 return;
175
176 case PROP_TIMEZONE:
177 e_date_time_list_set_timezone (
178 E_DATE_TIME_LIST (object),
179 g_value_get_pointer (value));
180 return;
181 }
182
183 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
184 }
185
186 static void
date_time_list_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)187 date_time_list_get_property (GObject *object,
188 guint property_id,
189 GValue *value,
190 GParamSpec *pspec)
191 {
192 switch (property_id) {
193 case PROP_USE_24_HOUR_FORMAT:
194 g_value_set_boolean (
195 value,
196 e_date_time_list_get_use_24_hour_format (
197 E_DATE_TIME_LIST (object)));
198 return;
199
200 case PROP_TIMEZONE:
201 g_value_set_pointer (
202 value, e_date_time_list_get_timezone (
203 E_DATE_TIME_LIST (object)));
204 return;
205 }
206
207 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
208 }
209
210 static void
date_time_list_finalize(GObject * object)211 date_time_list_finalize (GObject *object)
212 {
213 EDateTimeList *date_time_list = E_DATE_TIME_LIST (object);
214
215 g_clear_object (&date_time_list->priv->zone);
216
217 g_list_free_full (date_time_list->priv->list, g_object_unref);
218 date_time_list->priv->list = NULL;
219
220 /* Chain up to parent's method. */
221 G_OBJECT_CLASS (e_date_time_list_parent_class)->finalize (object);
222 }
223
224 static GtkTreeModelFlags
date_time_list_get_flags(GtkTreeModel * tree_model)225 date_time_list_get_flags (GtkTreeModel *tree_model)
226 {
227 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), 0);
228
229 return GTK_TREE_MODEL_LIST_ONLY;
230 }
231
232 static gint
date_time_list_get_n_columns(GtkTreeModel * tree_model)233 date_time_list_get_n_columns (GtkTreeModel *tree_model)
234 {
235 EDateTimeList *date_time_list = (EDateTimeList *) tree_model;
236
237 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), 0);
238
239 date_time_list->priv->columns_dirty = TRUE;
240 return E_DATE_TIME_LIST_NUM_COLUMNS;
241 }
242
243 static GType
date_time_list_get_column_type(GtkTreeModel * tree_model,gint index)244 date_time_list_get_column_type (GtkTreeModel *tree_model,
245 gint index)
246 {
247 EDateTimeList *date_time_list = (EDateTimeList *) tree_model;
248
249 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), G_TYPE_INVALID);
250 g_return_val_if_fail (index < E_DATE_TIME_LIST_NUM_COLUMNS &&
251 index >= 0, G_TYPE_INVALID);
252
253 date_time_list->priv->columns_dirty = TRUE;
254 return column_types[index];
255 }
256
257 static gboolean
date_time_list_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)258 date_time_list_get_iter (GtkTreeModel *tree_model,
259 GtkTreeIter *iter,
260 GtkTreePath *path)
261 {
262 EDateTimeList *date_time_list = (EDateTimeList *) tree_model;
263 GList *l;
264 gint i;
265
266 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), FALSE);
267 g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE);
268
269 if (!date_time_list->priv->list)
270 return FALSE;
271
272 date_time_list->priv->columns_dirty = TRUE;
273
274 i = gtk_tree_path_get_indices (path)[0];
275 l = g_list_nth (date_time_list->priv->list, i);
276 if (!l)
277 return FALSE;
278
279 iter->user_data = l;
280 iter->stamp = date_time_list->priv->stamp;
281 return TRUE;
282 }
283
284 static GtkTreePath *
date_time_list_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)285 date_time_list_get_path (GtkTreeModel *tree_model,
286 GtkTreeIter *iter)
287 {
288 EDateTimeList *date_time_list = (EDateTimeList *) tree_model;
289 GtkTreePath *retval;
290 GList *l;
291
292 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), NULL);
293 g_return_val_if_fail (iter->stamp == E_DATE_TIME_LIST (tree_model)->priv->stamp, NULL);
294
295 l = iter->user_data;
296 retval = gtk_tree_path_new ();
297 gtk_tree_path_append_index (retval, g_list_position (date_time_list->priv->list, l));
298 return retval;
299 }
300
301 static void
date_time_list_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,gint column,GValue * value)302 date_time_list_get_value (GtkTreeModel *tree_model,
303 GtkTreeIter *iter,
304 gint column,
305 GValue *value)
306 {
307 EDateTimeList *date_time_list = E_DATE_TIME_LIST (tree_model);
308 ICalTime *itt;
309 GList *link;
310 const gchar *str;
311
312 g_return_if_fail (E_IS_DATE_TIME_LIST (tree_model));
313 g_return_if_fail (column < E_DATE_TIME_LIST_NUM_COLUMNS);
314 g_return_if_fail (E_DATE_TIME_LIST (tree_model)->priv->stamp == iter->stamp);
315 g_return_if_fail (IS_VALID_ITER (date_time_list, iter));
316
317 g_value_init (value, column_types[column]);
318
319 if (!date_time_list->priv->list)
320 return;
321
322 link = iter->user_data;
323 itt = link->data;
324
325 if (!itt)
326 return;
327
328 switch (column) {
329 case E_DATE_TIME_LIST_COLUMN_DESCRIPTION:
330 str = get_exception_string (date_time_list, itt);
331 g_value_set_string (value, str);
332 break;
333 }
334 }
335
336 static gboolean
date_time_list_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)337 date_time_list_iter_next (GtkTreeModel *tree_model,
338 GtkTreeIter *iter)
339 {
340 GList *l;
341
342 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), FALSE);
343 g_return_val_if_fail (IS_VALID_ITER (E_DATE_TIME_LIST (tree_model), iter), FALSE);
344
345 if (!E_DATE_TIME_LIST (tree_model)->priv->list)
346 return FALSE;
347
348 l = iter->user_data;
349 l = g_list_next (l);
350 if (l) {
351 iter->user_data = l;
352 return TRUE;
353 }
354
355 return FALSE;
356 }
357
358 static gboolean
date_time_list_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)359 date_time_list_iter_children (GtkTreeModel *tree_model,
360 GtkTreeIter *iter,
361 GtkTreeIter *parent)
362 {
363 EDateTimeList *date_time_list = E_DATE_TIME_LIST (tree_model);
364
365 /* this is a list, nodes have no children */
366 if (parent)
367 return FALSE;
368
369 /* but if parent == NULL we return the list itself as children of the
370 * "root" */
371
372 if (!date_time_list->priv->list)
373 return FALSE;
374
375 iter->stamp = E_DATE_TIME_LIST (tree_model)->priv->stamp;
376 iter->user_data = date_time_list->priv->list;
377 return TRUE;
378 }
379
380 static gboolean
date_time_list_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)381 date_time_list_iter_has_child (GtkTreeModel *tree_model,
382 GtkTreeIter *iter)
383 {
384 g_return_val_if_fail (IS_VALID_ITER (E_DATE_TIME_LIST (tree_model), iter), FALSE);
385 return FALSE;
386 }
387
388 static gint
date_time_list_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)389 date_time_list_iter_n_children (GtkTreeModel *tree_model,
390 GtkTreeIter *iter)
391 {
392 EDateTimeList *date_time_list = E_DATE_TIME_LIST (tree_model);
393
394 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), -1);
395
396 if (iter == NULL)
397 return g_list_length (date_time_list->priv->list);
398
399 g_return_val_if_fail (E_DATE_TIME_LIST (tree_model)->priv->stamp == iter->stamp, -1);
400 return 0;
401 }
402
403 static gboolean
date_time_list_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)404 date_time_list_iter_nth_child (GtkTreeModel *tree_model,
405 GtkTreeIter *iter,
406 GtkTreeIter *parent,
407 gint n)
408 {
409 EDateTimeList *date_time_list = E_DATE_TIME_LIST (tree_model);
410
411 g_return_val_if_fail (E_IS_DATE_TIME_LIST (tree_model), FALSE);
412
413 if (parent)
414 return FALSE;
415
416 if (date_time_list->priv->list) {
417 GList *l;
418
419 l = g_list_nth (date_time_list->priv->list, n);
420 if (!l)
421 return FALSE;
422
423 iter->stamp = date_time_list->priv->stamp;
424 iter->user_data = l;
425 return TRUE;
426 }
427
428 return FALSE;
429 }
430
431 static gboolean
date_time_list_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)432 date_time_list_iter_parent (GtkTreeModel *tree_model,
433 GtkTreeIter *iter,
434 GtkTreeIter *child)
435 {
436 return FALSE;
437 }
438
439 static void
e_date_time_list_class_init(EDateTimeListClass * class)440 e_date_time_list_class_init (EDateTimeListClass *class)
441 {
442 GObjectClass *object_class;
443
444 g_type_class_add_private (class, sizeof (EDateTimeListPrivate));
445
446 object_class = G_OBJECT_CLASS (class);
447 object_class->set_property = date_time_list_set_property;
448 object_class->get_property = date_time_list_get_property;
449 object_class->finalize = date_time_list_finalize;
450
451 g_object_class_install_property (
452 object_class,
453 PROP_USE_24_HOUR_FORMAT,
454 g_param_spec_boolean (
455 "use-24-hour-format",
456 "Use 24-hour Format",
457 NULL,
458 FALSE,
459 G_PARAM_READWRITE));
460
461 g_object_class_install_property (
462 object_class,
463 PROP_TIMEZONE,
464 g_param_spec_pointer (
465 "timezone",
466 "Time Zone",
467 NULL,
468 G_PARAM_READWRITE));
469
470 column_types[E_DATE_TIME_LIST_COLUMN_DESCRIPTION] = G_TYPE_STRING;
471 }
472
473 static void
e_date_time_list_init(EDateTimeList * date_time_list)474 e_date_time_list_init (EDateTimeList *date_time_list)
475 {
476 date_time_list->priv = G_TYPE_INSTANCE_GET_PRIVATE (date_time_list, E_TYPE_DATE_TIME_LIST, EDateTimeListPrivate);
477
478 date_time_list->priv->stamp = g_random_int ();
479 date_time_list->priv->columns_dirty = FALSE;
480 date_time_list->priv->list = NULL;
481 }
482
483 static void
e_date_time_list_tree_model_init(GtkTreeModelIface * iface)484 e_date_time_list_tree_model_init (GtkTreeModelIface *iface)
485 {
486 iface->get_flags = date_time_list_get_flags;
487 iface->get_n_columns = date_time_list_get_n_columns;
488 iface->get_column_type = date_time_list_get_column_type;
489 iface->get_iter = date_time_list_get_iter;
490 iface->get_path = date_time_list_get_path;
491 iface->get_value = date_time_list_get_value;
492 iface->iter_next = date_time_list_iter_next;
493 iface->iter_children = date_time_list_iter_children;
494 iface->iter_has_child = date_time_list_iter_has_child;
495 iface->iter_n_children = date_time_list_iter_n_children;
496 iface->iter_nth_child = date_time_list_iter_nth_child;
497 iface->iter_parent = date_time_list_iter_parent;
498 }
499
500 EDateTimeList *
e_date_time_list_new(void)501 e_date_time_list_new (void)
502 {
503 return g_object_new (E_TYPE_DATE_TIME_LIST, NULL);
504 }
505
506 ICalTime *
e_date_time_list_get_date_time(EDateTimeList * date_time_list,GtkTreeIter * iter)507 e_date_time_list_get_date_time (EDateTimeList *date_time_list,
508 GtkTreeIter *iter)
509 {
510 g_return_val_if_fail (IS_VALID_ITER (date_time_list, iter), NULL);
511
512 return G_LIST (iter->user_data)->data;
513 }
514
515 void
e_date_time_list_set_date_time(EDateTimeList * date_time_list,GtkTreeIter * iter,const ICalTime * itt)516 e_date_time_list_set_date_time (EDateTimeList *date_time_list,
517 GtkTreeIter *iter,
518 const ICalTime *itt)
519 {
520 ICalTime *itt_old;
521
522 g_return_if_fail (IS_VALID_ITER (date_time_list, iter));
523
524 itt_old = G_LIST (iter->user_data)->data;
525 g_clear_object (&itt_old);
526 G_LIST (iter->user_data)->data = i_cal_time_clone (itt);
527 row_updated (date_time_list,
528 g_list_position (date_time_list->priv->list, G_LIST (iter->user_data)));
529 }
530
531 gboolean
e_date_time_list_get_use_24_hour_format(EDateTimeList * date_time_list)532 e_date_time_list_get_use_24_hour_format (EDateTimeList *date_time_list)
533 {
534 g_return_val_if_fail (E_IS_DATE_TIME_LIST (date_time_list), FALSE);
535
536 return date_time_list->priv->use_24_hour_format;
537 }
538
539 void
e_date_time_list_set_use_24_hour_format(EDateTimeList * date_time_list,gboolean use_24_hour_format)540 e_date_time_list_set_use_24_hour_format (EDateTimeList *date_time_list,
541 gboolean use_24_hour_format)
542 {
543 g_return_if_fail (E_IS_DATE_TIME_LIST (date_time_list));
544
545 if (date_time_list->priv->use_24_hour_format == use_24_hour_format)
546 return;
547
548 date_time_list->priv->use_24_hour_format = use_24_hour_format;
549
550 g_object_notify (G_OBJECT (date_time_list), "use-24-hour-format");
551 }
552
553 ICalTimezone *
e_date_time_list_get_timezone(EDateTimeList * date_time_list)554 e_date_time_list_get_timezone (EDateTimeList *date_time_list)
555 {
556 g_return_val_if_fail (E_IS_DATE_TIME_LIST (date_time_list), NULL);
557
558 return date_time_list->priv->zone;
559 }
560
561 void
e_date_time_list_set_timezone(EDateTimeList * date_time_list,const ICalTimezone * zone)562 e_date_time_list_set_timezone (EDateTimeList *date_time_list,
563 const ICalTimezone *zone)
564 {
565 g_return_if_fail (E_IS_DATE_TIME_LIST (date_time_list));
566
567 if (date_time_list->priv->zone == zone)
568 return;
569
570 g_clear_object (&date_time_list->priv->zone);
571 if (zone)
572 date_time_list->priv->zone = g_object_ref ((ICalTimezone *) zone);
573
574 g_object_notify (G_OBJECT (date_time_list), "timezone");
575 }
576
577 void
e_date_time_list_append(EDateTimeList * date_time_list,GtkTreeIter * iter,const ICalTime * itt)578 e_date_time_list_append (EDateTimeList *date_time_list,
579 GtkTreeIter *iter,
580 const ICalTime *itt)
581 {
582 g_return_if_fail (itt != NULL);
583 g_return_if_fail (i_cal_time_is_valid_time ((ICalTime *) itt));
584
585 if (g_list_find_custom (date_time_list->priv->list, itt, (GCompareFunc) compare_datetime) == NULL) {
586 date_time_list->priv->list = g_list_append (date_time_list->priv->list, i_cal_time_clone (itt));
587 row_added (date_time_list, g_list_length (date_time_list->priv->list) - 1);
588 }
589
590 if (iter) {
591 iter->user_data = g_list_last (date_time_list->priv->list);
592 iter->stamp = date_time_list->priv->stamp;
593 }
594 }
595
596 void
e_date_time_list_remove(EDateTimeList * date_time_list,GtkTreeIter * iter)597 e_date_time_list_remove (EDateTimeList *date_time_list,
598 GtkTreeIter *iter)
599 {
600 ICalTime *itt;
601 gint n;
602
603 g_return_if_fail (IS_VALID_ITER (date_time_list, iter));
604
605 n = g_list_position (date_time_list->priv->list, G_LIST (iter->user_data));
606 itt = G_LIST (iter->user_data)->data;
607 g_clear_object (&itt);
608 date_time_list->priv->list = g_list_delete_link (
609 date_time_list->priv->list, G_LIST (iter->user_data));
610 row_deleted (date_time_list, n);
611 }
612
613 void
e_date_time_list_clear(EDateTimeList * date_time_list)614 e_date_time_list_clear (EDateTimeList *date_time_list)
615 {
616 all_rows_deleted (date_time_list);
617
618 g_list_free_full (date_time_list->priv->list, g_object_unref);
619 date_time_list->priv->list = NULL;
620 }
621