I#27 - [IMAPx] Ignore DavMail's CR/LF in BODYSTRUCTURE response
[evolution-data-server.git] / src / libedataserverui / e-reminders-widget.c
blob7b42c2d95d6a7c7fe6137d54f988ab0ce19386da
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2018 Red Hat, Inc. (www.redhat.com)
5 * This library 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.
9 * This library 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 Lesser General Public License
12 * for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * SECTION: e-reminders-widget
20 * @include: libedataserverui/libedataserverui.h
21 * @short_description: An #ERemindersWidget to work with past reminders
23 * The #ERemindersWidget is a widget which does common tasks on past reminders
24 * provided by #EReminderWatcher. The owner should connect to the "changed" signal
25 * to be notified on any changes, including when the list of past reminders
26 * is either expanded or shrunk, which usually causes the dialog with this
27 * widget to be shown or hidden.
29 * The widget itself is an #EExtensible.
31 * The widget does not listen to #EReminderWatcher::triggered signal.
32 **/
34 #include "evolution-data-server-config.h"
36 #include <glib/gi18n-lib.h>
38 #include "libedataserver/libedataserver.h"
39 #include "libecal/libecal.h"
41 #include "libedataserverui-private.h"
43 #include "e-reminders-widget.h"
45 #define MAX_CUSTOM_SNOOZE_VALUES 7
47 struct _ERemindersWidgetPrivate {
48 EReminderWatcher *watcher;
49 GSettings *settings;
50 gboolean is_empty;
52 GtkTreeView *tree_view;
53 GtkWidget *dismiss_button;
54 GtkWidget *dismiss_all_button;
55 GtkWidget *snooze_combo;
56 GtkWidget *snooze_button;
58 GtkWidget *add_snooze_popover;
59 GtkWidget *add_snooze_days_spin;
60 GtkWidget *add_snooze_hours_spin;
61 GtkWidget *add_snooze_minutes_spin;
62 GtkWidget *add_snooze_add_button;
64 GtkInfoBar *info_bar;
66 GCancellable *cancellable;
67 guint refresh_idle_id;
69 gboolean is_mapped;
70 guint overdue_update_id;
71 gint64 last_overdue_update; /* in seconds */
72 gboolean overdue_update_rounded;
74 gboolean updating_snooze_combo;
75 gint last_selected_snooze_minutes; /* not the same as the saved value in GSettings */
78 enum {
79 CHANGED,
80 ACTIVATED,
81 LAST_SIGNAL
84 enum {
85 PROP_0,
86 PROP_WATCHER,
87 PROP_EMPTY
90 static guint signals[LAST_SIGNAL];
92 G_DEFINE_TYPE_WITH_CODE (ERemindersWidget, e_reminders_widget, GTK_TYPE_GRID,
93 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
95 static gboolean
96 reminders_widget_snooze_combo_separator_cb (GtkTreeModel *model,
97 GtkTreeIter *iter,
98 gpointer user_data)
100 gint32 minutes = -1;
102 if (!model || !iter)
103 return FALSE;
105 gtk_tree_model_get (model, iter, 1, &minutes, -1);
107 return !minutes;
110 static GtkWidget *
111 reminders_widget_new_snooze_combo (void)
113 GtkWidget *combo;
114 GtkListStore *list_store;
115 GtkCellRenderer *renderer;
117 list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
119 combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list_store));
121 g_object_unref (list_store);
123 renderer = gtk_cell_renderer_text_new ();
124 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
125 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, "text", 0, NULL);
127 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
128 reminders_widget_snooze_combo_separator_cb, NULL, NULL);
130 return combo;
133 static void
134 reminders_widget_fill_snooze_combo (ERemindersWidget *reminders,
135 gint preselect_minutes)
137 const gint predefined_minutes[] = {
143 24 * 60,
144 7 * 24 * 60
146 gint ii, last_sel = -1;
147 GtkComboBox *combo;
148 GtkListStore *list_store;
149 GtkTreeIter iter, tosel_iter;
150 GVariant *variant;
151 gboolean tosel_set = FALSE;
152 gboolean any_stored_added = FALSE;
154 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
156 reminders->priv->updating_snooze_combo = TRUE;
158 combo = GTK_COMBO_BOX (reminders->priv->snooze_combo);
159 list_store = GTK_LIST_STORE (gtk_combo_box_get_model (combo));
161 if (gtk_combo_box_get_active_iter (combo, &iter)) {
162 gtk_tree_model_get (GTK_TREE_MODEL (list_store), &iter, 1, &last_sel, -1);
165 gtk_list_store_clear (list_store);
167 #define add_minutes(_minutes) G_STMT_START { \
168 gint32 minutes = (_minutes); \
169 gchar *text; \
171 text = e_cal_util_seconds_to_string (minutes * 60); \
172 gtk_list_store_append (list_store, &iter); \
173 gtk_list_store_set (list_store, &iter, \
174 0, text, \
175 1, minutes, \
176 -1); \
177 g_free (text); \
179 if (preselect_minutes > 0 && preselect_minutes == minutes) { \
180 tosel_set = TRUE; \
181 tosel_iter = iter; \
182 last_sel = -1; \
183 } else if (last_sel > 0 && minutes == last_sel) { \
184 tosel_set = TRUE; \
185 tosel_iter = iter; \
187 } G_STMT_END
189 /* Custom user values first */
190 variant = g_settings_get_value (reminders->priv->settings, "notify-custom-snooze-minutes");
191 if (variant) {
192 const gint32 *stored;
193 gsize nstored = 0;
195 stored = g_variant_get_fixed_array (variant, &nstored, sizeof (gint32));
196 if (stored && nstored > 0) {
197 for (ii = 0; ii < nstored; ii++) {
198 if (stored[ii] > 0) {
199 add_minutes (stored[ii]);
200 any_stored_added = TRUE;
205 g_variant_unref (variant);
207 if (any_stored_added) {
208 /* Separator */
209 gtk_list_store_append (list_store, &iter);
210 gtk_list_store_set (list_store, &iter, 1, 0, -1);
214 for (ii = 0; ii < G_N_ELEMENTS (predefined_minutes); ii++) {
215 add_minutes (predefined_minutes[ii]);
218 #undef add_minutes
220 /* Separator */
221 gtk_list_store_append (list_store, &iter);
222 gtk_list_store_set (list_store, &iter, 1, 0, -1);
224 gtk_list_store_append (list_store, &iter);
225 gtk_list_store_set (list_store, &iter, 0, _("Add custom timeā€¦"), 1, -1, -1);
227 if (any_stored_added) {
228 gtk_list_store_append (list_store, &iter);
229 gtk_list_store_set (list_store, &iter, 0, _("Clear custom times"), 1, -2, -1);
232 reminders->priv->updating_snooze_combo = FALSE;
234 if (tosel_set)
235 gtk_combo_box_set_active_iter (combo, &tosel_iter);
236 else
237 gtk_combo_box_set_active (combo, 0);
240 static void
241 reminders_widget_custom_snooze_minutes_changed_cb (GSettings *settings,
242 const gchar *key,
243 gpointer user_data)
245 ERemindersWidget *reminders = user_data;
247 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
249 reminders_widget_fill_snooze_combo (reminders, -1);
252 static void
253 reminders_get_reminder_markups (ERemindersWidget *reminders,
254 const EReminderData *rd,
255 gchar **out_overdue_markup,
256 gchar **out_description_markup)
258 g_return_if_fail (rd != NULL);
260 if (out_overdue_markup) {
261 gint64 diff;
262 gboolean in_future;
263 gchar *time_str;
265 diff = (g_get_real_time () / G_USEC_PER_SEC) - ((gint64) rd->instance.occur_start);
266 in_future = diff < 0;
267 if (in_future)
268 diff = (-1) * diff;
270 /* in minutes */
271 if (in_future && (diff % 60) > 0)
272 diff += 60;
274 diff = diff / 60;
276 if (!diff) {
277 time_str = g_strdup (C_("overdue", "now"));
278 } else if (diff < 60) {
279 time_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d minute", "%d minutes", diff), (gint) diff);
280 } else if (diff < 24 * 60) {
281 gint hours = diff / 60;
283 time_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d hour", "%d hours", hours), hours);
284 } else if (diff < 7 * 24 * 60) {
285 gint days = diff / (24 * 60);
287 time_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d day", "%d days", days), days);
288 } else if (diff < 54 * 7 * 24 * 60) {
289 gint weeks = diff / (7 * 24 * 60);
291 time_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d week", "%d weeks", weeks), weeks);
292 } else {
293 gint years = diff / (366 * 24 * 60);
295 time_str = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%d year", "%d years", years), years);
298 if (in_future || !diff) {
299 *out_overdue_markup = g_markup_printf_escaped ("<span size=\"x-small\">%s</span>", time_str);
300 } else {
301 *out_overdue_markup = g_markup_printf_escaped ("<span size=\"x-small\">%s\n%s</span>", time_str, C_("overdue", "overdue"));
304 g_free (time_str);
307 if (out_description_markup) {
308 *out_description_markup = e_reminder_watcher_describe_data (reminders->priv->watcher, rd, E_REMINDER_WATCHER_DESCRIBE_FLAG_MARKUP);
312 static void
313 reminders_widget_overdue_update (ERemindersWidget *reminders)
315 GtkListStore *list_store;
316 GtkTreeModel *model;
317 GtkTreeIter iter;
318 gboolean any_changed = FALSE;
320 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
322 model = gtk_tree_view_get_model (reminders->priv->tree_view);
323 if (!model)
324 return;
326 if (!gtk_tree_model_get_iter_first (model, &iter))
327 return;
329 list_store = GTK_LIST_STORE (model);
331 do {
332 EReminderData *rd = NULL;
334 gtk_tree_model_get (model, &iter,
335 E_REMINDERS_WIDGET_COLUMN_REMINDER_DATA, &rd,
336 -1);
338 if (rd) {
339 gchar *overdue_markup = NULL;
341 reminders_get_reminder_markups (reminders, rd, &overdue_markup, NULL);
342 if (overdue_markup) {
343 gchar *current = NULL;
345 gtk_tree_model_get (model, &iter,
346 E_REMINDERS_WIDGET_COLUMN_OVERDUE, &current,
347 -1);
349 if (g_strcmp0 (current, overdue_markup) != 0) {
350 gtk_list_store_set (list_store, &iter,
351 E_REMINDERS_WIDGET_COLUMN_OVERDUE, overdue_markup,
352 -1);
353 any_changed = TRUE;
356 g_free (overdue_markup);
357 g_free (current);
360 e_reminder_data_free (rd);
362 } while (gtk_tree_model_iter_next (model, &iter));
364 if (any_changed) {
365 GtkTreeViewColumn *column;
367 column = gtk_tree_view_get_column (reminders->priv->tree_view, 0);
368 if (column)
369 gtk_tree_view_column_queue_resize (column);
373 static gboolean
374 reminders_widget_overdue_update_cb (gpointer user_data)
376 ERemindersWidget *reminders = user_data;
377 gint64 now_seconds, last_update;
379 if (g_source_is_destroyed (g_main_current_source ()))
380 return FALSE;
382 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), FALSE);
384 reminders_widget_overdue_update (reminders);
386 now_seconds = g_get_real_time () / G_USEC_PER_SEC;
387 last_update = reminders->priv->last_overdue_update;
388 reminders->priv->last_overdue_update = now_seconds;
390 if (!last_update || (
391 (now_seconds - last_update) % 60 > 2 &&
392 (now_seconds - last_update) % 60 < 58)) {
393 gint until_minute = 60 - (now_seconds % 60);
395 if (until_minute >= 59) {
396 reminders->priv->overdue_update_rounded = TRUE;
397 until_minute = 60;
398 } else {
399 reminders->priv->overdue_update_rounded = FALSE;
402 reminders->priv->overdue_update_id = g_timeout_add_seconds (until_minute,
403 reminders_widget_overdue_update_cb, reminders);
405 return FALSE;
406 } else if (!reminders->priv->overdue_update_rounded) {
407 reminders->priv->overdue_update_rounded = TRUE;
408 reminders->priv->overdue_update_id = g_timeout_add_seconds (60,
409 reminders_widget_overdue_update_cb, reminders);
411 return FALSE;
414 return TRUE;
417 static void
418 reminders_widget_maybe_schedule_overdue_update (ERemindersWidget *reminders)
420 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
422 if (reminders->priv->is_empty || !reminders->priv->is_mapped) {
423 if (reminders->priv->overdue_update_id) {
424 g_source_remove (reminders->priv->overdue_update_id);
425 reminders->priv->overdue_update_id = 0;
427 } else if (!reminders->priv->overdue_update_id) {
428 gint until_minute = 60 - ((g_get_real_time () / G_USEC_PER_SEC) % 60);
430 reminders->priv->last_overdue_update = g_get_real_time () / G_USEC_PER_SEC;
432 if (until_minute >= 59) {
433 reminders->priv->overdue_update_rounded = TRUE;
434 until_minute = 60;
435 } else {
436 reminders->priv->overdue_update_rounded = FALSE;
439 reminders->priv->overdue_update_id = g_timeout_add_seconds (until_minute,
440 reminders_widget_overdue_update_cb, reminders);
444 static void
445 reminders_widget_map (GtkWidget *widget)
447 ERemindersWidget *reminders;
449 g_return_if_fail (E_IS_REMINDERS_WIDGET (widget));
451 /* Chain up to parent's method. */
452 GTK_WIDGET_CLASS (e_reminders_widget_parent_class)->map (widget);
454 reminders = E_REMINDERS_WIDGET (widget);
455 reminders->priv->is_mapped = TRUE;
457 reminders_widget_maybe_schedule_overdue_update (reminders);
461 static void
462 reminders_widget_unmap (GtkWidget *widget)
464 ERemindersWidget *reminders;
466 g_return_if_fail (E_IS_REMINDERS_WIDGET (widget));
468 /* Chain up to parent's method. */
469 GTK_WIDGET_CLASS (e_reminders_widget_parent_class)->unmap (widget);
471 reminders = E_REMINDERS_WIDGET (widget);
472 reminders->priv->is_mapped = FALSE;
474 reminders_widget_maybe_schedule_overdue_update (reminders);
477 static gint
478 reminders_sort_by_occur (gconstpointer ptr1,
479 gconstpointer ptr2)
481 const EReminderData *rd1 = ptr1, *rd2 = ptr2;
482 gint cmp;
484 if (!rd1 || !rd2)
485 return rd1 == rd2 ? 0 : rd1 ? 1 : -1;
487 if (rd1->instance.occur_start != rd2->instance.occur_start)
488 return rd1->instance.occur_start < rd2->instance.occur_start ? -1 : 1;
490 if (rd1->instance.trigger != rd2->instance.trigger)
491 return rd1->instance.trigger < rd2->instance.trigger ? -1 : 1;
493 cmp = g_strcmp0 (rd1->source_uid, rd2->source_uid);
494 if (!cmp)
495 cmp = g_strcmp0 (rd1->instance.auid, rd2->instance.auid);
497 return cmp;
500 static void
501 reminders_widget_set_is_empty (ERemindersWidget *reminders,
502 gboolean is_empty)
504 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
506 if (!is_empty == !reminders->priv->is_empty)
507 return;
509 reminders->priv->is_empty = is_empty;
511 g_object_notify (G_OBJECT (reminders), "empty");
513 reminders_widget_maybe_schedule_overdue_update (reminders);
516 static gint
517 reminders_widget_invert_tree_path_compare (gconstpointer ptr1,
518 gconstpointer ptr2)
520 return (-1) * gtk_tree_path_compare (ptr1, ptr2);
523 static void
524 reminders_widget_select_one_of (ERemindersWidget *reminders,
525 GList **inout_previous_paths) /* GtkTreePath * */
527 GList *link;
528 guint len;
529 gint to_select = -1;
530 gint n_rows;
532 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
534 if (!inout_previous_paths || !*inout_previous_paths)
535 return;
537 n_rows = gtk_tree_model_iter_n_children (gtk_tree_view_get_model (reminders->priv->tree_view), NULL);
538 if (n_rows <= 0)
539 return;
541 *inout_previous_paths = g_list_sort (*inout_previous_paths, reminders_widget_invert_tree_path_compare);
543 len = g_list_length (*inout_previous_paths);
545 for (link = *inout_previous_paths; link && to_select == -1; link = g_list_next (link), len--) {
546 GtkTreePath *path = link->data;
547 gint *indices, index;
549 if (!path || gtk_tree_path_get_depth (path) != 1)
550 continue;
552 indices = gtk_tree_path_get_indices (path);
553 if (!indices)
554 continue;
556 index = indices[0] - len + 1;
558 if (index >= n_rows)
559 to_select = n_rows - 1;
560 else
561 to_select = index;
564 if (to_select >= 0 && to_select < n_rows) {
565 GtkTreePath *path;
567 path = gtk_tree_path_new_from_indices (to_select, -1);
568 if (path) {
569 gtk_tree_selection_select_path (gtk_tree_view_get_selection (reminders->priv->tree_view), path);
570 gtk_tree_path_free (path);
575 static gboolean
576 reminders_widget_refresh_content_cb (gpointer user_data)
578 ERemindersWidget *reminders = user_data;
579 GList *previous_paths;
580 GSList *past;
581 GtkTreeModel *model;
582 GtkTreeSelection *selection;
583 GtkListStore *list_store;
585 if (g_source_is_destroyed (g_main_current_source ()))
586 return FALSE;
588 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), FALSE);
590 reminders->priv->refresh_idle_id = 0;
592 model = gtk_tree_view_get_model (reminders->priv->tree_view);
593 if (!model)
594 return FALSE;
596 selection = gtk_tree_view_get_selection (reminders->priv->tree_view);
597 previous_paths = gtk_tree_selection_get_selected_rows (selection, NULL);
598 list_store = GTK_LIST_STORE (model);
600 g_object_ref (model);
601 gtk_tree_view_set_model (reminders->priv->tree_view, NULL);
603 gtk_list_store_clear (list_store);
605 past = e_reminder_watcher_dup_past (reminders->priv->watcher);
606 if (past) {
607 GSList *link;
608 GtkTreeIter iter;
610 past = g_slist_sort (past, reminders_sort_by_occur);
611 for (link = past; link; link = g_slist_next (link)) {
612 const EReminderData *rd = link->data;
613 gchar *overdue = NULL, *description = NULL;
615 if (!rd || !rd->component)
616 continue;
618 reminders_get_reminder_markups (reminders, rd, &overdue, &description);
620 gtk_list_store_append (list_store, &iter);
621 gtk_list_store_set (list_store, &iter,
622 E_REMINDERS_WIDGET_COLUMN_OVERDUE, overdue,
623 E_REMINDERS_WIDGET_COLUMN_DESCRIPTION, description,
624 E_REMINDERS_WIDGET_COLUMN_REMINDER_DATA, rd,
625 -1);
627 g_free (description);
628 g_free (overdue);
632 gtk_tree_view_set_model (reminders->priv->tree_view, model);
633 g_object_unref (model);
635 reminders_widget_set_is_empty (reminders, !past);
637 if (past) {
638 GtkTreeViewColumn *column;
640 column = gtk_tree_view_get_column (reminders->priv->tree_view, 0);
641 if (column)
642 gtk_tree_view_column_queue_resize (column);
644 reminders_widget_select_one_of (reminders, &previous_paths);
647 g_list_free_full (previous_paths, (GDestroyNotify) gtk_tree_path_free);
648 g_slist_free_full (past, e_reminder_data_free);
650 g_signal_emit (reminders, signals[CHANGED], 0, NULL);
652 return FALSE;
655 static void
656 reminders_widget_schedule_content_refresh (ERemindersWidget *reminders)
658 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
660 if (!reminders->priv->refresh_idle_id) {
661 reminders->priv->refresh_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
662 reminders_widget_refresh_content_cb, reminders, NULL);
666 static void
667 reminders_widget_watcher_changed_cb (EReminderWatcher *watcher,
668 gpointer user_data)
670 ERemindersWidget *reminders = user_data;
672 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
674 reminders_widget_schedule_content_refresh (reminders);
677 static void
678 reminders_widget_gather_selected_cb (GtkTreeModel *model,
679 GtkTreePath *path,
680 GtkTreeIter *iter,
681 gpointer user_data)
683 GSList **inout_selected = user_data;
684 EReminderData *rd = NULL;
686 g_return_if_fail (inout_selected != NULL);
688 gtk_tree_model_get (model, iter, E_REMINDERS_WIDGET_COLUMN_REMINDER_DATA, &rd, -1);
690 if (rd)
691 *inout_selected = g_slist_prepend (*inout_selected, rd);
694 static void
695 reminders_widget_do_dismiss_cb (ERemindersWidget *reminders,
696 const EReminderData *rd,
697 GString *gathered_errors,
698 GCancellable *cancellable,
699 gpointer user_data)
701 GError *local_error = NULL;
703 if (g_cancellable_is_cancelled (cancellable))
704 return;
706 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
707 g_return_if_fail (rd != NULL);
709 if (!e_reminder_watcher_dismiss_sync (reminders->priv->watcher, rd, cancellable, &local_error) && local_error && gathered_errors &&
710 !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
711 if (gathered_errors->len)
712 g_string_append_c (gathered_errors, '\n');
713 g_string_append (gathered_errors, local_error->message);
716 g_clear_error (&local_error);
719 typedef void (* ForeachSelectedSyncFunc) (ERemindersWidget *reminders,
720 const EReminderData *rd,
721 GString *gathered_errors,
722 GCancellable *cancellable,
723 gpointer user_data);
725 typedef struct _ForeachSelectedData {
726 GSList *selected; /* EReminderData * */
727 ForeachSelectedSyncFunc sync_func;
728 gpointer user_data;
729 GDestroyNotify user_data_destroy;
730 gchar *error_prefix;
731 } ForeachSelectedData;
733 static void
734 foreach_selected_data_free (gpointer ptr)
736 ForeachSelectedData *fsd = ptr;
738 if (fsd) {
739 g_slist_free_full (fsd->selected, e_reminder_data_free);
740 if (fsd->user_data_destroy)
741 fsd->user_data_destroy (fsd->user_data);
742 g_free (fsd->error_prefix);
743 g_free (fsd);
747 static void
748 reminders_widget_foreach_selected_thread (GTask *task,
749 gpointer source_object,
750 gpointer task_data,
751 GCancellable *cancellable)
753 ForeachSelectedData *fsd = task_data;
754 GString *gathered_errors;
755 GSList *link;
757 g_return_if_fail (fsd != NULL);
758 g_return_if_fail (fsd->selected != NULL);
759 g_return_if_fail (fsd->sync_func != NULL);
761 if (g_cancellable_is_cancelled (cancellable))
762 return;
764 gathered_errors = g_string_new ("");
766 for (link = fsd->selected; link && !g_cancellable_is_cancelled (cancellable); link = g_slist_next (link)) {
767 const EReminderData *rd = link->data;
769 fsd->sync_func (source_object, rd, gathered_errors, cancellable, fsd->user_data);
772 if (gathered_errors->len) {
773 if (fsd->error_prefix) {
774 g_string_prepend_c (gathered_errors, '\n');
775 g_string_prepend (gathered_errors, fsd->error_prefix);
778 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", gathered_errors->str);
779 } else {
780 g_task_return_boolean (task, TRUE);
783 g_string_free (gathered_errors, TRUE);
786 static void
787 reminders_widget_foreach_selected_done_cb (GObject *source_object,
788 GAsyncResult *result,
789 gpointer user_data)
791 ERemindersWidget *reminders;
792 GError *local_error = NULL;
794 g_return_if_fail (E_IS_REMINDERS_WIDGET (source_object));
796 reminders = E_REMINDERS_WIDGET (source_object);
797 g_return_if_fail (g_task_is_valid (result, reminders));
799 if (!g_task_propagate_boolean (G_TASK (result), &local_error) && local_error) {
800 e_reminders_widget_report_error (reminders, NULL, local_error);
803 g_clear_error (&local_error);
806 static void
807 reminders_widget_foreach_selected (ERemindersWidget *reminders,
808 ForeachSelectedSyncFunc sync_func,
809 gpointer user_data,
810 GDestroyNotify user_data_destroy,
811 const gchar *error_prefix)
813 GtkTreeSelection *selection;
814 GSList *selected = NULL; /* EReminderData * */
815 GTask *task;
817 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
818 g_return_if_fail (sync_func != NULL);
820 selection = gtk_tree_view_get_selection (reminders->priv->tree_view);
821 gtk_tree_selection_selected_foreach (selection, reminders_widget_gather_selected_cb, &selected);
823 if (selected) {
824 ForeachSelectedData *fsd;
826 fsd = g_new0 (ForeachSelectedData, 1);
827 fsd->selected = selected; /* Takes ownership */
828 fsd->sync_func = sync_func;
829 fsd->user_data = user_data;
830 fsd->user_data_destroy = user_data_destroy;
831 fsd->error_prefix = g_strdup (error_prefix);
833 task = g_task_new (reminders, reminders->priv->cancellable, reminders_widget_foreach_selected_done_cb, NULL);
834 g_task_set_task_data (task, fsd, foreach_selected_data_free);
835 g_task_set_check_cancellable (task, FALSE);
836 g_task_run_in_thread (task, reminders_widget_foreach_selected_thread);
837 g_object_unref (task);
841 static void
842 reminders_widget_row_activated_cb (GtkTreeView *tree_view,
843 GtkTreePath *path,
844 GtkTreeViewColumn *column,
845 gpointer user_data)
847 ERemindersWidget *reminders = user_data;
848 GtkTreeModel *model;
849 GtkTreeIter iter;
851 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
853 if (!path)
854 return;
856 model = gtk_tree_view_get_model (reminders->priv->tree_view);
857 if (gtk_tree_model_get_iter (model, &iter, path)) {
858 EReminderData *rd = NULL;
860 gtk_tree_model_get (model, &iter,
861 E_REMINDERS_WIDGET_COLUMN_REMINDER_DATA, &rd,
862 -1);
864 if (rd) {
865 gboolean result = FALSE;
867 g_signal_emit (reminders, signals[ACTIVATED], 0, rd, &result);
869 if (!result) {
870 const gchar *scheme = NULL;
871 const gchar *comp_uid = NULL;
873 e_cal_component_get_uid (rd->component, &comp_uid);
875 switch (e_cal_component_get_vtype (rd->component)) {
876 case E_CAL_COMPONENT_EVENT:
877 scheme = "calendar:";
878 break;
879 case E_CAL_COMPONENT_TODO:
880 scheme = "task:";
881 break;
882 case E_CAL_COMPONENT_JOURNAL:
883 scheme = "memo:";
884 break;
885 default:
886 break;
889 if (scheme && comp_uid && rd->source_uid) {
890 GString *uri;
891 gchar *tmp;
892 GError *error = NULL;
894 uri = g_string_sized_new (128);
895 g_string_append (uri, scheme);
896 g_string_append (uri, "///?");
898 tmp = g_uri_escape_string (rd->source_uid, NULL, TRUE);
899 g_string_append (uri, "source-uid=");
900 g_string_append (uri, tmp);
901 g_free (tmp);
903 g_string_append (uri, "&");
905 tmp = g_uri_escape_string (comp_uid, NULL, TRUE);
906 g_string_append (uri, "comp-uid=");
907 g_string_append (uri, tmp);
908 g_free (tmp);
910 if (!g_app_info_launch_default_for_uri (uri->str, NULL, &error) &&
911 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
912 gchar *prefix = g_strdup_printf (_("Failed to launch URI ā€œ%sā€:"), uri->str);
913 e_reminders_widget_report_error (reminders, prefix, error);
914 g_free (prefix);
917 g_string_free (uri, TRUE);
918 g_clear_error (&error);
922 e_reminder_data_free (rd);
927 static void
928 reminders_widget_selection_changed_cb (GtkTreeSelection *selection,
929 gpointer user_data)
931 ERemindersWidget *reminders = user_data;
932 gint nselected;
934 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
935 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
937 nselected = gtk_tree_selection_count_selected_rows (selection);
938 gtk_widget_set_sensitive (reminders->priv->snooze_combo, nselected > 0);
939 gtk_widget_set_sensitive (reminders->priv->snooze_button, nselected > 0);
940 gtk_widget_set_sensitive (reminders->priv->dismiss_button, nselected > 0);
943 static void
944 reminders_widget_dismiss_button_clicked_cb (GtkButton *button,
945 gpointer user_data)
947 ERemindersWidget *reminders = user_data;
949 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
951 g_signal_handlers_block_by_func (reminders->priv->watcher, reminders_widget_watcher_changed_cb, reminders);
953 reminders_widget_foreach_selected (reminders, reminders_widget_do_dismiss_cb, NULL, NULL, _("Failed to dismiss reminder:"));
955 g_signal_handlers_unblock_by_func (reminders->priv->watcher, reminders_widget_watcher_changed_cb, reminders);
957 reminders_widget_watcher_changed_cb (reminders->priv->watcher, reminders);
960 static void
961 reminders_widget_dismiss_all_done_cb (GObject *source_object,
962 GAsyncResult *result,
963 gpointer user_data)
965 ERemindersWidget *reminders = user_data;
966 GError *local_error = NULL;
968 g_return_if_fail (E_IS_REMINDER_WATCHER (source_object));
970 if (!e_reminder_watcher_dismiss_all_finish (reminders->priv->watcher, result, &local_error) &&
971 !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
972 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
974 e_reminders_widget_report_error (reminders, _("Failed to dismiss all:"), local_error);
977 g_clear_error (&local_error);
980 static void
981 reminders_widget_dismiss_all_button_clicked_cb (GtkButton *button,
982 gpointer user_data)
984 ERemindersWidget *reminders = user_data;
986 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
988 e_reminder_watcher_dismiss_all (reminders->priv->watcher, reminders->priv->cancellable,
989 reminders_widget_dismiss_all_done_cb, reminders);
992 static void
993 reminders_widget_add_snooze_add_button_clicked_cb (GtkButton *button,
994 gpointer user_data)
996 ERemindersWidget *reminders = user_data;
997 GtkTreeModel *model;
998 GtkTreeIter iter;
999 gboolean found = FALSE;
1000 gint new_minutes;
1002 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1004 new_minutes =
1005 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_minutes_spin)) +
1006 (60 * gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_hours_spin))) +
1007 (24 * 60 * gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_days_spin)));
1008 g_return_if_fail (new_minutes > 0);
1010 gtk_widget_hide (reminders->priv->add_snooze_popover);
1012 model = gtk_combo_box_get_model (GTK_COMBO_BOX (reminders->priv->snooze_combo));
1013 g_return_if_fail (model != NULL);
1015 if (gtk_tree_model_get_iter_first (model, &iter)) {
1016 do {
1017 gint minutes = 0;
1019 gtk_tree_model_get (model, &iter, 1, &minutes, -1);
1021 if (minutes == new_minutes) {
1022 found = TRUE;
1023 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (reminders->priv->snooze_combo), &iter);
1024 break;
1026 } while (gtk_tree_model_iter_next (model, &iter));
1029 if (!found) {
1030 GVariant *variant;
1031 gint32 array[MAX_CUSTOM_SNOOZE_VALUES] = { 0 }, narray = 0, ii;
1033 variant = g_settings_get_value (reminders->priv->settings, "notify-custom-snooze-minutes");
1034 if (variant) {
1035 const gint32 *stored;
1036 gsize nstored = 0;
1038 stored = g_variant_get_fixed_array (variant, &nstored, sizeof (gint32));
1039 if (stored && nstored > 0) {
1040 /* Skip the oldest, when too many stored */
1041 for (ii = nstored >= MAX_CUSTOM_SNOOZE_VALUES ? 1 : 0; ii < MAX_CUSTOM_SNOOZE_VALUES && ii < nstored; ii++) {
1042 array[narray] = stored[ii];
1043 narray++;
1047 g_variant_unref (variant);
1050 /* Add the new at the end of the array */
1051 array[narray] = new_minutes;
1052 narray++;
1054 variant = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32, array, narray, sizeof (gint32));
1055 g_settings_set_value (reminders->priv->settings, "notify-custom-snooze-minutes", variant);
1057 reminders_widget_fill_snooze_combo (reminders, new_minutes);
1061 static void
1062 reminders_widget_add_snooze_update_sensitize_cb (GtkSpinButton *spin,
1063 gpointer user_data)
1065 ERemindersWidget *reminders = user_data;
1067 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1069 gtk_widget_set_sensitive (reminders->priv->add_snooze_add_button,
1070 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_minutes_spin)) +
1071 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_hours_spin)) +
1072 gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (reminders->priv->add_snooze_days_spin)) > 0);
1075 static void
1076 reminders_widget_snooze_add_custom (ERemindersWidget *reminders)
1078 GtkTreeIter iter;
1080 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1082 if (!reminders->priv->add_snooze_popover) {
1083 GtkWidget *widget;
1084 GtkBox *vbox, *box;
1086 reminders->priv->add_snooze_days_spin = gtk_spin_button_new_with_range (0.0, 366.0, 1.0);
1087 reminders->priv->add_snooze_hours_spin = gtk_spin_button_new_with_range (0.0, 23.0, 1.0);
1088 reminders->priv->add_snooze_minutes_spin = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
1090 g_object_set (G_OBJECT (reminders->priv->add_snooze_days_spin),
1091 "digits", 0,
1092 "numeric", TRUE,
1093 "snap-to-ticks", TRUE,
1094 NULL);
1096 g_object_set (G_OBJECT (reminders->priv->add_snooze_hours_spin),
1097 "digits", 0,
1098 "numeric", TRUE,
1099 "snap-to-ticks", TRUE,
1100 NULL);
1102 g_object_set (G_OBJECT (reminders->priv->add_snooze_minutes_spin),
1103 "digits", 0,
1104 "numeric", TRUE,
1105 "snap-to-ticks", TRUE,
1106 NULL);
1108 vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 2));
1110 widget = gtk_label_new (_("Set a custom snooze time for"));
1111 gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
1113 box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
1114 g_object_set (G_OBJECT (box),
1115 "halign", GTK_ALIGN_START,
1116 "hexpand", FALSE,
1117 "valign", GTK_ALIGN_CENTER,
1118 "vexpand", FALSE,
1119 NULL);
1121 gtk_box_pack_start (box, reminders->priv->add_snooze_days_spin, FALSE, FALSE, 4);
1122 /* Translators: this is part of: "Set a custom snooze time for [nnn] days [nnn] hours [nnn] minutes", where the text in "[]" means a separate widget */
1123 widget = gtk_label_new_with_mnemonic (C_("reminders-snooze", "da_ys"));
1124 gtk_label_set_mnemonic_widget (GTK_LABEL (widget), reminders->priv->add_snooze_days_spin);
1125 gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
1127 gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
1129 box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
1130 g_object_set (G_OBJECT (box),
1131 "halign", GTK_ALIGN_START,
1132 "hexpand", FALSE,
1133 "valign", GTK_ALIGN_CENTER,
1134 "vexpand", FALSE,
1135 NULL);
1137 gtk_box_pack_start (box, reminders->priv->add_snooze_hours_spin, FALSE, FALSE, 4);
1138 /* Translators: this is part of: "Set a custom snooze time for [nnn] days [nnn] hours [nnn] minutes", where the text in "[]" means a separate widget */
1139 widget = gtk_label_new_with_mnemonic (C_("reminders-snooze", "_hours"));
1140 gtk_label_set_mnemonic_widget (GTK_LABEL (widget), reminders->priv->add_snooze_hours_spin);
1141 gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
1143 gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
1145 box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
1146 g_object_set (G_OBJECT (box),
1147 "halign", GTK_ALIGN_START,
1148 "hexpand", FALSE,
1149 "valign", GTK_ALIGN_CENTER,
1150 "vexpand", FALSE,
1151 NULL);
1153 gtk_box_pack_start (box, reminders->priv->add_snooze_minutes_spin, FALSE, FALSE, 4);
1154 /* Translators: this is part of: "Set a custom snooze time for [nnn] days [nnn] hours [nnn] minutes", where the text in "[]" means a separate widget */
1155 widget = gtk_label_new_with_mnemonic (C_("reminders-snooze", "_minutes"));
1156 gtk_label_set_mnemonic_widget (GTK_LABEL (widget), reminders->priv->add_snooze_minutes_spin);
1157 gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
1159 gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
1161 reminders->priv->add_snooze_add_button = gtk_button_new_with_mnemonic (_("_Add Snooze time"));
1162 g_object_set (G_OBJECT (reminders->priv->add_snooze_add_button),
1163 "halign", GTK_ALIGN_CENTER,
1164 NULL);
1166 gtk_box_pack_start (vbox, reminders->priv->add_snooze_add_button, FALSE, FALSE, 0);
1168 gtk_widget_show_all (GTK_WIDGET (vbox));
1170 reminders->priv->add_snooze_popover = gtk_popover_new (GTK_WIDGET (reminders));
1171 gtk_popover_set_position (GTK_POPOVER (reminders->priv->add_snooze_popover), GTK_POS_BOTTOM);
1172 gtk_container_add (GTK_CONTAINER (reminders->priv->add_snooze_popover), GTK_WIDGET (vbox));
1173 gtk_container_set_border_width (GTK_CONTAINER (reminders->priv->add_snooze_popover), 6);
1175 g_signal_connect (reminders->priv->add_snooze_add_button, "clicked",
1176 G_CALLBACK (reminders_widget_add_snooze_add_button_clicked_cb), reminders);
1178 g_signal_connect (reminders->priv->add_snooze_days_spin, "value-changed",
1179 G_CALLBACK (reminders_widget_add_snooze_update_sensitize_cb), reminders);
1181 g_signal_connect (reminders->priv->add_snooze_hours_spin, "value-changed",
1182 G_CALLBACK (reminders_widget_add_snooze_update_sensitize_cb), reminders);
1184 g_signal_connect (reminders->priv->add_snooze_minutes_spin, "value-changed",
1185 G_CALLBACK (reminders_widget_add_snooze_update_sensitize_cb), reminders);
1187 reminders_widget_add_snooze_update_sensitize_cb (NULL, reminders);
1190 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (reminders->priv->snooze_combo), &iter)) {
1191 gint minutes = -1;
1193 gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (reminders->priv->snooze_combo)), &iter, 1, &minutes, -1);
1195 if (minutes > 0) {
1196 gtk_spin_button_set_value (GTK_SPIN_BUTTON (reminders->priv->add_snooze_minutes_spin), minutes % 60);
1198 minutes = minutes / 60;
1199 gtk_spin_button_set_value (GTK_SPIN_BUTTON (reminders->priv->add_snooze_hours_spin), minutes % 24);
1201 minutes = minutes / 24;
1202 gtk_spin_button_set_value (GTK_SPIN_BUTTON (reminders->priv->add_snooze_days_spin), minutes);
1206 gtk_widget_hide (reminders->priv->add_snooze_popover);
1207 gtk_popover_set_relative_to (GTK_POPOVER (reminders->priv->add_snooze_popover), reminders->priv->snooze_combo);
1208 gtk_widget_show (reminders->priv->add_snooze_popover);
1210 gtk_widget_grab_focus (reminders->priv->add_snooze_days_spin);
1213 static void
1214 reminders_widget_snooze_combo_changed_cb (GtkComboBox *combo,
1215 gpointer user_data)
1217 ERemindersWidget *reminders = user_data;
1218 GtkTreeIter iter;
1220 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1222 if (reminders->priv->updating_snooze_combo)
1223 return;
1225 if (gtk_combo_box_get_active_iter (combo, &iter)) {
1226 GtkTreeModel *model;
1227 gint minutes = -3;
1229 model = gtk_combo_box_get_model (combo);
1231 gtk_tree_model_get (model, &iter, 1, &minutes, -1);
1233 if (minutes > 0) {
1234 reminders->priv->last_selected_snooze_minutes = minutes;
1235 } else if (minutes == -1 || minutes == -2) {
1236 if (reminders->priv->last_selected_snooze_minutes) {
1237 reminders->priv->updating_snooze_combo = TRUE;
1239 if (gtk_tree_model_get_iter_first (model, &iter)) {
1240 do {
1241 gint stored = -1;
1243 gtk_tree_model_get (model, &iter, 1, &stored, -1);
1244 if (stored == reminders->priv->last_selected_snooze_minutes) {
1245 gtk_combo_box_set_active_iter (combo, &iter);
1246 break;
1248 } while (gtk_tree_model_iter_next (model, &iter));
1251 reminders->priv->updating_snooze_combo = FALSE;
1254 /* The "Add custom" item was selected */
1255 if (minutes == -1) {
1256 reminders_widget_snooze_add_custom (reminders);
1257 /* The "Clear custom times" item was selected */
1258 } else if (minutes == -2) {
1259 g_settings_reset (reminders->priv->settings, "notify-custom-snooze-minutes");
1265 static void
1266 reminders_widget_snooze_button_clicked_cb (GtkButton *button,
1267 gpointer user_data)
1269 ERemindersWidget *reminders = user_data;
1270 GtkTreeSelection *selection;
1271 GSList *selected = NULL, *link;
1272 GtkTreeIter iter;
1273 gint minutes = 0;
1274 gint64 until;
1276 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1277 g_return_if_fail (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (reminders->priv->snooze_combo), &iter));
1279 gtk_tree_model_get (gtk_combo_box_get_model (GTK_COMBO_BOX (reminders->priv->snooze_combo)), &iter, 1, &minutes, -1);
1281 g_return_if_fail (minutes > 0);
1283 until = (g_get_real_time () / G_USEC_PER_SEC) + (minutes * 60);
1285 g_settings_set_int (reminders->priv->settings, "notify-last-snooze-minutes", minutes);
1287 selection = gtk_tree_view_get_selection (reminders->priv->tree_view);
1288 gtk_tree_selection_selected_foreach (selection, reminders_widget_gather_selected_cb, &selected);
1290 g_signal_handlers_block_by_func (reminders->priv->watcher, reminders_widget_watcher_changed_cb, reminders);
1292 for (link = selected; link; link = g_slist_next (link)) {
1293 const EReminderData *rd = link->data;
1295 e_reminder_watcher_snooze (reminders->priv->watcher, rd, until);
1298 g_slist_free_full (selected, e_reminder_data_free);
1300 g_signal_handlers_unblock_by_func (reminders->priv->watcher, reminders_widget_watcher_changed_cb, reminders);
1302 if (selected)
1303 reminders_widget_watcher_changed_cb (reminders->priv->watcher, reminders);
1306 static void
1307 reminders_widget_set_watcher (ERemindersWidget *reminders,
1308 EReminderWatcher *watcher)
1310 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1311 g_return_if_fail (E_IS_REMINDER_WATCHER (watcher));
1312 g_return_if_fail (reminders->priv->watcher == NULL);
1314 reminders->priv->watcher = g_object_ref (watcher);
1317 static void
1318 reminders_widget_set_property (GObject *object,
1319 guint property_id,
1320 const GValue *value,
1321 GParamSpec *pspec)
1323 switch (property_id) {
1324 case PROP_WATCHER:
1325 reminders_widget_set_watcher (
1326 E_REMINDERS_WIDGET (object),
1327 g_value_get_object (value));
1328 return;
1331 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1334 static void
1335 reminders_widget_get_property (GObject *object,
1336 guint property_id,
1337 GValue *value,
1338 GParamSpec *pspec)
1340 switch (property_id) {
1341 case PROP_WATCHER:
1342 g_value_set_object (
1343 value, e_reminders_widget_get_watcher (
1344 E_REMINDERS_WIDGET (object)));
1345 return;
1347 case PROP_EMPTY:
1348 g_value_set_boolean (
1349 value, e_reminders_widget_is_empty (
1350 E_REMINDERS_WIDGET (object)));
1351 return;
1354 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1357 static void
1358 reminders_widget_constructed (GObject *object)
1360 ERemindersWidget *reminders = E_REMINDERS_WIDGET (object);
1361 GtkWidget *scrolled_window;
1362 GtkListStore *list_store;
1363 GtkTreeSelection *selection;
1364 GtkTreeViewColumn *column;
1365 GtkCellRenderer *renderer;
1366 GtkWidget *widget;
1367 GtkBox *box;
1369 /* Chain up to parent's method. */
1370 G_OBJECT_CLASS (e_reminders_widget_parent_class)->constructed (object);
1372 scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1373 g_object_set (G_OBJECT (scrolled_window),
1374 "halign", GTK_ALIGN_FILL,
1375 "hexpand", TRUE,
1376 "valign", GTK_ALIGN_FILL,
1377 "vexpand", TRUE,
1378 "hscrollbar-policy", GTK_POLICY_NEVER,
1379 "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
1380 "shadow-type", GTK_SHADOW_IN,
1381 NULL);
1383 gtk_grid_attach (GTK_GRID (reminders), scrolled_window, 0, 0, 1, 1);
1385 list_store = gtk_list_store_new (E_REMINDERS_WIDGET_N_COLUMNS,
1386 G_TYPE_STRING, /* E_REMINDERS_WIDGET_COLUMN_OVERDUE */
1387 G_TYPE_STRING, /* E_REMINDERS_WIDGET_COLUMN_DESCRIPTION */
1388 E_TYPE_REMINDER_DATA); /* E_REMINDERS_WIDGET_COLUMN_REMINDER_DATA */
1390 reminders->priv->tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store)));
1392 g_object_unref (list_store);
1394 g_object_set (G_OBJECT (reminders->priv->tree_view),
1395 "halign", GTK_ALIGN_FILL,
1396 "hexpand", TRUE,
1397 "valign", GTK_ALIGN_FILL,
1398 "vexpand", TRUE,
1399 "activate-on-single-click", FALSE,
1400 "enable-search", FALSE,
1401 "fixed-height-mode", TRUE,
1402 "headers-visible", FALSE,
1403 "hover-selection", FALSE,
1404 NULL);
1406 gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (reminders->priv->tree_view));
1408 gtk_tree_view_set_tooltip_column (reminders->priv->tree_view, E_REMINDERS_WIDGET_COLUMN_DESCRIPTION);
1410 /* Headers not visible, thus column's caption is not localized */
1411 gtk_tree_view_insert_column_with_attributes (reminders->priv->tree_view, -1, "Overdue",
1412 gtk_cell_renderer_text_new (), "markup", E_REMINDERS_WIDGET_COLUMN_OVERDUE, NULL);
1414 renderer = gtk_cell_renderer_text_new ();
1415 g_object_set (G_OBJECT (renderer),
1416 "ellipsize", PANGO_ELLIPSIZE_END,
1417 NULL);
1419 gtk_tree_view_insert_column_with_attributes (reminders->priv->tree_view, -1, "Description",
1420 renderer, "markup", E_REMINDERS_WIDGET_COLUMN_DESCRIPTION, NULL);
1422 column = gtk_tree_view_get_column (reminders->priv->tree_view, 0);
1423 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
1425 column = gtk_tree_view_get_column (reminders->priv->tree_view, 1);
1426 gtk_tree_view_column_set_expand (column, TRUE);
1428 reminders->priv->dismiss_button = gtk_button_new_with_mnemonic (_("_Dismiss"));
1429 reminders->priv->dismiss_all_button = gtk_button_new_with_mnemonic (_("Dismiss _All"));
1430 reminders->priv->snooze_combo = reminders_widget_new_snooze_combo ();
1431 reminders->priv->snooze_button = gtk_button_new_with_mnemonic (_("_Snooze"));
1433 reminders_widget_fill_snooze_combo (reminders,
1434 g_settings_get_int (reminders->priv->settings, "notify-last-snooze-minutes"));
1436 box = GTK_BOX (gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL));
1437 g_object_set (G_OBJECT (box),
1438 "halign", GTK_ALIGN_END,
1439 "hexpand", TRUE,
1440 "valign", GTK_ALIGN_CENTER,
1441 "vexpand", FALSE,
1442 "margin-top", 4,
1443 NULL);
1445 widget = gtk_label_new ("");
1447 gtk_box_pack_start (box, reminders->priv->snooze_combo, FALSE, FALSE, 0);
1448 gtk_box_pack_start (box, reminders->priv->snooze_button, FALSE, FALSE, 0);
1449 gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
1450 gtk_box_pack_start (box, reminders->priv->dismiss_button, FALSE, FALSE, 0);
1451 gtk_box_pack_start (box, reminders->priv->dismiss_all_button, FALSE, FALSE, 0);
1453 gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (box), reminders->priv->snooze_combo, TRUE);
1454 gtk_button_box_set_child_non_homogeneous (GTK_BUTTON_BOX (box), widget, TRUE);
1456 gtk_grid_attach (GTK_GRID (reminders), GTK_WIDGET (box), 0, 1, 1, 1);
1458 gtk_widget_show_all (GTK_WIDGET (reminders));
1460 selection = gtk_tree_view_get_selection (reminders->priv->tree_view);
1461 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
1463 g_signal_connect (reminders->priv->tree_view, "row-activated",
1464 G_CALLBACK (reminders_widget_row_activated_cb), reminders);
1466 g_signal_connect (selection, "changed",
1467 G_CALLBACK (reminders_widget_selection_changed_cb), reminders);
1469 g_signal_connect (reminders->priv->snooze_button, "clicked",
1470 G_CALLBACK (reminders_widget_snooze_button_clicked_cb), reminders);
1472 g_signal_connect (reminders->priv->dismiss_button, "clicked",
1473 G_CALLBACK (reminders_widget_dismiss_button_clicked_cb), reminders);
1475 g_signal_connect (reminders->priv->dismiss_all_button, "clicked",
1476 G_CALLBACK (reminders_widget_dismiss_all_button_clicked_cb), reminders);
1478 g_signal_connect (reminders->priv->watcher, "changed",
1479 G_CALLBACK (reminders_widget_watcher_changed_cb), reminders);
1481 g_signal_connect (reminders->priv->snooze_combo, "changed",
1482 G_CALLBACK (reminders_widget_snooze_combo_changed_cb), reminders);
1484 g_signal_connect (reminders->priv->settings, "changed::notify-custom-snooze-minutes",
1485 G_CALLBACK (reminders_widget_custom_snooze_minutes_changed_cb), reminders);
1487 e_binding_bind_property (reminders, "empty",
1488 reminders->priv->dismiss_all_button, "sensitive",
1489 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
1491 e_binding_bind_property (reminders, "empty",
1492 scrolled_window, "sensitive",
1493 G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
1495 _libedataserverui_load_modules ();
1497 e_extensible_load_extensions (E_EXTENSIBLE (object));
1499 reminders_widget_schedule_content_refresh (reminders);
1502 static void
1503 reminders_widget_dispose (GObject *object)
1505 ERemindersWidget *reminders = E_REMINDERS_WIDGET (object);
1507 g_cancellable_cancel (reminders->priv->cancellable);
1509 if (reminders->priv->refresh_idle_id) {
1510 g_source_remove (reminders->priv->refresh_idle_id);
1511 reminders->priv->refresh_idle_id = 0;
1514 if (reminders->priv->overdue_update_id) {
1515 g_source_remove (reminders->priv->overdue_update_id);
1516 reminders->priv->overdue_update_id = 0;
1519 if (reminders->priv->watcher)
1520 g_signal_handlers_disconnect_by_data (reminders->priv->watcher, reminders);
1522 if (reminders->priv->settings)
1523 g_signal_handlers_disconnect_by_data (reminders->priv->settings, reminders);
1525 /* Chain up to parent's method. */
1526 G_OBJECT_CLASS (e_reminders_widget_parent_class)->dispose (object);
1529 static void
1530 reminders_widget_finalize (GObject *object)
1532 ERemindersWidget *reminders = E_REMINDERS_WIDGET (object);
1534 g_clear_object (&reminders->priv->watcher);
1535 g_clear_object (&reminders->priv->settings);
1536 g_clear_object (&reminders->priv->cancellable);
1538 /* Chain up to parent's method. */
1539 G_OBJECT_CLASS (e_reminders_widget_parent_class)->finalize (object);
1542 static void
1543 e_reminders_widget_class_init (ERemindersWidgetClass *klass)
1545 GObjectClass *object_class;
1546 GtkWidgetClass *widget_class;
1548 g_type_class_add_private (klass, sizeof (ERemindersWidgetPrivate));
1550 object_class = G_OBJECT_CLASS (klass);
1551 object_class->set_property = reminders_widget_set_property;
1552 object_class->get_property = reminders_widget_get_property;
1553 object_class->constructed = reminders_widget_constructed;
1554 object_class->dispose = reminders_widget_dispose;
1555 object_class->finalize = reminders_widget_finalize;
1557 widget_class = GTK_WIDGET_CLASS (klass);
1558 widget_class->map = reminders_widget_map;
1559 widget_class->unmap = reminders_widget_unmap;
1562 * ERemindersWidget::watcher:
1564 * An #EReminderWatcher used to work with reminders.
1566 * Since: 3.30
1568 g_object_class_install_property (
1569 object_class,
1570 PROP_WATCHER,
1571 g_param_spec_object (
1572 "watcher",
1573 "Reminder Watcher",
1574 "The reminder watcher used to work with reminders",
1575 E_TYPE_REMINDER_WATCHER,
1576 G_PARAM_READWRITE |
1577 G_PARAM_CONSTRUCT_ONLY |
1578 G_PARAM_STATIC_STRINGS));
1581 * ERemindersWidget::empty:
1583 * Set to %TRUE when there's no past reminder in the widget.
1585 * Since: 3.30
1587 g_object_class_install_property (
1588 object_class,
1589 PROP_EMPTY,
1590 g_param_spec_boolean (
1591 "empty",
1592 "Empty",
1593 "Whether there are no past reminders",
1594 TRUE,
1595 G_PARAM_READABLE |
1596 G_PARAM_STATIC_STRINGS));
1599 * ERemindersWidget:changed:
1600 * @reminders: an #ERemindersWidget
1602 * A signal being called to notify about changes in the past reminders list.
1604 * Since: 3.30
1606 signals[CHANGED] = g_signal_new (
1607 "changed",
1608 G_OBJECT_CLASS_TYPE (klass),
1609 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1610 G_STRUCT_OFFSET (ERemindersWidgetClass, changed),
1611 NULL,
1612 NULL,
1613 g_cclosure_marshal_generic,
1614 G_TYPE_NONE, 0,
1615 G_TYPE_NONE);
1618 * ERemindersWidget:activated:
1619 * @reminders: an #ERemindersWidget
1620 * @rd: an #EReminderData
1622 * A signal being called when the user activates one of the past reminders in the tree view.
1623 * The @rd corresponds to the activated reminder.
1625 * Returns: %TRUE, when the further processing of this signal should be stopped, %FALSE otherwise.
1627 * Since: 3.30
1629 signals[ACTIVATED] = g_signal_new (
1630 "activated",
1631 G_OBJECT_CLASS_TYPE (klass),
1632 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1633 G_STRUCT_OFFSET (ERemindersWidgetClass, activated),
1634 g_signal_accumulator_first_wins,
1635 NULL,
1636 g_cclosure_marshal_generic,
1637 G_TYPE_BOOLEAN, 1,
1638 E_TYPE_REMINDER_DATA);
1641 static void
1642 e_reminders_widget_init (ERemindersWidget *reminders)
1644 reminders->priv = G_TYPE_INSTANCE_GET_PRIVATE (reminders, E_TYPE_REMINDERS_WIDGET, ERemindersWidgetPrivate);
1645 reminders->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
1646 reminders->priv->cancellable = g_cancellable_new ();
1647 reminders->priv->is_empty = TRUE;
1648 reminders->priv->is_mapped = FALSE;
1652 * e_reminders_widget_new:
1653 * @watcher: an #EReminderWatcher
1655 * Creates a new instance of #ERemindersWidget. It adds its own reference
1656 * on the @watcher.
1658 * Returns: (transfer full): a new instance of #ERemindersWidget.
1660 * Since: 3.30
1662 ERemindersWidget *
1663 e_reminders_widget_new (EReminderWatcher *watcher)
1665 g_return_val_if_fail (E_IS_REMINDER_WATCHER (watcher), NULL);
1667 return g_object_new (E_TYPE_REMINDERS_WIDGET,
1668 "watcher", watcher,
1669 NULL);
1673 * e_reminders_widget_get_watcher:
1674 * @reminders: an #ERemindersWidget
1676 * Returns: (transfer none): an #EReminderWatcher with which the @reminders had
1677 * been created. Do on unref it, it's owned by the @reminders.
1679 * Since: 3.30
1681 EReminderWatcher *
1682 e_reminders_widget_get_watcher (ERemindersWidget *reminders)
1684 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), NULL);
1686 return reminders->priv->watcher;
1690 * e_reminders_widget_get_settings:
1691 * @reminders: an #ERemindersWidget
1693 * Returns: (transfer none): a #GSettings pointing to org.gnome.evolution-data-server.calendar
1694 * used by the @reminders widget.
1696 * Since: 3.30
1698 GSettings *
1699 e_reminders_widget_get_settings (ERemindersWidget *reminders)
1701 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), NULL);
1703 return reminders->priv->settings;
1707 * e_reminders_widget_is_empty:
1708 * @reminders: an #ERemindersWidget
1710 * Returns: %TRUE, when there is no past reminder left, %FALSE otherwise.
1712 * Since: 3.30
1714 gboolean
1715 e_reminders_widget_is_empty (ERemindersWidget *reminders)
1717 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), FALSE);
1719 return reminders->priv->is_empty;
1723 * e_reminders_widget_get_tree_view:
1724 * @reminders: an #ERemindersWidget
1726 * Returns: (transfer none): a #GtkTreeView with past reminders. It's owned
1727 * by the @reminders widget.
1729 * Since: 3.30
1731 GtkTreeView *
1732 e_reminders_widget_get_tree_view (ERemindersWidget *reminders)
1734 g_return_val_if_fail (E_IS_REMINDERS_WIDGET (reminders), NULL);
1736 return reminders->priv->tree_view;
1739 static void
1740 reminders_widget_error_response_cb (GtkInfoBar *info_bar,
1741 gint response_id,
1742 gpointer user_data)
1744 ERemindersWidget *reminders = user_data;
1746 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1748 if (reminders->priv->info_bar == info_bar) {
1749 gtk_widget_destroy (GTK_WIDGET (reminders->priv->info_bar));
1750 reminders->priv->info_bar = NULL;
1755 * e_reminders_widget_report_error:
1756 * @reminders: an #ERemindersWidget
1757 * @prefix: (nullable): an optional prefix to show before the error message, or %NULL for none
1758 * @error: (nullable): a #GError to show the message from in the UI, or %NULL for unknown error
1760 * Shows a warning in the GUI with the @error message, optionally prefixed
1761 * with @prefix. When @error is %NULL, an "Unknown error" message is shown
1762 * instead.
1764 * Since: 3.30
1766 void
1767 e_reminders_widget_report_error (ERemindersWidget *reminders,
1768 const gchar *prefix,
1769 const GError *error)
1771 GtkLabel *label;
1772 const gchar *message;
1773 gchar *tmp = NULL;
1775 g_return_if_fail (E_IS_REMINDERS_WIDGET (reminders));
1777 if (error)
1778 message = error->message;
1779 else
1780 message = _("Unknown error");
1782 if (prefix && *prefix) {
1783 if (gtk_widget_get_direction (GTK_WIDGET (reminders)) == GTK_TEXT_DIR_RTL)
1784 tmp = g_strconcat (message, " ", prefix, NULL);
1785 else
1786 tmp = g_strconcat (prefix, " ", message, NULL);
1788 message = tmp;
1791 if (reminders->priv->info_bar) {
1792 gtk_widget_destroy (GTK_WIDGET (reminders->priv->info_bar));
1793 reminders->priv->info_bar = NULL;
1796 reminders->priv->info_bar = GTK_INFO_BAR (gtk_info_bar_new ());
1797 gtk_info_bar_set_message_type (reminders->priv->info_bar, GTK_MESSAGE_ERROR);
1798 gtk_info_bar_set_show_close_button (reminders->priv->info_bar, TRUE);
1800 label = GTK_LABEL (gtk_label_new (message));
1801 gtk_label_set_max_width_chars (label, 120);
1802 gtk_label_set_line_wrap (label, TRUE);
1803 gtk_label_set_selectable (label, TRUE);
1804 gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (reminders->priv->info_bar)), GTK_WIDGET (label));
1805 gtk_widget_show (GTK_WIDGET (label));
1806 gtk_widget_show (GTK_WIDGET (reminders->priv->info_bar));
1808 g_signal_connect (reminders->priv->info_bar, "response", G_CALLBACK (reminders_widget_error_response_cb), reminders);
1810 gtk_grid_attach (GTK_GRID (reminders), GTK_WIDGET (reminders->priv->info_bar), 0, 2, 1, 1);
1812 g_free (tmp);