From 4dc5558f19f96858ec2a97d82b23b6401ed74a0b Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Mon, 2 Jan 2012 17:07:59 +0100 Subject: Bug #353743 - Add Print button to meeting notification dialog --- calendar/Makefile.am | 2 +- calendar/alarm-notify/Makefile.am | 66 + calendar/alarm-notify/alarm-notify-dialog.c | 525 +++++ calendar/alarm-notify/alarm-notify-dialog.h | 63 + calendar/alarm-notify/alarm-notify.c | 566 +++++ calendar/alarm-notify/alarm-notify.h | 81 + calendar/alarm-notify/alarm-notify.ui | 377 +++ calendar/alarm-notify/alarm-queue.c | 2418 ++++++++++++++++++++ calendar/alarm-notify/alarm-queue.h | 37 + calendar/alarm-notify/alarm.c | 324 +++ calendar/alarm-notify/alarm.h | 41 + calendar/alarm-notify/config-data.c | 410 ++++ calendar/alarm-notify/config-data.h | 64 + .../alarm-notify/evolution-alarm-notify-icon.rc | 1 + calendar/alarm-notify/evolution-alarm-notify.ico | Bin 0 -> 17542 bytes calendar/alarm-notify/notify-main.c | 110 + calendar/alarm-notify/util.c | 92 + calendar/alarm-notify/util.h | 33 + calendar/gui/Makefile.am | 2 +- calendar/gui/alarm-notify/Makefile.am | 65 - calendar/gui/alarm-notify/alarm-notify-dialog.c | 502 ---- calendar/gui/alarm-notify/alarm-notify-dialog.h | 62 - calendar/gui/alarm-notify/alarm-notify.c | 566 ----- calendar/gui/alarm-notify/alarm-notify.h | 81 - calendar/gui/alarm-notify/alarm-notify.ui | 362 --- calendar/gui/alarm-notify/alarm-queue.c | 2400 ------------------- calendar/gui/alarm-notify/alarm-queue.h | 37 - calendar/gui/alarm-notify/alarm.c | 324 --- calendar/gui/alarm-notify/alarm.h | 41 - calendar/gui/alarm-notify/config-data.c | 410 ---- calendar/gui/alarm-notify/config-data.h | 64 - .../alarm-notify/evolution-alarm-notify-icon.rc | 1 - .../gui/alarm-notify/evolution-alarm-notify.ico | Bin 17542 -> 0 bytes calendar/gui/alarm-notify/notify-main.c | 110 - calendar/gui/alarm-notify/util.c | 92 - calendar/gui/alarm-notify/util.h | 33 - configure.ac | 2 +- po/POTFILES.in | 8 +- 38 files changed, 5215 insertions(+), 5157 deletions(-) create mode 100644 calendar/alarm-notify/Makefile.am create mode 100644 calendar/alarm-notify/alarm-notify-dialog.c create mode 100644 calendar/alarm-notify/alarm-notify-dialog.h create mode 100644 calendar/alarm-notify/alarm-notify.c create mode 100644 calendar/alarm-notify/alarm-notify.h create mode 100644 calendar/alarm-notify/alarm-notify.ui create mode 100644 calendar/alarm-notify/alarm-queue.c create mode 100644 calendar/alarm-notify/alarm-queue.h create mode 100644 calendar/alarm-notify/alarm.c create mode 100644 calendar/alarm-notify/alarm.h create mode 100644 calendar/alarm-notify/config-data.c create mode 100644 calendar/alarm-notify/config-data.h create mode 100644 calendar/alarm-notify/evolution-alarm-notify-icon.rc create mode 100644 calendar/alarm-notify/evolution-alarm-notify.ico create mode 100644 calendar/alarm-notify/notify-main.c create mode 100644 calendar/alarm-notify/util.c create mode 100644 calendar/alarm-notify/util.h delete mode 100644 calendar/gui/alarm-notify/Makefile.am delete mode 100644 calendar/gui/alarm-notify/alarm-notify-dialog.c delete mode 100644 calendar/gui/alarm-notify/alarm-notify-dialog.h delete mode 100644 calendar/gui/alarm-notify/alarm-notify.c delete mode 100644 calendar/gui/alarm-notify/alarm-notify.h delete mode 100644 calendar/gui/alarm-notify/alarm-notify.ui delete mode 100644 calendar/gui/alarm-notify/alarm-queue.c delete mode 100644 calendar/gui/alarm-notify/alarm-queue.h delete mode 100644 calendar/gui/alarm-notify/alarm.c delete mode 100644 calendar/gui/alarm-notify/alarm.h delete mode 100644 calendar/gui/alarm-notify/config-data.c delete mode 100644 calendar/gui/alarm-notify/config-data.h delete mode 100644 calendar/gui/alarm-notify/evolution-alarm-notify-icon.rc delete mode 100644 calendar/gui/alarm-notify/evolution-alarm-notify.ico delete mode 100644 calendar/gui/alarm-notify/notify-main.c delete mode 100644 calendar/gui/alarm-notify/util.c delete mode 100644 calendar/gui/alarm-notify/util.h diff --git a/calendar/Makefile.am b/calendar/Makefile.am index 76bc20338d..4fe6165577 100644 --- a/calendar/Makefile.am +++ b/calendar/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = importers gui +SUBDIRS = importers gui alarm-notify error_DATA = calendar.error errordir = $(privdatadir)/errors diff --git a/calendar/alarm-notify/Makefile.am b/calendar/alarm-notify/Makefile.am new file mode 100644 index 0000000000..59c918251e --- /dev/null +++ b/calendar/alarm-notify/Makefile.am @@ -0,0 +1,66 @@ +if OS_WIN32 +bin_PROGRAMS = evolution-alarm-notify +else +privlibexec_PROGRAMS = evolution-alarm-notify +endif + +if HAVE_WINDRES +EVOLUTIONALARMNOTIFYICON = evolution-alarm-notify-icon.o +endif + +evolution_alarm_notify_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DG_LOG_DOMAIN=\"evolution-alarm-notify\" \ + -I$(top_srcdir) \ + -I$(top_srcdir)/widgets \ + -I$(top_srcdir)/calendar \ + -DEVOLUTION_UIDIR=\""$(uidir)"\" \ + -DEVOLUTION_ICONDIR=\""$(icondir)"\" \ + -DEVOLUTION_LOCALEDIR=\""$(localedir)"\" \ + -DEVOLUTION_LIBEXECDIR=\""$(privlibexecdir)"\" \ + $(EVOLUTION_DATA_SERVER_CFLAGS) \ + $(GNOME_PLATFORM_CFLAGS) \ + $(LIBNOTIFY_CFLAGS) \ + $(CANBERRA_CFLAGS) + +ui_DATA = \ + alarm-notify.ui + +evolution_alarm_notify_SOURCES = \ + alarm.c \ + alarm.h \ + alarm-notify.c \ + alarm-notify.h \ + alarm-notify-dialog.c \ + alarm-notify-dialog.h \ + alarm-queue.c \ + alarm-queue.h \ + config-data.c \ + config-data.h \ + notify-main.c \ + util.c \ + util.h + +evolution_alarm_notify_LDADD = \ + $(top_builddir)/e-util/libeutil.la \ + $(top_builddir)/widgets/misc/libemiscwidgets.la \ + $(top_builddir)/calendar/gui/libevolution-calendar.la \ + $(EVOLUTION_DATA_SERVER_LIBS) \ + $(GNOME_PLATFORM_LIBS) \ + $(LIBNOTIFY_LIBS) \ + $(CANBERRA_LIBS) \ + $(EVOLUTIONALARMNOTIFYICON) + +if OS_WIN32 +evolution_alarm_notify_LDFLAGS = -mwindows +endif + +EXTRA_DIST = $(ui_DATA) \ + evolution-alarm-notify-icon.rc \ + evolution-alarm-notify.ico + + +evolution-alarm-notify-icon.o: evolution-alarm-notify.ico evolution-alarm-notify-icon.rc + $(WINDRES) evolution-alarm-notify-icon.rc evolution-alarm-notify-icon.o + +-include $(top_srcdir)/git.mk diff --git a/calendar/alarm-notify/alarm-notify-dialog.c b/calendar/alarm-notify/alarm-notify-dialog.c new file mode 100644 index 0000000000..c9a264d880 --- /dev/null +++ b/calendar/alarm-notify/alarm-notify-dialog.c @@ -0,0 +1,525 @@ +/* + * Evolution calendar - alarm notification dialog + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include "alarm-notify-dialog.h" +#include "config-data.h" +#include "util.h" + +#include "e-util/e-util.h" +#include "e-util/e-util-private.h" +#include "misc/e-buffer-tagger.h" + +enum { + ALARM_DISPLAY_COLUMN, + ALARM_SUMMARY_COLUMN, + ALARM_DESCRIPTION_COLUMN, + ALARM_LOCATION_COLUMN, + + ALARM_START_COLUMN, + ALARM_END_COLUMN, + + ALARM_FUNCINFO_COLUMN, + + N_ALARM_COLUMNS +}; + +/* The useful contents of the alarm notify dialog */ + +typedef struct { + AlarmNotifyFunc func; + gpointer func_data; +} AlarmFuncInfo; + +typedef struct { + GtkBuilder *builder; + + GtkWidget *dialog; + GtkWidget *snooze_time_min; + GtkWidget *snooze_time_hrs; + GtkWidget *snooze_time_days; + GtkWidget *snooze_btn; + GtkWidget *edit_btn; + GtkWidget *print_btn; + GtkWidget *dismiss_btn; + GtkWidget *minutes_label; + GtkWidget *hrs_label; + GtkWidget *days_label; + GtkWidget *description; + GtkWidget *location; + GtkWidget *treeview; + + AlarmFuncInfo *cur_funcinfo; + +} AlarmNotify; + +static void tree_selection_changed_cb (GtkTreeSelection *selection, + gpointer data); +static void fill_in_labels (AlarmNotify *an, + const gchar *summary, + const gchar *description, + const gchar *location, + time_t occur_start, + time_t occur_end); + +static void edit_pressed_cb (GtkButton *button, + gpointer user_data); +static void snooze_pressed_cb (GtkButton *button, + gpointer user_data); + +static void +an_update_minutes_label (GtkSpinButton *sb, + gpointer data) +{ + AlarmNotify *an; + gint snooze_timeout_min; + + an = (AlarmNotify *) data; + + snooze_timeout_min = gtk_spin_button_get_value_as_int (sb); + gtk_label_set_text (GTK_LABEL (an->minutes_label), ngettext ("minute", "minutes", snooze_timeout_min)); +} + +static void +an_update_hrs_label (GtkSpinButton *sb, + gpointer data) +{ + AlarmNotify *an; + gint snooze_timeout_hrs; + + an = (AlarmNotify *) data; + + snooze_timeout_hrs = gtk_spin_button_get_value_as_int (sb); + gtk_label_set_text (GTK_LABEL (an->hrs_label), ngettext ("hour", "hours", snooze_timeout_hrs)); +} + +static void +an_update_days_label (GtkSpinButton *sb, + gpointer data) +{ + AlarmNotify *an; + gint snooze_timeout_days; + + an = (AlarmNotify *) data; + + snooze_timeout_days = gtk_spin_button_get_value_as_int (sb); + gtk_label_set_text (GTK_LABEL (an->days_label), ngettext ("day", "days", snooze_timeout_days)); +} + +static void +dialog_response_cb (GtkDialog *dialog, + guint response_id, + gpointer user_data) +{ + AlarmNotify *an = user_data; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + AlarmFuncInfo *funcinfo = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + } + + if (!funcinfo) { + GtkTreeModel *treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (an->treeview)); + if (!gtk_tree_model_get_iter_first (treemodel, &iter)) + return; + + gtk_tree_model_get (treemodel, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + } + + g_return_if_fail (funcinfo); + + switch (response_id) { + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + (* funcinfo->func) (ALARM_NOTIFY_CLOSE, -1, funcinfo->func_data); + break; + } +} + +static void +edit_pressed_cb (GtkButton *button, + gpointer user_data) +{ + AlarmNotify *an = user_data; + AlarmFuncInfo *funcinfo = NULL; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + + g_return_if_fail (funcinfo); + + (* funcinfo->func) (ALARM_NOTIFY_EDIT, -1, funcinfo->func_data); +} + +static void +print_pressed_cb (GtkButton *button, + gpointer user_data) +{ + AlarmNotify *an = user_data; + AlarmFuncInfo *funcinfo = NULL; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + + g_return_if_fail (funcinfo); + + (* funcinfo->func) (ALARM_NOTIFY_PRINT, -1, funcinfo->func_data); +} + +#define DEFAULT_SNOOZE_MINS 5 + +static void +snooze_pressed_cb (GtkButton *button, + gpointer user_data) +{ + gint snooze_timeout; + AlarmNotify *an = user_data; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + AlarmFuncInfo *funcinfo = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + + gtk_widget_grab_focus ((GtkWidget *) button); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + + g_return_if_fail (funcinfo); + + snooze_timeout = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_min)); + snooze_timeout += 60 * (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_hrs))); + snooze_timeout += 60 * 24 * (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_days))); + if (!snooze_timeout) + snooze_timeout = DEFAULT_SNOOZE_MINS; + (* funcinfo->func) (ALARM_NOTIFY_SNOOZE, snooze_timeout, funcinfo->func_data); +} + +static void +dismiss_pressed_cb (GtkButton *button, + gpointer user_data) +{ + AlarmNotify *an = user_data; + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (an->treeview)); + + g_return_if_fail (model != NULL); + + if (gtk_tree_model_iter_n_children (model, NULL) <= 1) { + gtk_dialog_response (GTK_DIALOG (an->dialog), GTK_RESPONSE_CLOSE); + } else { + GtkTreeIter iter; + AlarmFuncInfo *funcinfo = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); + + g_return_if_fail (funcinfo); + + (* funcinfo->func) (ALARM_NOTIFY_DISMISS, -1, funcinfo->func_data); + } +} + +static void +dialog_destroyed_cb (GtkWidget *dialog, + gpointer user_data) +{ + AlarmNotify *an = user_data; + + g_object_unref (an->builder); + g_free (an); +} + +/** + * notified_alarms_dialog_new: + * + * Return value: a new dialog in which you can add alarm notifications + **/ +AlarmNotificationsDialog * +notified_alarms_dialog_new (void) +{ + GtkWidget *container; + GtkWidget *image; + GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); + AlarmNotificationsDialog *na = NULL; + AlarmNotify *an = g_new0 (AlarmNotify, 1); + GtkTreeViewColumn *column = NULL; + GtkTreeSelection *selection = NULL; + GtkTreeModel *model = GTK_TREE_MODEL (gtk_list_store_new ( + N_ALARM_COLUMNS, + + G_TYPE_STRING, /* Display */ + G_TYPE_STRING, /* Summary */ + G_TYPE_STRING, /* Description */ + G_TYPE_STRING, /* Location */ + + G_TYPE_POINTER, /* Start */ + G_TYPE_POINTER, /* End */ + + G_TYPE_POINTER /* FuncInfo */)); + + an->builder = gtk_builder_new (); + e_load_ui_builder_definition (an->builder, "alarm-notify.ui"); + + an->dialog = e_builder_get_widget (an->builder, "alarm-notify"); + an->snooze_time_min = e_builder_get_widget (an->builder, "snooze-time-min"); + an->minutes_label = e_builder_get_widget (an->builder, "minutes-label"); + an->snooze_time_hrs = e_builder_get_widget (an->builder, "snooze-time-hrs"); + an->hrs_label = e_builder_get_widget (an->builder, "hrs-label"); + an->snooze_time_days = e_builder_get_widget (an->builder, "snooze-time-days"); + an->days_label = e_builder_get_widget (an->builder, "days-label"); + an->description = e_builder_get_widget (an->builder, "description-label"); + an->location = e_builder_get_widget (an->builder, "location-label"); + an->treeview = e_builder_get_widget (an->builder, "appointments-treeview"); + an->snooze_btn = e_builder_get_widget (an->builder, "snooze-button"); + an->edit_btn = e_builder_get_widget (an->builder, "edit-button"); + an->print_btn = e_builder_get_widget (an->builder, "print-button"); + an->dismiss_btn = e_builder_get_widget (an->builder, "dismiss-button"); + + if (!(an->dialog && an->treeview + && an->snooze_time_min && an->snooze_time_hrs && an->snooze_time_days + && an->description && an->location && an->edit_btn && an->print_btn && an->snooze_btn && an->dismiss_btn)) { + g_warning ("alarm_notify_dialog(): Could not find all widgets in alarm-notify.ui file!"); + g_object_unref (an->builder); + g_free (an); + return NULL; + } + + e_buffer_tagger_connect (GTK_TEXT_VIEW (an->description)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (an->treeview), model); + + gtk_window_set_keep_above (GTK_WINDOW (an->dialog), TRUE); + + column = gtk_tree_view_column_new_with_attributes (_("Start time"), + renderer, "text", ALARM_DISPLAY_COLUMN, NULL); + + gtk_tree_view_column_set_attributes (column, renderer, + "markup", ALARM_DISPLAY_COLUMN, NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (an->treeview), column); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + g_signal_connect ( + selection, "changed", + G_CALLBACK (tree_selection_changed_cb), an); + tree_selection_changed_cb (selection, an); + + gtk_widget_realize (an->dialog); + + container = gtk_dialog_get_action_area (GTK_DIALOG (an->dialog)); + gtk_container_set_border_width (GTK_CONTAINER (container), 12); + + container = gtk_dialog_get_content_area (GTK_DIALOG (an->dialog)); + gtk_container_set_border_width (GTK_CONTAINER (container), 0); + + image = e_builder_get_widget (an->builder, "alarm-image"); + gtk_image_set_from_icon_name ( + GTK_IMAGE (image), "stock_alarm", GTK_ICON_SIZE_DIALOG); + + g_signal_connect (an->edit_btn, "clicked", G_CALLBACK (edit_pressed_cb), an); + g_signal_connect (an->print_btn, "clicked", G_CALLBACK (print_pressed_cb), an); + g_signal_connect (an->snooze_btn, "clicked", G_CALLBACK (snooze_pressed_cb), an); + g_signal_connect (an->dismiss_btn, "clicked", G_CALLBACK (dismiss_pressed_cb), an); + g_signal_connect ( + an->dialog, "response", + G_CALLBACK (dialog_response_cb), an); + g_signal_connect ( + an->dialog, "destroy", + G_CALLBACK (dialog_destroyed_cb), an); + + if (!gtk_widget_get_realized (an->dialog)) + gtk_widget_realize (an->dialog); + + gtk_window_set_icon_name (GTK_WINDOW (an->dialog), "stock_alarm"); + + /* Set callback for updating the snooze "minutes" label */ + g_signal_connect ( + an->snooze_time_min, "value_changed", + G_CALLBACK (an_update_minutes_label), an); + + /* Set callback for updating the snooze "hours" label */ + g_signal_connect ( + an->snooze_time_hrs, "value_changed", + G_CALLBACK (an_update_hrs_label), an); + + /* Set callback for updating the snooze "days" label */ + g_signal_connect ( + an->snooze_time_days, "value_changed", + G_CALLBACK (an_update_days_label), an); + + na = g_new0 (AlarmNotificationsDialog, 1); + + na->treeview = an->treeview; + na->dialog = an->dialog; + + return na; +} + +/** + * add_alarm_to_notified_alarms_dialog: + * @na: Pointer to the dialog-info + * @trigger: Trigger time for the alarm. + * @occur_start: Start of occurrence time for the event. + * @occur_end: End of occurrence time for the event. + * @vtype: Type of the component which corresponds to the alarm. + * @summary: Short summary of the appointment + * @description: Long description of the appointment + * @location: Location of the appointment + * @func: Function to be called when a dialog action is invoked. + * @func_data: Closure data for @func. + * + * The specified @func will be used to notify the client about result of + * the actions in the dialog. + * + * Return value: the iter in the treeview of the dialog + **/ + +GtkTreeIter +add_alarm_to_notified_alarms_dialog (AlarmNotificationsDialog *na, + time_t trigger, + time_t occur_start, + time_t occur_end, + ECalComponentVType vtype, + const gchar *summary, + const gchar *description, + const gchar *location, + AlarmNotifyFunc func, + gpointer func_data) +{ + GtkTreeIter iter; + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (na->treeview)); + AlarmFuncInfo *funcinfo = NULL; + gchar *to_display = NULL, *start, *end, *str_time; + icaltimezone *current_zone; + + /* Iter is not yet defined but still we return it in all the g_return_val_if_fail() calls? */ + g_return_val_if_fail (trigger != -1, iter); + + /* Only VEVENTs or VTODOs can have alarms */ + g_return_val_if_fail (vtype == E_CAL_COMPONENT_EVENT || vtype == E_CAL_COMPONENT_TODO, iter); + g_return_val_if_fail (summary != NULL, iter); + g_return_val_if_fail (description != NULL, iter); + g_return_val_if_fail (location != NULL, iter); + g_return_val_if_fail (func != NULL, iter); + + funcinfo = g_new0 (AlarmFuncInfo, 1); + funcinfo->func = func; + funcinfo->func_data = func_data; + + gtk_list_store_append (GTK_LIST_STORE (model), &iter); + + current_zone = config_data_get_timezone (); + start = timet_to_str_with_zone (occur_start, current_zone); + end = timet_to_str_with_zone (occur_end, current_zone); + str_time = calculate_time (occur_start, occur_end); + to_display = g_markup_printf_escaped ("%s\n%s %s", + summary, start, str_time); + g_free (start); + g_free (end); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, + ALARM_DISPLAY_COLUMN, to_display, -1); + g_free (to_display); + g_free (str_time); + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_SUMMARY_COLUMN, summary, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_DESCRIPTION_COLUMN, description, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_LOCATION_COLUMN, location, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_START_COLUMN, occur_start, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_END_COLUMN, occur_end, -1); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_FUNCINFO_COLUMN, funcinfo, -1); + + return iter; +} + +static void +tree_selection_changed_cb (GtkTreeSelection *selection, + gpointer user_data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + AlarmNotify *an = user_data; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gchar *summary, *description, *location; + time_t occur_start, occur_end; + + gtk_widget_set_sensitive (an->snooze_btn, TRUE); + gtk_widget_set_sensitive (an->edit_btn, TRUE); + gtk_widget_set_sensitive (an->print_btn, TRUE); + gtk_widget_set_sensitive (an->dismiss_btn, TRUE); + gtk_tree_model_get (model, &iter, ALARM_SUMMARY_COLUMN, &summary, -1); + gtk_tree_model_get (model, &iter, ALARM_DESCRIPTION_COLUMN, &description, -1); + gtk_tree_model_get (model, &iter, ALARM_LOCATION_COLUMN, &location, -1); + gtk_tree_model_get (model, &iter, ALARM_START_COLUMN, &occur_start, -1); + gtk_tree_model_get (model, &iter, ALARM_END_COLUMN, &occur_end, -1);\ + gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &an->cur_funcinfo, -1); + + fill_in_labels (an, summary, description, location, occur_start, occur_end); + } else { + gtk_widget_set_sensitive (an->snooze_btn, FALSE); + gtk_widget_set_sensitive (an->edit_btn, FALSE); + gtk_widget_set_sensitive (an->print_btn, FALSE); + gtk_widget_set_sensitive (an->dismiss_btn, FALSE); + + fill_in_labels (an, "", "", "", -1, -1); + } +} + +static void +fill_in_labels (AlarmNotify *an, + const gchar *summary, + const gchar *description, + const gchar *location, + time_t occur_start, + time_t occur_end) +{ + GtkTextTagTable *table = gtk_text_tag_table_new (); + GtkTextBuffer *buffer = gtk_text_buffer_new (table); + gtk_text_buffer_set_text (buffer, description, -1); + e_buffer_tagger_disconnect (GTK_TEXT_VIEW (an->description)); + gtk_text_view_set_buffer (GTK_TEXT_VIEW (an->description), buffer); + gtk_label_set_text (GTK_LABEL (an->location), location); + e_buffer_tagger_connect (GTK_TEXT_VIEW (an->description)); + e_buffer_tagger_update_tags (GTK_TEXT_VIEW (an->description)); + g_object_unref (table); + g_object_unref (buffer); +} diff --git a/calendar/alarm-notify/alarm-notify-dialog.h b/calendar/alarm-notify/alarm-notify-dialog.h new file mode 100644 index 0000000000..f61bdba9c8 --- /dev/null +++ b/calendar/alarm-notify/alarm-notify-dialog.h @@ -0,0 +1,63 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef ALARM_NOTIFY_DIALOG_H +#define ALARM_NOTIFY_DIALOG_H + +#include +#include +#include + +typedef enum { + ALARM_NOTIFY_CLOSE, + ALARM_NOTIFY_SNOOZE, + ALARM_NOTIFY_EDIT, + ALARM_NOTIFY_PRINT, + ALARM_NOTIFY_DISMISS +} AlarmNotifyResult; + +typedef struct _AlarmNotificationsDialog AlarmNotificationsDialog; + +struct _AlarmNotificationsDialog { + GtkWidget *dialog; + GtkWidget *treeview; +}; + +typedef void (*AlarmNotifyFunc) (AlarmNotifyResult result, + gint snooze_mins, + gpointer data); + +AlarmNotificationsDialog * + notified_alarms_dialog_new (void); +GtkTreeIter add_alarm_to_notified_alarms_dialog + (AlarmNotificationsDialog *na, + time_t trigger, + time_t occur_start, + time_t occur_end, + ECalComponentVType vtype, + const gchar *summary, + const gchar *description, + const gchar *location, + AlarmNotifyFunc func, + gpointer func_data); + +#endif /* ALARM_NOTIFY_DIALOG_H */ diff --git a/calendar/alarm-notify/alarm-notify.c b/calendar/alarm-notify/alarm-notify.c new file mode 100644 index 0000000000..eeca88e1b6 --- /dev/null +++ b/calendar/alarm-notify/alarm-notify.c @@ -0,0 +1,566 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "alarm.h" +#include "alarm-notify.h" +#include "alarm-queue.h" +#include "config-data.h" + +#define ALARM_NOTIFY_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), TYPE_ALARM_NOTIFY, AlarmNotifyPrivate)) + +#define APPLICATION_ID "org.gnome.EvolutionAlarmNotify" + +struct _AlarmNotifyPrivate { + /* Mapping from EUri's to LoadedClient structures */ + /* FIXME do we need per source type uri hashes? or perhaps we + * just need to hash based on source */ + GHashTable *uri_client_hash[E_CAL_CLIENT_SOURCE_TYPE_LAST]; + ESourceList *source_lists[E_CAL_CLIENT_SOURCE_TYPE_LAST]; + ESourceList *selected_calendars; + GMutex *mutex; + + GSList *offline_sources; + guint offline_timeout_id; +}; + +typedef struct { + AlarmNotify *an; + ESourceList *source_list; + GList *removals; +} ProcessRemovalsData; + +/* Forward Declarations */ +static void alarm_notify_initable_init (GInitableIface *interface); + +G_DEFINE_TYPE_WITH_CODE ( + AlarmNotify, alarm_notify, GTK_TYPE_APPLICATION, + G_IMPLEMENT_INTERFACE ( + G_TYPE_INITABLE, alarm_notify_initable_init)) + +static void +process_removal_in_hash (const gchar *uri, + gpointer value, + ProcessRemovalsData *prd) +{ + GSList *groups, *sources, *p, *q; + gboolean found = FALSE; + + /* search the list of selected calendars */ + groups = e_source_list_peek_groups (prd->source_list); + for (p = groups; p != NULL; p = p->next) { + ESourceGroup *group = E_SOURCE_GROUP (p->data); + + sources = e_source_group_peek_sources (group); + for (q = sources; q != NULL; q = q->next) { + ESource *source = E_SOURCE (q->data); + gchar *source_uri; + const gchar *completion = e_source_get_property (source, "alarm"); + + source_uri = e_source_get_uri (source); + if (strcmp (source_uri, uri) == 0) + if (!completion || !g_ascii_strcasecmp (completion, "true")) + found = TRUE; + + g_free (source_uri); + + if (found) + return; + } + } + + /* not found, so list it for removal */ + prd->removals = g_list_prepend (prd->removals, (gpointer) uri); +} + +static gint +find_slist_source_uri_cb (gconstpointer a, + gconstpointer b) +{ + ESource *asource = (ESource *) a; + const gchar *buri = b; + gchar *auri; + gint res; + + auri = e_source_get_uri (asource); + res = g_strcmp0 (auri, buri); + g_free (auri); + + return res; +} + +static void +alarm_notify_list_changed_cb (ESourceList *source_list, + AlarmNotify *an) +{ + GSList *groups, *sources, *p, *q; + ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST; + ProcessRemovalsData prd; + GList *l; + gint i; + + g_signal_handlers_block_by_func ( + source_list, alarm_notify_list_changed_cb, an); + + /* Figure out the source type */ + for (i = 0; i < E_CAL_CLIENT_SOURCE_TYPE_LAST; i++) { + if (source_list == an->priv->source_lists[i]) { + source_type = i; + break; + } + } + if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) + return; + + /* process the additions */ + groups = e_source_list_peek_groups (source_list); + for (p = groups; p != NULL; p = p->next) { + ESourceGroup *group = E_SOURCE_GROUP (p->data); + + sources = e_source_group_peek_sources (group); + for (q = sources; q != NULL; q = q->next) { + ESource *source = E_SOURCE (q->data); + gchar *uri; + const gchar *alarm = e_source_get_property (source, "alarm"); + + if (alarm && (!g_ascii_strcasecmp (alarm, "false") || + !g_ascii_strcasecmp (alarm, "never"))) + continue; + + uri = e_source_get_uri (source); + if (!g_hash_table_lookup (an->priv->uri_client_hash[source_type], uri) && + !g_slist_find_custom (an->priv->offline_sources, uri, find_slist_source_uri_cb)) { + debug (("Adding Calendar %s", uri)); + alarm_notify_add_calendar (an, source_type, source); + } + g_free (uri); + } + } + + /* process the removals */ + prd.an = an; + prd.source_list = an->priv->source_lists[source_type]; + prd.removals = NULL; + g_hash_table_foreach ( + an->priv->uri_client_hash[source_type], + (GHFunc) process_removal_in_hash, &prd); + + for (l = prd.removals; l; l = l->next) { + debug (("Removing Calendar %s", (gchar *)l->data)); + alarm_notify_remove_calendar (an, source_type, l->data); + } + g_list_free (prd.removals); + g_signal_handlers_unblock_by_func ( + source_list, alarm_notify_list_changed_cb, an); + +} + +static void +alarm_notify_load_calendars (AlarmNotify *an, + ECalClientSourceType source_type) +{ + ESourceList *source_list; + GSList *groups, *sources, *p, *q; + + if (!e_cal_client_get_sources (&source_list, source_type, NULL)) { + debug (("Cannont get sources")); + an->priv->source_lists[source_type] = NULL; + + return; + } + + groups = e_source_list_peek_groups (source_list); + for (p = groups; p != NULL; p = p->next) { + ESourceGroup *group = E_SOURCE_GROUP (p->data); + + sources = e_source_group_peek_sources (group); + for (q = sources; q != NULL; q = q->next) { + ESource *source = E_SOURCE (q->data); + gchar *uri; + const gchar *alarm = e_source_get_property (source, "alarm"); + + if (alarm && (!g_ascii_strcasecmp (alarm, "false") || !g_ascii_strcasecmp (alarm, "never"))) + continue; + + uri = e_source_get_uri (source); + debug (("Loading Calendar %s", uri)); + alarm_notify_add_calendar (an, source_type, source); + g_free (uri); + + } + } + + e_source_list_sync (source_list, NULL); + g_signal_connect_object ( + source_list, "changed", + G_CALLBACK (alarm_notify_list_changed_cb), an, 0); + an->priv->source_lists[source_type] = source_list; +} + +static void +alarm_notify_dequeue_client (gpointer key, + ECalClient *client) +{ + alarm_queue_remove_client (client, TRUE); +} + +static void +alarm_notify_finalize (GObject *object) +{ + AlarmNotifyPrivate *priv; + gint ii; + + priv = ALARM_NOTIFY_GET_PRIVATE (object); + + if (priv->offline_timeout_id) + g_source_remove (priv->offline_timeout_id); + priv->offline_timeout_id = 0; + g_slist_free_full (priv->offline_sources, g_object_unref); + priv->offline_sources = NULL; + + for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) { + g_hash_table_foreach ( + priv->uri_client_hash[ii], + (GHFunc) alarm_notify_dequeue_client, NULL); + g_hash_table_destroy (priv->uri_client_hash[ii]); + } + + alarm_queue_done (); + alarm_done (); + + e_passwords_shutdown (); + + g_mutex_free (priv->mutex); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (alarm_notify_parent_class)->finalize (object); +} + +static void +alarm_notify_startup (GApplication *application) +{ + GtkIconTheme *icon_theme; + + /* Chain up to parent's startup() method. */ + G_APPLICATION_CLASS (alarm_notify_parent_class)->startup (application); + + /* Keep the application running. */ + g_application_hold (application); + + config_data_init_debugging (); + + /* FIXME Ideally we should not use Camel libraries in calendar, + * though it is the case currently for attachments. Remove + * this once that is fixed. */ + + /* Initialize Camel's type system. */ + camel_object_get_type (); + + icon_theme = gtk_icon_theme_get_default (); + gtk_icon_theme_append_search_path (icon_theme, EVOLUTION_ICONDIR); +} + +static void +alarm_notify_activate (GApplication *application) +{ + /* Disregard. This is just here to prevent the default + * activate method from running, which issues a warning + * if there are no handlers connected to this signal. */ +} + +static gboolean +alarm_notify_initable (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + /* XXX Just return TRUE for now. We'll have use for this soon. */ + return TRUE; +} + +static void +alarm_notify_class_init (AlarmNotifyClass *class) +{ + GObjectClass *object_class; + GApplicationClass *application_class; + + g_type_class_add_private (class, sizeof (AlarmNotifyPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = alarm_notify_finalize; + + application_class = G_APPLICATION_CLASS (class); + application_class->startup = alarm_notify_startup; + application_class->activate = alarm_notify_activate; +} + +static void +alarm_notify_initable_init (GInitableIface *interface) +{ + /* XXX Awkward name since we're missing an 'E' prefix. */ + interface->init = alarm_notify_initable; +} + +static void +alarm_notify_init (AlarmNotify *an) +{ + gint ii; + + an->priv = ALARM_NOTIFY_GET_PRIVATE (an); + an->priv->mutex = g_mutex_new (); + an->priv->selected_calendars = config_data_get_calendars ( + "/apps/evolution/calendar/sources"); + + for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) + an->priv->uri_client_hash[ii] = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_object_unref); + + alarm_queue_init (an); + + for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) + alarm_notify_load_calendars (an, ii); +} + +ESourceList * +alarm_notify_get_selected_calendars (AlarmNotify *an) +{ + return an->priv->selected_calendars; +} + +/** + * alarm_notify_new: + * + * Creates a new #AlarmNotify object. + * + * Returns: a newly-created #AlarmNotify + **/ +AlarmNotify * +alarm_notify_new (GCancellable *cancellable, + GError **error) +{ + return g_initable_new ( + TYPE_ALARM_NOTIFY, cancellable, error, + "application-id", APPLICATION_ID, NULL); +} + +static gboolean +try_open_offline_timeout_cb (gpointer user_data) +{ + AlarmNotify *an = ALARM_NOTIFY (user_data); + GSList *sources, *iter; + + g_return_val_if_fail (an != NULL, FALSE); + g_return_val_if_fail (an->priv != NULL, FALSE); + + sources = an->priv->offline_sources; + an->priv->offline_sources = NULL; + an->priv->offline_timeout_id = 0; + + for (iter = sources; iter; iter = iter->next) { + ESource *source = iter->data; + + alarm_notify_add_calendar (an, + GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (source), "source-type")), + source); + } + + g_slist_free_full (sources, g_object_unref); + + return FALSE; +} + +static void +client_opened_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + ESource *source = E_SOURCE (source_object); + AlarmNotify *an = ALARM_NOTIFY (user_data); + EClient *client = NULL; + ECalClient *cal_client; + ECalClientSourceType source_type; + const gchar *uri; + GError *error = NULL; + + e_client_utils_open_new_finish (source, result, &client, &error); + + if (client == NULL) { + if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) { + if (an->priv->offline_timeout_id) + g_source_remove (an->priv->offline_timeout_id); + an->priv->offline_sources = g_slist_append (an->priv->offline_sources, g_object_ref (source)); + an->priv->offline_timeout_id = g_timeout_add_seconds (5 * 60, try_open_offline_timeout_cb, an); + } + + g_clear_error (&error); + + return; + } + + cal_client = E_CAL_CLIENT (client); + source_type = e_cal_client_get_source_type (cal_client); + uri = e_client_get_uri (client); + + g_hash_table_insert ( + an->priv->uri_client_hash[source_type], + g_strdup (uri), cal_client); + + /* to resolve floating DATE-TIME properly */ + e_cal_client_set_default_timezone ( + cal_client, config_data_get_timezone ()); + + alarm_queue_add_client (cal_client); +} + +/** + * alarm_notify_add_calendar: + * @an: An alarm notification service. + * @uri: URI of the calendar to load. + * + * Tells the alarm notification service to load a calendar and start monitoring + * its alarms. It can optionally be made to save the URI of this calendar so + * that it can be loaded in the future when the alarm daemon starts up. + **/ +void +alarm_notify_add_calendar (AlarmNotify *an, + ECalClientSourceType source_type, + ESource *source) +{ + AlarmNotifyPrivate *priv; + EClientSourceType client_source_type; + EUri *e_uri; + gchar *str_uri; + gchar *pass_key; + g_return_if_fail (an != NULL); + g_return_if_fail (IS_ALARM_NOTIFY (an)); + + /* Make sure the key used in for getting password is + * properly generated for all types of backends. */ + priv = an->priv; + str_uri = e_source_get_uri (source); + e_uri = e_uri_new (str_uri); + if (e_source_get_property (source, "auth-type")) + pass_key = e_uri_to_string (e_uri, FALSE); + else + pass_key = g_strdup (str_uri); + e_uri_free (e_uri); + + g_mutex_lock (an->priv->mutex); + /* See if we already know about this uri */ + if (g_hash_table_lookup (priv->uri_client_hash[source_type], str_uri)) { + g_mutex_unlock (an->priv->mutex); + g_free (str_uri); + g_free (pass_key); + return; + } + + /* If loading of this requires password and password is not + * currently availble in e-password session, skip this source + * loading. We do not really want to prompt for auth from + * the alarm dameon. */ + + if (e_source_get_property (source, "auth")) { + + if (!e_passwords_get_password (NULL, pass_key)) { + g_mutex_unlock (an->priv->mutex); + g_free (str_uri); + g_free (pass_key); + + return; + } + } + + debug (("%s - Calendar Open Async... %p", str_uri, source)); + + switch (source_type) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + client_source_type = E_CLIENT_SOURCE_TYPE_EVENTS; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + client_source_type = E_CLIENT_SOURCE_TYPE_TASKS; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + client_source_type = E_CLIENT_SOURCE_TYPE_MEMOS; + break; + default: + g_warn_if_reached (); + client_source_type = E_CLIENT_SOURCE_TYPE_LAST; + } + + g_object_set_data (G_OBJECT (source), "source-type", GUINT_TO_POINTER (source_type)); + + e_client_utils_open_new ( + source, client_source_type, TRUE, NULL, + e_client_utils_authenticate_handler, NULL, + client_opened_cb, an); + + g_free (str_uri); + g_free (pass_key); + g_mutex_unlock (an->priv->mutex); +} + +void +alarm_notify_remove_calendar (AlarmNotify *an, + ECalClientSourceType source_type, + const gchar *str_uri) +{ + AlarmNotifyPrivate *priv; + ECalClient *cal_client; + GSList *in_offline; + + priv = an->priv; + + cal_client = g_hash_table_lookup ( + priv->uri_client_hash[source_type], str_uri); + if (cal_client) { + debug (("Removing Client %p", cal_client)); + alarm_queue_remove_client (cal_client, FALSE); + g_hash_table_remove (priv->uri_client_hash[source_type], str_uri); + } + + in_offline = g_slist_find_custom (priv->offline_sources, str_uri, find_slist_source_uri_cb); + if (in_offline) { + ESource *source = in_offline->data; + + priv->offline_sources = g_slist_remove (priv->offline_sources, source); + if (!priv->offline_sources && priv->offline_timeout_id) { + g_source_remove (priv->offline_timeout_id); + priv->offline_timeout_id = 0; + } + + g_object_unref (source); + } +} diff --git a/calendar/alarm-notify/alarm-notify.h b/calendar/alarm-notify/alarm-notify.h new file mode 100644 index 0000000000..b48cd5a602 --- /dev/null +++ b/calendar/alarm-notify/alarm-notify.h @@ -0,0 +1,81 @@ +/* + * + * Evolution calendar - Alarm notification service object + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef ALARM_NOTIFY_H +#define ALARM_NOTIFY_H + +#include +#include +#include + +/* Standard GObject macros */ +#define TYPE_ALARM_NOTIFY \ + (alarm_notify_get_type ()) +#define ALARM_NOTIFY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST \ + ((obj), TYPE_ALARM_NOTIFY, AlarmNotify)) +#define ALARM_NOTIFY_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_CAST \ + ((cls), TYPE_ALARM_NOTIFY, AlarmNotifyClass)) +#define IS_ALARM_NOTIFY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE \ + ((obj), TYPE_ALARM_NOTIFY)) +#define IS_ALARM_NOTIFY_CLASS(cls) \ + (G_TYPE_CHECK_CLASS_TYPE \ + ((cls), TYPE_ALARM_NOTIFY)) +#define ALARM_NOTIFY_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS \ + ((obj), TYPE_ALARM_NOTIFY, AlarmNotifyClass)) + +G_BEGIN_DECLS + +typedef struct _AlarmNotify AlarmNotify; +typedef struct _AlarmNotifyClass AlarmNotifyClass; +typedef struct _AlarmNotifyPrivate AlarmNotifyPrivate; + +struct _AlarmNotify { + GtkApplication parent; + AlarmNotifyPrivate *priv; +}; + +struct _AlarmNotifyClass { + GtkApplicationClass parent_class; +}; + +GType alarm_notify_get_type (void); +AlarmNotify * alarm_notify_new (GCancellable *cancellable, + GError **error); +void alarm_notify_add_calendar (AlarmNotify *an, + ECalClientSourceType source_type, + ESource *source); +void alarm_notify_remove_calendar (AlarmNotify *an, + ECalClientSourceType source_type, + const gchar *str_uri); +ESourceList * alarm_notify_get_selected_calendars + (AlarmNotify *an); + +G_END_DECLS + +#endif /* ALARM_NOTIFY_H */ diff --git a/calendar/alarm-notify/alarm-notify.ui b/calendar/alarm-notify/alarm-notify.ui new file mode 100644 index 0000000000..acfdf5cd64 --- /dev/null +++ b/calendar/alarm-notify/alarm-notify.ui @@ -0,0 +1,377 @@ + + + + + + 65535 + 1 + 5 + + + 65535 + 1 + 5 + + + 65535 + 5 + 1 + 10 + + + True + 5 + Appointments + 384 + 200 + dialog + + + True + vertical + + + True + 5 + 12 + + + True + 0 + gtk-dialog-info + 6 + + + False + 0 + + + + + True + 3 + 2 + 6 + 12 + + + 375 + True + True + automatic + automatic + in + + + True + True + False + + + + + 2 + + + + + True + True + automatic + automatic + in + + + True + True + False + word + + + + + 1 + 2 + + + + + True + vertical + 6 + start + + + _Snooze + True + True + True + True + refresh-icon + True + + + False + False + 0 + + + + + gtk-edit + True + True + True + True + True + + + False + False + 1 + + + + + gtk-print + True + True + True + True + True + + + False + False + 2 + + + + + _Dismiss + True + True + True + apply-icon + True + + + False + False + 3 + + + + + 1 + 2 + 1 + 2 + + + + + + + True + 2 + 2 + 6 + 6 + + + True + 0 + Snooze _time: + True + + + 1 + 2 + GTK_FILL + + + + + + True + 6 + + + True + True + + adjustment1 + 1 + + + False + False + 0 + + + + + True + days + + + False + False + 1 + + + + + + True + True + + adjustment2 + 1 + + + False + False + 2 + + + + + True + hours + + + False + False + 3 + + + + + + + True + True + + adjustment3 + 1 + + + False + False + 4 + + + + + True + minutes + + + False + False + 5 + + + + + 1 + 2 + 1 + 2 + GTK_FILL + + + + + True + 0 + location of appointment + + + 1 + 2 + GTK_FILL + + + + + + True + 0 + Location: + True + + + GTK_FILL + + + + + + 2 + 2 + 3 + + + + + + + + + 1 + + + + + 1 + + + + + True + end + + + Dismiss _All + True + True + True + False + close-icon + True + + + False + False + 0 + + + + + False + end + 0 + + + + + + dismiss-all-button + + + + True + gtk-close + + + True + gtk-refresh + + + True + gtk-apply + + diff --git a/calendar/alarm-notify/alarm-queue.c b/calendar/alarm-notify/alarm-queue.c new file mode 100644 index 0000000000..f7597338a7 --- /dev/null +++ b/calendar/alarm-notify/alarm-queue.c @@ -0,0 +1,2418 @@ +/* + * Alarm queueing engine + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#ifdef HAVE_CANBERRA +#include +#endif + +#include +#include +#include + +#ifdef HAVE_LIBNOTIFY +#include +#endif + +#include "alarm.h" +#include "alarm-notify-dialog.h" +#include "alarm-queue.h" +#include "alarm-notify.h" +#include "config-data.h" +#include "util.h" + +#include "calendar/gui/print.h" + +/* The dialog with alarm nofications */ +static AlarmNotificationsDialog *alarm_notifications_dialog = NULL; + +/* Whether the queueing system has been initialized */ +static gboolean alarm_queue_inited = FALSE; + +/* Clients we are monitoring for alarms */ +static GHashTable *client_alarms_hash = NULL; + +/* List of tray icons being displayed */ +static GList *tray_icons_list = NULL; + +/* Top Tray Image */ +static GtkStatusIcon *tray_icon = NULL; +static gint tray_blink_id = -1; +static gint tray_blink_countdown = 0; +static AlarmNotify *an; + +/* Structure that stores a client we are monitoring */ +typedef struct { + /* Monitored client */ + ECalClient *cal_client; + + /* The live view to the calendar */ + ECalClientView *view; + + /* Hash table of component UID -> CompQueuedAlarms. If an element is + * present here, then it means its cqa->queued_alarms contains at least + * one queued alarm. When all the alarms for a component have been + * dequeued, the CompQueuedAlarms structure is removed from the hash + * table. Thus a CQA exists <=> it has queued alarms. + */ + GHashTable *uid_alarms_hash; +} ClientAlarms; + +/* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the + * actual alarm instance structures. + */ +typedef struct { + /* The parent client alarms structure */ + ClientAlarms *parent_client; + + /* The component's UID */ + ECalComponentId *id; + + /* The actual component and its alarm instances */ + ECalComponentAlarms *alarms; + + /* List of QueuedAlarm structures */ + GSList *queued_alarms; + + /* Flags */ + gboolean expecting_update; +} CompQueuedAlarms; + +/* Pair of a queued alarm ID and the alarm trigger instance it refers to */ +typedef struct { + /* Alarm ID from alarm.h */ + gpointer alarm_id; + + /* Instance from our parent CompQueuedAlarms->alarms->alarms list */ + ECalComponentAlarmInstance *instance; + + /* original trigger of the instance from component */ + time_t orig_trigger; + + /* Whether this is a snoozed queued alarm or a normal one */ + guint snooze : 1; +} QueuedAlarm; + +/* Alarm ID for the midnight refresh function */ +static gpointer midnight_refresh_id = NULL; +static time_t midnight = 0; + +static void remove_client_alarms (ClientAlarms *ca); +static void display_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id, + gboolean use_description); +static void audio_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id); +static void mail_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id); +static void procedure_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id); +#ifdef HAVE_LIBNOTIFY +static void popup_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id, + gboolean use_description); +#endif +static void query_objects_modified_cb (ECalClientView *view, + const GSList *objects, + gpointer data); +static void query_objects_removed_cb (ECalClientView *view, + const GSList *uids, + gpointer data); + +static void update_cqa (CompQueuedAlarms *cqa, + ECalComponent *comp); +static void update_qa (ECalComponentAlarms *alarms, + QueuedAlarm *qa); +static void tray_list_remove_cqa (CompQueuedAlarms *cqa); +static void on_dialog_objs_removed_cb (ECalClientView *view, + const GSList *uids, + gpointer data); + +/* Alarm queue engine */ + +static void load_alarms_for_today (ClientAlarms *ca); +static void midnight_refresh_cb (gpointer alarm_id, + time_t trigger, + gpointer data); + +/* Simple asynchronous message dispatcher */ + +typedef struct _Message Message; +typedef void (*MessageFunc) (Message *msg); + +struct _Message { + MessageFunc func; +}; + +/* +static void +message_proxy (Message *msg) +{ + g_return_if_fail (msg->func != NULL); + * + msg->func (msg); +} + * +static gpointer +create_thread_pool (void) +{ + return g_thread_pool_new ((GFunc) message_proxy, NULL, 1, FALSE, NULL); +}*/ + +static void +message_push (Message *msg) +{ + /* This used be pushed through the thread pool. This fix is made to + * work-around the crashers in dbus due to threading. The threading + * is not completely removed as its better to have alarm daemon + * running in a thread rather than blocking main thread. This is + * the reason the creation of thread pool is commented out. */ + msg->func (msg); +} + +/* + * use a static ring-buffer so we can call this twice + * in a printf without getting nonsense results. + */ +static const gchar * +e_ctime (const time_t *timep) +{ + static gchar *buffer[4] = { 0, }; + static gint next = 0; + const gchar *ret; + + g_free (buffer[next]); + ret = buffer[next++] = g_strdup (ctime (timep)); + if (buffer[next - 1] && *buffer[next - 1]) { + gint len = strlen (buffer[next - 1]); + while (len > 0 && (buffer[next - 1][len - 1] == '\n' || + buffer[next - 1][len - 1] == '\r' || + g_ascii_isspace (buffer[next - 1][len - 1]))) + len--; + + buffer[next - 1][len - 1] = 0; + } + + if (next >= G_N_ELEMENTS (buffer)) + next = 0; + + return ret; +} + +/* Queues an alarm trigger for midnight so that we can load the next + * day's worth of alarms. */ +static void +queue_midnight_refresh (void) +{ + icaltimezone *zone; + + if (midnight_refresh_id != NULL) { + alarm_remove (midnight_refresh_id); + midnight_refresh_id = NULL; + } + + zone = config_data_get_timezone (); + midnight = time_day_end_with_zone (time (NULL), zone); + + debug (("Refresh at %s", e_ctime (&midnight))); + + midnight_refresh_id = alarm_add ( + midnight, midnight_refresh_cb, NULL, NULL); + if (!midnight_refresh_id) { + debug (("Could not setup the midnight refresh alarm")); + /* FIXME: what to do? */ + } +} + +/* Loads a client's alarms; called from g_hash_table_foreach() */ +static void +add_client_alarms_cb (gpointer key, + gpointer value, + gpointer data) +{ + ClientAlarms *ca = (ClientAlarms *) value; + + debug (("Adding %p", ca)); + + load_alarms_for_today (ca); +} + +struct _midnight_refresh_msg { + Message header; + gboolean remove; +}; + +/* Loads the alarms for the new day every midnight */ +static void +midnight_refresh_async (struct _midnight_refresh_msg *msg) +{ + debug (("...")); + + /* Re-load the alarms for all clients */ + g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL); + + /* Re-schedule the midnight update */ + if (msg->remove && midnight_refresh_id != NULL) { + debug (("Reschedule the midnight update")); + alarm_remove (midnight_refresh_id); + midnight_refresh_id = NULL; + } + + queue_midnight_refresh (); + + g_slice_free (struct _midnight_refresh_msg, msg); +} + +static void +midnight_refresh_cb (gpointer alarm_id, + time_t trigger, + gpointer data) +{ + struct _midnight_refresh_msg *msg; + + msg = g_slice_new0 (struct _midnight_refresh_msg); + msg->header.func = (MessageFunc) midnight_refresh_async; + msg->remove = TRUE; + + message_push ((Message *) msg); +} + +/* Looks up a client in the client alarms hash table */ +static ClientAlarms * +lookup_client (ECalClient *cal_client) +{ + return g_hash_table_lookup (client_alarms_hash, cal_client); +} + +/* Looks up a queued alarm based on its alarm ID */ +static QueuedAlarm * +lookup_queued_alarm (CompQueuedAlarms *cqa, + gpointer alarm_id) +{ + GSList *l; + QueuedAlarm *qa; + + qa = NULL; + + for (l = cqa->queued_alarms; l; l = l->next) { + qa = l->data; + if (qa->alarm_id == alarm_id) + return qa; + } + + /* not found, might have been updated/removed */ + return NULL; +} + +/* Removes an alarm from the list of alarms of a component. If the alarm was + * the last one listed for the component, it removes the component itself. + */ +static gboolean +remove_queued_alarm (CompQueuedAlarms *cqa, + gpointer alarm_id, + gboolean free_object, + gboolean remove_alarm) +{ + QueuedAlarm *qa = NULL; + GSList *l; + + debug (("...")); + + for (l = cqa->queued_alarms; l; l = l->next) { + qa = l->data; + if (qa->alarm_id == alarm_id) + break; + } + + if (!l) + return FALSE; + + cqa->queued_alarms = g_slist_delete_link (cqa->queued_alarms, l); + + if (remove_alarm) { + GError *error = NULL; + ECalComponentId *id; + + id = e_cal_component_get_id (cqa->alarms->comp); + if (id) { + cqa->expecting_update = TRUE; + e_cal_client_discard_alarm_sync ( + cqa->parent_client->cal_client, id->uid, + id->rid, qa->instance->auid, NULL, &error); + cqa->expecting_update = FALSE; + + if (error) { + if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) + g_warning ( + "%s: Failed to discard alarm: %s", + G_STRFUNC, error->message); + g_error_free (error); + } + e_cal_component_free_id (id); + } + } + + g_free (qa); + + /* If this was the last queued alarm for this component, remove the + * component itself. + */ + + if (cqa->queued_alarms != NULL) + return FALSE; + + debug (("Last Component. Removing CQA- Free=%d", free_object)); + if (free_object) { + cqa->id = NULL; + cqa->parent_client = NULL; + e_cal_component_alarms_free (cqa->alarms); + g_free (cqa); + } else { + e_cal_component_alarms_free (cqa->alarms); + cqa->alarms = NULL; + } + + return TRUE; +} + +/** + * has_known_notification: + * Test for notification method and returns if it knows it or not. + * @comp: Component with an alarm. + * @alarm_uid: ID of the alarm in the comp to test. + * + * Returns: %TRUE when we know the notification type, %FALSE otherwise. + */ +static gboolean +has_known_notification (ECalComponent *comp, + const gchar *alarm_uid) +{ + ECalComponentAlarm *alarm; + ECalComponentAlarmAction action; + + g_return_val_if_fail (comp != NULL, FALSE); + g_return_val_if_fail (alarm_uid != NULL, FALSE); + + alarm = e_cal_component_get_alarm (comp, alarm_uid); + if (!alarm) + return FALSE; + + e_cal_component_alarm_get_action (alarm, &action); + e_cal_component_alarm_free (alarm); + + switch (action) { + case E_CAL_COMPONENT_ALARM_AUDIO: + case E_CAL_COMPONENT_ALARM_DISPLAY: + case E_CAL_COMPONENT_ALARM_EMAIL: + case E_CAL_COMPONENT_ALARM_PROCEDURE: + return TRUE; + default: + break; + } + + return FALSE; +} + +/* Callback used when an alarm triggers */ +static void +alarm_trigger_cb (gpointer alarm_id, + time_t trigger, + gpointer data) +{ + CompQueuedAlarms *cqa; + ECalComponent *comp; + QueuedAlarm *qa; + ECalComponentAlarm *alarm; + ECalComponentAlarmAction action; + + cqa = data; + comp = cqa->alarms->comp; + + config_data_set_last_notification_time ( + cqa->parent_client->cal_client, trigger); + debug (("Setting Last notification time to %s", e_ctime (&trigger))); + + qa = lookup_queued_alarm (cqa, alarm_id); + if (!qa) + return; + + /* Decide what to do based on the alarm action. We use the trigger that + * is passed to us instead of the one from the instance structure + * because this may be a snoozed alarm instead of an original + * occurrence. + */ + + alarm = e_cal_component_get_alarm (comp, qa->instance->auid); + if (!alarm) + return; + + e_cal_component_alarm_get_action (alarm, &action); + e_cal_component_alarm_free (alarm); + + switch (action) { + case E_CAL_COMPONENT_ALARM_AUDIO: + audio_notification (trigger, cqa, alarm_id); + break; + + case E_CAL_COMPONENT_ALARM_DISPLAY: +#ifdef HAVE_LIBNOTIFY + popup_notification (trigger, cqa, alarm_id, TRUE); +#endif + display_notification (trigger, cqa, alarm_id, TRUE); + break; + + case E_CAL_COMPONENT_ALARM_EMAIL: + mail_notification (trigger, cqa, alarm_id); + break; + + case E_CAL_COMPONENT_ALARM_PROCEDURE: + procedure_notification (trigger, cqa, alarm_id); + break; + + default: + g_return_if_reached (); + } + debug (("Notification sent: %d", action)); +} + +/* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a + * particular client. Also puts the triggers in the alarm timer queue. + */ +static void +add_component_alarms (ClientAlarms *ca, + ECalComponentAlarms *alarms) +{ + ECalComponentId *id; + CompQueuedAlarms *cqa; + GSList *l; + + /* No alarms? */ + if (alarms == NULL || alarms->alarms == NULL) { + debug (("No alarms to add")); + if (alarms) + e_cal_component_alarms_free (alarms); + return; + } + + cqa = g_new (CompQueuedAlarms, 1); + cqa->parent_client = ca; + cqa->alarms = alarms; + cqa->expecting_update = FALSE; + + cqa->queued_alarms = NULL; + debug (("Creating CQA %p", cqa)); + + for (l = alarms->alarms; l; l = l->next) { + ECalComponentAlarmInstance *instance; + gpointer alarm_id; + QueuedAlarm *qa; + + instance = l->data; + + if (!has_known_notification (cqa->alarms->comp, instance->auid)) + continue; + + alarm_id = alarm_add ( + instance->trigger, alarm_trigger_cb, cqa, NULL); + if (!alarm_id) + continue; + + qa = g_new (QueuedAlarm, 1); + qa->alarm_id = alarm_id; + qa->instance = instance; + qa->orig_trigger = instance->trigger; + qa->snooze = FALSE; + + cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa); + } + + id = e_cal_component_get_id (alarms->comp); + + /* If we failed to add all the alarms, then we should get rid of the cqa */ + if (cqa->queued_alarms == NULL) { + e_cal_component_alarms_free (cqa->alarms); + cqa->alarms = NULL; + debug (("Failed to add all : %p", cqa)); + g_free (cqa); + return; + } + + cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms); + cqa->id = id; + debug (("Alarm added for %s", id->uid)); + g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa); +} + +/* Loads the alarms of a client for a given range of time */ +static void +load_alarms (ClientAlarms *ca, + time_t start, + time_t end) +{ + gchar *str_query, *iso_start, *iso_end; + GError *error = NULL; + + debug (("...")); + + iso_start = isodate_from_time_t (start); + if (!iso_start) + return; + + iso_end = isodate_from_time_t (end); + if (!iso_end) { + g_free (iso_start); + return; + } + + str_query = g_strdup_printf ( + "(has-alarms-in-range? (make-time \"%s\") " + "(make-time \"%s\"))", iso_start, iso_end); + g_free (iso_start); + g_free (iso_end); + + /* create the live query */ + if (ca->view) { + debug (("Disconnecting old queries")); + g_signal_handlers_disconnect_matched ( + ca->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca); + g_object_unref (ca->view); + ca->view = NULL; + } + + e_cal_client_get_view_sync ( + ca->cal_client, str_query, &ca->view, NULL, &error); + + if (error != NULL) { + g_warning ( + "%s: Could not get query for client: %s", + G_STRFUNC, error->message); + g_error_free (error); + } else { + debug (("Setting Call backs")); + + g_signal_connect ( + ca->view, "objects-added", + G_CALLBACK (query_objects_modified_cb), ca); + g_signal_connect ( + ca->view, "objects-modified", + G_CALLBACK (query_objects_modified_cb), ca); + g_signal_connect ( + ca->view, "objects-removed", + G_CALLBACK (query_objects_removed_cb), ca); + + e_cal_client_view_start (ca->view, &error); + + if (error != NULL) { + g_warning ( + "%s: Failed to start view: %s", + G_STRFUNC, error->message); + g_error_free (error); + } + } + + g_free (str_query); +} + +/* Loads today's remaining alarms for a client */ +static void +load_alarms_for_today (ClientAlarms *ca) +{ + time_t now, from, day_end, day_start; + icaltimezone *zone; + + now = time (NULL); + zone = config_data_get_timezone (); + day_start = time_day_begin_with_zone (now, zone); + + /* Make sure we don't miss some events from the last notification. + * We add 1 to the saved notification time to make the time ranges + * half-open; we do not want to display the "last" displayed alarm + * twice, once when it occurs and once when the alarm daemon restarts. + */ + from = config_data_get_last_notification_time (ca->cal_client) + 1; + if (from <= 0) + from = MAX (from, day_start); + + day_end = time_day_end_with_zone (now, zone); + debug (("From %s to %s", e_ctime (&from), e_ctime (&day_end))); + load_alarms (ca, from, day_end); +} + +/* Looks up a component's queued alarm structure in a client alarms structure */ +static CompQueuedAlarms * +lookup_comp_queued_alarms (ClientAlarms *ca, + const ECalComponentId *id) +{ + return g_hash_table_lookup (ca->uid_alarms_hash, id); +} + +static void +remove_alarms (CompQueuedAlarms *cqa, + gboolean free_object) +{ + GSList *l; + + debug (("Removing for %p", cqa)); + for (l = cqa->queued_alarms; l;) { + QueuedAlarm *qa; + + qa = l->data; + + /* Get the next element here because the list element will go + * away in remove_queued_alarm(). The qa will be freed there as + * well. + */ + l = l->next; + + alarm_remove (qa->alarm_id); + remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE); + } +} + +/* Removes a component an its alarms */ +static void +remove_comp (ClientAlarms *ca, + ECalComponentId *id) +{ + CompQueuedAlarms *cqa; + + debug (("Removing uid %s", id->uid)); + + if (id->rid && !(*(id->rid))) { + g_free (id->rid); + id->rid = NULL; + } + + cqa = lookup_comp_queued_alarms (ca, id); + if (!cqa) + return; + + /* If a component is present, then it means we must have alarms queued + * for it. + */ + g_return_if_fail (cqa->queued_alarms != NULL); + + debug (("Removing CQA %p", cqa)); + remove_alarms (cqa, TRUE); +} + +/* Called when a calendar component changes; we must reload its corresponding + * alarms. + */ +struct _query_msg { + Message header; + GSList *objects; + gpointer data; +}; + +static GSList * +duplicate_ical (const GSList *in_list) +{ + const GSList *l; + GSList *out_list = NULL; + for (l = in_list; l; l = l->next) { + out_list = g_slist_prepend ( + out_list, icalcomponent_new_clone (l->data)); + } + + return g_slist_reverse (out_list); +} + +static GSList * +duplicate_ecal (const GSList *in_list) +{ + const GSList *l; + GSList *out_list = NULL; + for (l = in_list; l; l = l->next) { + ECalComponentId *id, *old; + old = l->data; + id = g_new0 (ECalComponentId, 1); + id->uid = g_strdup (old->uid); + id->rid = g_strdup (old->rid); + out_list = g_slist_prepend (out_list, id); + } + + return g_slist_reverse (out_list); +} + +static gboolean +get_alarms_for_object (ECalClient *cal_client, + const ECalComponentId *id, + time_t start, + time_t end, + ECalComponentAlarms **alarms) +{ + icalcomponent *icalcomp; + ECalComponent *comp; + ECalComponentAlarmAction omit[] = {-1}; + + g_return_val_if_fail (cal_client != NULL, FALSE); + g_return_val_if_fail (id != NULL, FALSE); + g_return_val_if_fail (alarms != NULL, FALSE); + g_return_val_if_fail (start >= 0 && end >= 0, FALSE); + g_return_val_if_fail (start <= end, FALSE); + + if (!e_cal_client_get_object_sync ( + cal_client, id->uid, id->rid, &icalcomp, NULL, NULL)) + return FALSE; + + if (!icalcomp) + return FALSE; + + comp = e_cal_component_new (); + if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { + icalcomponent_free (icalcomp); + g_object_unref (comp); + return FALSE; + } + + *alarms = e_cal_util_generate_alarms_for_comp ( + comp, start, end, omit, e_cal_client_resolve_tzid_cb, + cal_client, e_cal_client_get_default_timezone (cal_client)); + + g_object_unref (comp); + + return TRUE; +} + +static void +query_objects_changed_async (struct _query_msg *msg) +{ + ClientAlarms *ca; + time_t from, day_end; + ECalComponentAlarms *alarms; + gboolean found; + icaltimezone *zone; + CompQueuedAlarms *cqa; + GSList *l; + GSList *objects; + + ca = msg->data; + objects = msg->objects; + + from = config_data_get_last_notification_time (ca->cal_client); + if (from == -1) + from = time (NULL); + else + from += 1; /* we add 1 to make sure the alarm is not displayed twice */ + + zone = config_data_get_timezone (); + + day_end = time_day_end_with_zone (time (NULL), zone); + + for (l = objects; l != NULL; l = l->next) { + ECalComponentId *id; + GSList *sl; + ECalComponent *comp = e_cal_component_new (); + + e_cal_component_set_icalcomponent (comp, l->data); + + id = e_cal_component_get_id (comp); + found = get_alarms_for_object (ca->cal_client, id, from, day_end, &alarms); + + if (!found) { + debug (("No Alarm found for client %p", ca->cal_client)); + tray_list_remove_cqa (lookup_comp_queued_alarms (ca, id)); + remove_comp (ca, id); + g_hash_table_remove (ca->uid_alarms_hash, id); + e_cal_component_free_id (id); + g_object_unref (comp); + comp = NULL; + continue; + } + + cqa = lookup_comp_queued_alarms (ca, id); + if (!cqa) { + debug (("No currently queued alarms for %s", id->uid)); + add_component_alarms (ca, alarms); + g_object_unref (comp); + comp = NULL; + continue; + } + + debug (("Alarm Already Exist for %s", id->uid)); + /* If the alarms or the alarms list is empty, + * remove it after updating the cqa structure. */ + if (alarms == NULL || alarms->alarms == NULL) { + + /* Update the cqa and its queued alarms + * for changes in summary and alarm_uid. */ + update_cqa (cqa, comp); + + if (alarms) + e_cal_component_alarms_free (alarms); + continue; + } + + /* if already in the list, just update it */ + remove_alarms (cqa, FALSE); + cqa->alarms = alarms; + cqa->queued_alarms = NULL; + + /* add the new alarms */ + for (sl = cqa->alarms->alarms; sl; sl = sl->next) { + ECalComponentAlarmInstance *instance; + gpointer alarm_id; + QueuedAlarm *qa; + + instance = sl->data; + + if (!has_known_notification (cqa->alarms->comp, instance->auid)) + continue; + + alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL); + if (!alarm_id) + continue; + + qa = g_new (QueuedAlarm, 1); + qa->alarm_id = alarm_id; + qa->instance = instance; + qa->snooze = FALSE; + qa->orig_trigger = instance->trigger; + cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa); + debug (("Adding %p to queue", qa)); + } + + cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms); + g_object_unref (comp); + comp = NULL; + } + g_slist_free (objects); + + g_slice_free (struct _query_msg, msg); +} + +static void +query_objects_modified_cb (ECalClientView *view, + const GSList *objects, + gpointer data) +{ + struct _query_msg *msg; + + msg = g_slice_new0 (struct _query_msg); + msg->header.func = (MessageFunc) query_objects_changed_async; + msg->objects = duplicate_ical (objects); + msg->data = data; + + message_push ((Message *) msg); +} + +/* Called when a calendar component is removed; we must delete its corresponding + * alarms. + */ +static void +query_objects_removed_async (struct _query_msg *msg) +{ + ClientAlarms *ca; + GSList *l; + GSList *objects; + + ca = msg->data; + objects = msg->objects; + + debug (("Removing %d objects", g_slist_length (objects))); + + for (l = objects; l != NULL; l = l->next) { + /* If the alarm is already triggered remove it. */ + tray_list_remove_cqa (lookup_comp_queued_alarms (ca, l->data)); + remove_comp (ca, l->data); + g_hash_table_remove (ca->uid_alarms_hash, l->data); + e_cal_component_free_id (l->data); + } + + g_slist_free (objects); + + g_slice_free (struct _query_msg, msg); +} + +static void +query_objects_removed_cb (ECalClientView *view, + const GSList *uids, + gpointer data) +{ + struct _query_msg *msg; + + msg = g_slice_new0 (struct _query_msg); + msg->header.func = (MessageFunc) query_objects_removed_async; + msg->objects = duplicate_ecal (uids); + msg->data = data; + + message_push ((Message *) msg); +} + +/* Notification functions */ + +/* Creates a snooze alarm based on an existing one. The snooze offset is + * compued with respect to the current time. + */ +static void +create_snooze (CompQueuedAlarms *cqa, + gpointer alarm_id, + gint snooze_mins) +{ + QueuedAlarm *orig_qa; + time_t t; + gpointer new_id; + + orig_qa = lookup_queued_alarm (cqa, alarm_id); + if (!orig_qa) + return; + + t = time (NULL); + t += snooze_mins * 60; + + new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL); + if (!new_id) { + debug (("Unable to schedule trigger for %s", e_ctime (&t))); + return; + } + + orig_qa->instance->trigger = t; + orig_qa->alarm_id = new_id; + orig_qa->snooze = TRUE; + debug (("Adding a alarm at %s", e_ctime (&t))); +} + +/* Launches a component editor for a component */ +static void +edit_component (ECalClient *cal_client, + ECalComponent *comp) +{ + ESource *source; + gchar *command_line; + const gchar *scheme; + const gchar *comp_uid; + const gchar *source_uid; + GError *error = NULL; + + /* XXX Don't we have a function to construct these URIs? + * How are other apps expected to know this stuff? */ + + source = e_client_get_source (E_CLIENT (cal_client)); + source_uid = e_source_peek_uid (source); + + e_cal_component_get_uid (comp, &comp_uid); + + switch (e_cal_client_get_source_type (cal_client)) { + case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: + scheme = "calendar:"; + break; + case E_CAL_CLIENT_SOURCE_TYPE_TASKS: + scheme = "task:"; + break; + case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: + scheme = "memo:"; + break; + default: + g_return_if_reached (); + } + + command_line = g_strdup_printf ( + "%s %s///?source-uid=%s&comp-uid=%s", + PACKAGE, scheme, source_uid, comp_uid); + + if (!g_spawn_command_line_async (command_line, &error)) { + g_critical ("%s", error->message); + g_error_free (error); + } + + g_free (command_line); +} + +static void +print_component (ECalClient *cal_client, + ECalComponent *comp) +{ + print_comp (comp, + cal_client, + config_data_get_timezone (), + config_data_get_24_hour_format (), + GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG); +} + +typedef struct { + gchar *summary; + gchar *description; + gchar *location; + gboolean blink_state; + gboolean snooze_set; + gint blink_id; + time_t trigger; + CompQueuedAlarms *cqa; + gpointer alarm_id; + ECalComponent *comp; + ECalClient *cal_client; + ECalClientView *view; + GdkPixbuf *image; + GtkTreeIter iter; +} TrayIconData; + +static void +free_tray_icon_data (TrayIconData *tray_data) +{ + g_return_if_fail (tray_data != NULL); + + if (tray_data->summary) { + g_free (tray_data->summary); + tray_data->summary = NULL; + } + + if (tray_data->description) { + g_free (tray_data->description); + tray_data->description = NULL; + } + + if (tray_data->location) { + g_free (tray_data->location); + tray_data->location = NULL; + } + + g_object_unref (tray_data->cal_client); + tray_data->cal_client = NULL; + + g_signal_handlers_disconnect_matched ( + tray_data->view, G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, on_dialog_objs_removed_cb, NULL); + g_object_unref (tray_data->view); + tray_data->view = NULL; + + g_object_unref (tray_data->comp); + tray_data->comp = NULL; + + tray_data->cqa = NULL; + tray_data->alarm_id = NULL; + tray_data->image = NULL; + + g_free (tray_data); +} + +static void +on_dialog_objs_removed_async (struct _query_msg *msg) +{ + TrayIconData *tray_data; + GSList *l, *objects; + ECalComponentId *our_id; + + debug (("...")); + + tray_data = msg->data; + objects = msg->objects; + + our_id = e_cal_component_get_id (tray_data->comp); + g_return_if_fail (our_id); + + for (l = objects; l != NULL; l = l->next) { + ECalComponentId *id = l->data; + + if (!id) + continue; + + if (g_strcmp0 (id->uid, our_id->uid) == 0&& g_strcmp0 (id->rid, our_id->rid) == 0) { + tray_data->cqa = NULL; + tray_data->alarm_id = NULL; + tray_icons_list = g_list_remove (tray_icons_list, tray_data); + tray_data = NULL; + } + + e_cal_component_free_id (id); + } + + e_cal_component_free_id (our_id); + g_slist_free (objects); + g_slice_free (struct _query_msg, msg); +} + +static void +on_dialog_objs_removed_cb (ECalClientView *view, + const GSList *uids, + gpointer data) +{ + struct _query_msg *msg; + + msg = g_slice_new0 (struct _query_msg); + msg->header.func = (MessageFunc) on_dialog_objs_removed_async; + msg->objects = duplicate_ecal (uids); + msg->data = data; + + message_push ((Message *) msg); +} + +struct _tray_cqa_msg { + Message header; + CompQueuedAlarms *cqa; +}; + +static void +tray_list_remove_cqa_async (struct _tray_cqa_msg *msg) +{ + CompQueuedAlarms *cqa = msg->cqa; + GList *list = tray_icons_list; + + debug (("Removing CQA %p from tray list", cqa)); + + while (list) { + TrayIconData *tray_data = list->data; + GList *tmp = list; + GtkTreeModel *model; + + list = list->next; + if (tray_data->cqa == cqa) { + debug (("Found")); + tray_icons_list = g_list_delete_link (tray_icons_list, tmp); + if (alarm_notifications_dialog) { + model = gtk_tree_view_get_model ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + gtk_list_store_remove (GTK_LIST_STORE (model), &(tray_data->iter)); + } + free_tray_icon_data (tray_data); + } + } + + debug (("%d alarms left", g_list_length (tray_icons_list))); + + if (alarm_notifications_dialog) { + if (!g_list_length (tray_icons_list)) { + gtk_widget_destroy (alarm_notifications_dialog->dialog); + g_free (alarm_notifications_dialog); + alarm_notifications_dialog = NULL; + } else { + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeSelection *sel; + + model = gtk_tree_view_get_model ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + gtk_tree_model_get_iter_first (model, &iter); + sel = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + gtk_tree_selection_select_iter (sel, &iter); + } + } + + g_slice_free (struct _tray_cqa_msg, msg); +} + +static void +tray_list_remove_cqa (CompQueuedAlarms *cqa) +{ + struct _tray_cqa_msg *msg; + + msg = g_slice_new0 (struct _tray_cqa_msg); + msg->header.func = (MessageFunc) tray_list_remove_cqa_async; + msg->cqa = cqa; + + message_push ((Message *) msg); +} + +/* Callback used from the alarm notify dialog */ +static void +tray_list_remove_async (Message *msg) +{ + GList *list = tray_icons_list; + + debug (("Removing %d alarms", g_list_length (list))); + while (list != NULL) { + + TrayIconData *tray_data = list->data; + + if (!tray_data->snooze_set) { + GList *temp = list->next; + gboolean status; + + tray_icons_list = g_list_remove_link (tray_icons_list, list); + status = remove_queued_alarm ( + tray_data->cqa, + tray_data->alarm_id, FALSE, TRUE); + if (status) { + g_hash_table_remove ( + tray_data->cqa->parent_client->uid_alarms_hash, + tray_data->cqa->id); + e_cal_component_free_id (tray_data->cqa->id); + g_free (tray_data->cqa); + } + free_tray_icon_data (tray_data); + tray_data = NULL; + g_list_free_1 (list); + if (tray_icons_list != list) /* List head is modified */ + list = tray_icons_list; + else + list = temp; + } else + list = list->next; + } + + g_slice_free (Message, msg); +} + +static void +tray_list_remove_icons (void) +{ + Message *msg; + + msg = g_slice_new0 (Message); + msg->func = tray_list_remove_async; + + message_push (msg); +} + +struct _tray_msg { + Message header; + TrayIconData *data; +}; + +static void +tray_list_remove_data_async (struct _tray_msg *msg) +{ + TrayIconData *tray_data = msg->data; + + debug (("Removing %p from tray list", tray_data)); + + tray_icons_list = g_list_remove_all (tray_icons_list, tray_data); + free_tray_icon_data (tray_data); + tray_data = NULL; + + g_slice_free (struct _tray_msg, msg); +} + +static void +tray_list_remove_data (TrayIconData *data) +{ + struct _tray_msg *msg; + + msg = g_slice_new0 (struct _tray_msg); + msg->header.func = (MessageFunc) tray_list_remove_data_async; + msg->data = data; + + message_push ((Message *) msg); +} + +static void +notify_dialog_cb (AlarmNotifyResult result, + gint snooze_mins, + gpointer data) +{ + TrayIconData *tray_data = data; + + debug (("Received from dialog")); + + g_signal_handlers_disconnect_matched ( + tray_data->view, G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, on_dialog_objs_removed_cb, NULL); + + switch (result) { + case ALARM_NOTIFY_SNOOZE: + debug (("Creating a snooze")); + create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins); + tray_data->snooze_set = TRUE; + tray_list_remove_data (tray_data); + if (alarm_notifications_dialog) { + GtkTreeSelection *selection = + gtk_tree_view_get_selection ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + GtkTreeIter iter; + GtkTreeModel *model = NULL; + + /* We can also use tray_data->iter */ + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + if (!gtk_tree_model_get_iter_first (model, &iter)) { + /* We removed the last one */ + gtk_widget_destroy (alarm_notifications_dialog->dialog); + g_free (alarm_notifications_dialog); + alarm_notifications_dialog = NULL; + } else { + /* Select the first */ + gtk_tree_selection_select_iter (selection, &iter); + } + } + + } + + break; + + case ALARM_NOTIFY_EDIT: + edit_component (tray_data->cal_client, tray_data->comp); + + break; + + case ALARM_NOTIFY_PRINT: + print_component (tray_data->cal_client, tray_data->comp); + + break; + + case ALARM_NOTIFY_DISMISS: + if (alarm_notifications_dialog) { + GtkTreeModel *model; + + model = gtk_tree_view_get_model ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + gtk_list_store_remove (GTK_LIST_STORE (model), &tray_data->iter); + } + break; + + case ALARM_NOTIFY_CLOSE: + debug (("Dialog close")); + if (alarm_notifications_dialog) { + GtkTreeIter iter; + GtkTreeModel *model = + gtk_tree_view_get_model ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + gboolean valid = gtk_tree_model_get_iter_first (model, &iter); + + /* Maybe we should warn about this first? */ + while (valid) { + valid = gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + } + + gtk_widget_destroy (alarm_notifications_dialog->dialog); + g_free (alarm_notifications_dialog); + alarm_notifications_dialog = NULL; + + /* Task to remove the tray icons */ + tray_list_remove_icons (); + } + + break; + + default: + g_return_if_reached (); + } + + return; +} + +static void +remove_tray_icon (void) +{ + if (tray_blink_id > -1) + g_source_remove (tray_blink_id); + tray_blink_id = -1; + + if (tray_icon) { + gtk_status_icon_set_visible (tray_icon, FALSE); + g_object_unref (tray_icon); + tray_icon = NULL; + } +} + +/* Callbacks. */ +static gboolean +open_alarm_dialog (TrayIconData *tray_data) +{ + QueuedAlarm *qa; + + debug (("...")); + qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id); + if (qa) { + remove_tray_icon (); + + if (!alarm_notifications_dialog) + alarm_notifications_dialog = notified_alarms_dialog_new (); + + if (alarm_notifications_dialog) { + + GtkTreeSelection *selection = NULL; + + selection = gtk_tree_view_get_selection ( + GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); + + tray_data->iter = add_alarm_to_notified_alarms_dialog ( + alarm_notifications_dialog, + tray_data->trigger, + qa->instance->occur_start, + qa->instance->occur_end, + e_cal_component_get_vtype (tray_data->comp), + tray_data->summary, + tray_data->description, + tray_data->location, + notify_dialog_cb, tray_data); + + gtk_tree_selection_select_iter ( + selection, &tray_data->iter); + + } + + } else { + remove_tray_icon (); + } + + return TRUE; +} + +static gint +tray_icon_clicked_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + if (event->type == GDK_BUTTON_PRESS) { + debug (("left click and %d alarms", g_list_length (tray_icons_list))); + if (event->button == 1 && g_list_length (tray_icons_list) > 0) { + GList *tmp; + for (tmp = tray_icons_list; tmp; tmp = tmp->next) { + open_alarm_dialog (tmp->data); + } + + return TRUE; + } else if (event->button == 3) { + debug (("right click")); + + remove_tray_icon (); + return TRUE; + } + } + + return FALSE; +} + +static void +icon_activated (GtkStatusIcon *icon) +{ + GdkEventButton event; + + event.type = GDK_BUTTON_PRESS; + event.button = 1; + event.time = gtk_get_current_event_time (); + + tray_icon_clicked_cb (NULL, &event, NULL); +} + +static void +popup_menu (GtkStatusIcon *icon, + guint button, + guint activate_time) +{ + if (button == 3) { + /* right click */ + GdkEventButton event; + + event.type = GDK_BUTTON_PRESS; + event.button = 3; + event.time = gtk_get_current_event_time (); + + tray_icon_clicked_cb (NULL, &event, NULL); + } +} + +static gboolean +tray_icon_blink_cb (gpointer data) +{ + static gboolean tray_blink_state = FALSE; + const gchar *icon_name; + + tray_blink_countdown--; + tray_blink_state = !tray_blink_state; + + if (tray_blink_state || tray_blink_countdown <= 0) + icon_name = "appointment-missed"; + else + icon_name = "appointment-soon"; + + if (tray_icon) + gtk_status_icon_set_from_icon_name (tray_icon, icon_name); + + if (tray_blink_countdown <= 0) + tray_blink_id = -1; + + return tray_blink_countdown > 0; +} + +/* Add a new data to tray list */ + +static void +tray_list_add_async (struct _tray_msg *msg) +{ + tray_icons_list = g_list_prepend (tray_icons_list, msg->data); + + g_slice_free (struct _tray_msg, msg); +} + +static void +tray_list_add_new (TrayIconData *data) +{ + struct _tray_msg *msg; + + msg = g_slice_new0 (struct _tray_msg); + msg->header.func = (MessageFunc) tray_list_add_async; + msg->data = data; + + message_push ((Message *) msg); +} + +/* Performs notification of a display alarm */ +static void +display_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id, + gboolean use_description) +{ + QueuedAlarm *qa; + ECalComponent *comp; + const gchar *summary, *description, *location; + TrayIconData *tray_data; + ECalComponentText text; + GSList *text_list; + gchar *str, *start_str, *end_str, *alarm_str, *time_str; + icaltimezone *current_zone; + ECalComponentOrganizer organiser; + + debug (("...")); + + comp = cqa->alarms->comp; + qa = lookup_queued_alarm (cqa, alarm_id); + if (!qa) + return; + + /* get a sensible description for the event */ + e_cal_component_get_summary (comp, &text); + e_cal_component_get_organizer (comp, &organiser); + + if (text.value) + summary = text.value; + else + summary = _("No summary available."); + + e_cal_component_get_description_list (comp, &text_list); + + if (text_list) { + text = *((ECalComponentText *) text_list->data); + if (text.value) + description = text.value; + else + description = _("No description available."); + } else { + description = _("No description available."); + } + + e_cal_component_free_text_list (text_list); + + e_cal_component_get_location (comp, &location); + + if (!location) + location = _("No location information available."); + + /* create the tray icon */ + if (tray_icon == NULL) { + tray_icon = gtk_status_icon_new (); + gtk_status_icon_set_from_icon_name ( + tray_icon, "appointment-soon"); + g_signal_connect ( + tray_icon, "activate", + G_CALLBACK (icon_activated), NULL); + g_signal_connect ( + tray_icon, "popup-menu", + G_CALLBACK (popup_menu), NULL); + } + + current_zone = config_data_get_timezone (); + alarm_str = timet_to_str_with_zone (trigger, current_zone); + start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); + end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); + time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); + + str = g_strdup_printf ("%s\n%s %s", + summary, start_str, time_str); + + /* create the private structure */ + tray_data = g_new0 (TrayIconData, 1); + tray_data->summary = g_strdup (summary); + tray_data->description = g_strdup (description); + tray_data->location = g_strdup (location); + tray_data->trigger = trigger; + tray_data->cqa = cqa; + tray_data->alarm_id = alarm_id; + tray_data->comp = g_object_ref (e_cal_component_clone (comp)); + tray_data->cal_client = cqa->parent_client->cal_client; + tray_data->view = g_object_ref (cqa->parent_client->view); + tray_data->blink_state = FALSE; + tray_data->snooze_set = FALSE; + g_object_ref (tray_data->cal_client); + + /* Task to add tray_data to the global tray_icon_list */ + tray_list_add_new (tray_data); + + if (g_list_length (tray_icons_list) > 1) { + gchar *tip; + + tip = g_strdup_printf (ngettext ( + "You have %d reminder", "You have %d reminders", + g_list_length (tray_icons_list)), + g_list_length (tray_icons_list)); + gtk_status_icon_set_tooltip_text (tray_icon, tip); + } + else { + gtk_status_icon_set_tooltip_text (tray_icon, str); + } + + g_free (start_str); + g_free (end_str); + g_free (alarm_str); + g_free (time_str); + g_free (str); + + g_signal_connect ( + tray_data->view, "objects_removed", + G_CALLBACK (on_dialog_objs_removed_cb), tray_data); + + /* FIXME: We should remove this check */ + if (!config_data_get_notify_with_tray ()) { + tray_blink_id = -1; + open_alarm_dialog (tray_data); + if (alarm_notifications_dialog) + gtk_window_stick (GTK_WINDOW ( + alarm_notifications_dialog->dialog)); + } else { + if (tray_blink_id == -1) { + tray_blink_countdown = 30; + tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data); + } + } +} + +#ifdef HAVE_LIBNOTIFY +static void +popup_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id, + gboolean use_description) +{ + QueuedAlarm *qa; + ECalComponent *comp; + const gchar *summary, *location; + ECalComponentText text; + gchar *str, *start_str, *end_str, *alarm_str, *time_str; + icaltimezone *current_zone; + ECalComponentOrganizer organiser; + NotifyNotification *n; + gchar *body; + + debug (("...")); + + comp = cqa->alarms->comp; + qa = lookup_queued_alarm (cqa, alarm_id); + if (!qa) + return; + if (!notify_is_initted ()) + notify_init("Evolution Alarm Notify"); + + /* get a sensible description for the event */ + e_cal_component_get_summary (comp, &text); + e_cal_component_get_organizer (comp, &organiser); + + if (text.value) + summary = text.value; + else + summary = _("No summary available."); + + e_cal_component_get_location (comp, &location); + + /* create the tray icon */ + + current_zone = config_data_get_timezone (); + alarm_str = timet_to_str_with_zone (trigger, current_zone); + start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); + end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); + time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); + + str = g_strdup_printf ("%s %s", + start_str, time_str); + + if (organiser.cn) { + if (location) + body = g_strdup_printf ( + "%s\n%s %s\n%s %s", + organiser.cn, _("Location:"), + location, start_str, time_str); + else + body = g_strdup_printf ( + "%s\n%s %s", + organiser.cn, start_str, time_str); + } + else { + if (location) + body = g_strdup_printf ( + "%s %s\n%s %s", _("Location:"), + location, start_str, time_str); + else + body = g_strdup_printf ( + "%s %s", start_str, time_str); + } + +#ifdef HAVE_LIBNOTIFY_07 + n = notify_notification_new (summary, body, "appointment-soon"); +#else + n = notify_notification_new (summary, body, "appointment-soon", NULL); +#endif /* HAVE_LIBNOTIFY_07 */ + if (!notify_notification_show (n, NULL)) + g_warning ("Could not send notification to daemon\n"); + + /* create the private structure */ + g_free (start_str); + g_free (end_str); + g_free (alarm_str); + g_free (time_str); + g_free (str); + +} +#endif + +/* Performs notification of an audio alarm */ +static void +audio_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id) +{ + QueuedAlarm *qa; + ECalComponent *comp; + ECalComponentAlarm *alarm; + icalattach *attach; + gint flag = 0; + + debug (("...")); + + comp = cqa->alarms->comp; + qa = lookup_queued_alarm (cqa, alarm_id); + if (!qa) + return; + + alarm = e_cal_component_get_alarm (comp, qa->instance->auid); + g_return_if_fail (alarm != NULL); + + e_cal_component_alarm_get_attach (alarm, &attach); + e_cal_component_alarm_free (alarm); + + if (attach && icalattach_get_is_url (attach)) { + const gchar *url; + + url = icalattach_get_url (attach); + if (url && *url) { + gchar *filename; + GError *error = NULL; + + filename = g_filename_from_uri (url, NULL, &error); + + if (error != NULL) { + g_warning ("%s: %s", G_STRFUNC, error->message); + g_error_free (error); + } else if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) { +#ifdef HAVE_CANBERRA + flag = 1; + ca_context_play ( + ca_gtk_context_get (), 0, + CA_PROP_MEDIA_FILENAME, filename, NULL); +#endif + } + + g_free (filename); + } + } + + if (!flag) + gdk_beep (); + + if (attach) + icalattach_unref (attach); + +} + +/* Performs notification of a mail alarm */ +static void +mail_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id) +{ + GtkWidget *container; + GtkWidget *dialog; + GtkWidget *label; + + /* FIXME */ + + debug (("...")); + + if (!e_client_check_capability ( + E_CLIENT (cqa->parent_client->cal_client), + CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS)) + return; + + dialog = gtk_dialog_new_with_buttons ( + _("Warning"), NULL, 0, + GTK_STOCK_OK, GTK_RESPONSE_CANCEL, + NULL); + label = gtk_label_new ( + _("Evolution does not support calendar reminders with\n" + "email notifications yet, but this reminder was\n" + "configured to send an email. Evolution will display\n" + "a normal reminder dialog box instead.")); + gtk_widget_show (label); + + container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/* Performs notification of a procedure alarm */ +static gboolean +procedure_notification_dialog (const gchar *cmd, + const gchar *url) +{ + GtkWidget *container; + GtkWidget *dialog; + GtkWidget *label; + GtkWidget *checkbox; + gchar *str; + gint btn; + + debug (("...")); + + if (config_data_is_blessed_program (url)) + return TRUE; + + dialog = gtk_dialog_new_with_buttons ( + _("Warning"), NULL, 0, + GTK_STOCK_NO, GTK_RESPONSE_CANCEL, + GTK_STOCK_YES, GTK_RESPONSE_OK, + NULL); + + str = g_strdup_printf ( + _("An Evolution Calendar reminder is about to trigger. " + "This reminder is configured to run the following program:\n\n" + " %s\n\n" + "Are you sure you want to run this program?"), + cmd); + label = gtk_label_new (str); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); + gtk_widget_show (label); + + container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); + g_free (str); + + checkbox = gtk_check_button_new_with_label + (_("Do not ask me about this program again.")); + gtk_widget_show (checkbox); + gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4); + + /* Run the dialog */ + btn = gtk_dialog_run (GTK_DIALOG (dialog)); + if (btn == GTK_RESPONSE_OK && + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox))) + config_data_save_blessed_program (url); + gtk_widget_destroy (dialog); + + return (btn == GTK_RESPONSE_OK); +} + +static void +procedure_notification (time_t trigger, + CompQueuedAlarms *cqa, + gpointer alarm_id) +{ + QueuedAlarm *qa; + ECalComponent *comp; + ECalComponentAlarm *alarm; + ECalComponentText description; + icalattach *attach; + const gchar *url; + gchar *cmd; + gboolean result = TRUE; + + debug (("...")); + + comp = cqa->alarms->comp; + qa = lookup_queued_alarm (cqa, alarm_id); + if (!qa) + return; + + alarm = e_cal_component_get_alarm (comp, qa->instance->auid); + g_return_if_fail (alarm != NULL); + + e_cal_component_alarm_get_attach (alarm, &attach); + e_cal_component_alarm_get_description (alarm, &description); + e_cal_component_alarm_free (alarm); + + /* If the alarm has no attachment, simply display a notification dialog. */ + if (!attach) + goto fallback; + + if (!icalattach_get_is_url (attach)) { + icalattach_unref (attach); + goto fallback; + } + + url = icalattach_get_url (attach); + g_return_if_fail (url != NULL); + + /* Ask for confirmation before executing the stuff */ + if (description.value) + cmd = g_strconcat (url, " ", description.value, NULL); + else + cmd = (gchar *) url; + + if (procedure_notification_dialog (cmd, url)) + result = g_spawn_command_line_async (cmd, NULL); + + if (cmd != (gchar *) url) + g_free (cmd); + + icalattach_unref (attach); + + /* Fall back to display notification if we got an error */ + if (result == FALSE) + goto fallback; + + return; + + fallback: + + display_notification (trigger, cqa, alarm_id, FALSE); +} + +static gboolean +check_midnight_refresh (gpointer user_data) +{ + time_t new_midnight; + icaltimezone *zone; + + debug (("...")); + + zone = config_data_get_timezone (); + new_midnight = time_day_end_with_zone (time (NULL), zone); + + if (new_midnight > midnight) { + struct _midnight_refresh_msg *msg; + + msg = g_slice_new0 (struct _midnight_refresh_msg); + msg->header.func = (MessageFunc) midnight_refresh_async; + msg->remove = FALSE; + + message_push ((Message *) msg); + } + + return TRUE; +} + +/** + * alarm_queue_init: + * + * Initializes the alarm queueing system. This should be called near the + * beginning of the program. + **/ +void +alarm_queue_init (gpointer data) +{ + an = data; + g_return_if_fail (alarm_queue_inited == FALSE); + + debug (("...")); + + client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + queue_midnight_refresh (); + + if (config_data_get_last_notification_time (NULL) == -1) { + time_t tmval = time_day_begin (time (NULL)); + debug (("Setting last notification time to %s", e_ctime (&tmval))); + config_data_set_last_notification_time (NULL, tmval); + } + + /* install timeout handler (every 30 mins) for not missing the midnight refresh */ + g_timeout_add_seconds (1800, (GSourceFunc) check_midnight_refresh, NULL); + +#ifdef HAVE_LIBNOTIFY + notify_init("Evolution Alarms"); +#endif + + alarm_queue_inited = TRUE; +} + +static gboolean +free_client_alarms_cb (gpointer key, + gpointer value, + gpointer user_data) +{ + ClientAlarms *ca = value; + + debug (("ca=%p", ca)); + + if (ca) { + remove_client_alarms (ca); + if (ca->cal_client) { + debug (("Disconnecting Client")); + + g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, ca); + g_object_unref (ca->cal_client); + } + + if (ca->view) { + debug (("Disconnecting Query")); + + g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, ca); + g_object_unref (ca->view); + } + + g_hash_table_destroy (ca->uid_alarms_hash); + + g_free (ca); + return TRUE; + } + + return FALSE; +} + +/** + * alarm_queue_done: + * + * Shuts down the alarm queueing system. This should be called near the end + * of the program. All the monitored calendar clients should already have been + * unregistered with alarm_queue_remove_client(). + **/ +void +alarm_queue_done (void) +{ + g_return_if_fail (alarm_queue_inited); + + /* All clients must be unregistered by now */ + g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0); + + debug (("...")); + + g_hash_table_foreach_remove ( + client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL); + g_hash_table_destroy (client_alarms_hash); + client_alarms_hash = NULL; + + if (midnight_refresh_id != NULL) { + alarm_remove (midnight_refresh_id); + midnight_refresh_id = NULL; + } + + alarm_queue_inited = FALSE; +} + +static gboolean +compare_ids (gpointer a, + gpointer b) +{ + ECalComponentId *id, *id1; + + id = a; + id1 = b; + if (id->uid != NULL && id1->uid != NULL) { + if (g_str_equal (id->uid, id1->uid)) { + + if (id->rid && id1->rid) + return g_str_equal (id->rid, id1->rid); + else if (!(id->rid && id1->rid)) + return TRUE; + } + } + return FALSE; +} + +static guint +hash_ids (gpointer a) +{ + ECalComponentId *id =a; + + return g_str_hash (id->uid); +} + +struct _alarm_client_msg { + Message header; + ECalClient *cal_client; +}; + +static void +alarm_queue_add_async (struct _alarm_client_msg *msg) +{ + ClientAlarms *ca; + ECalClient *cal_client = msg->cal_client; + + g_return_if_fail (alarm_queue_inited); + g_return_if_fail (cal_client != NULL); + g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); + + ca = lookup_client (cal_client); + if (ca) { + /* We already have it. Unref the passed one*/ + g_object_unref (cal_client); + return; + } + + debug (("client=%p", cal_client)); + + ca = g_new (ClientAlarms, 1); + + ca->cal_client = cal_client; + ca->view = NULL; + + g_hash_table_insert (client_alarms_hash, cal_client, ca); + + ca->uid_alarms_hash = g_hash_table_new ( + (GHashFunc) hash_ids, (GEqualFunc) compare_ids); + + load_alarms_for_today (ca); + + g_slice_free (struct _alarm_client_msg, msg); +} + +/** + * alarm_queue_add_client: + * @cal_client: A calendar client. + * + * Adds a calendar client to the alarm queueing system. Alarm trigger + * notifications will be presented at the appropriate times. The client should + * be removed with alarm_queue_remove_client() when receiving notifications + * from it is no longer desired. + * + * A client can be added any number of times to the alarm queueing system, + * but any single alarm trigger will only be presented once for a particular + * client. The client must still be removed the same number of times from the + * queueing system when it is no longer wanted. + **/ +void +alarm_queue_add_client (ECalClient *cal_client) +{ + struct _alarm_client_msg *msg; + + msg = g_slice_new0 (struct _alarm_client_msg); + msg->header.func = (MessageFunc) alarm_queue_add_async; + msg->cal_client = g_object_ref (cal_client); + + message_push ((Message *) msg); +} + +/* Removes a component an its alarms */ +static void +remove_cqa (ClientAlarms *ca, + ECalComponentId *id, + CompQueuedAlarms *cqa) +{ + + /* If a component is present, then it means we must have alarms queued + * for it. + */ + g_return_if_fail (cqa->queued_alarms != NULL); + + debug (("removing %d alarms", g_slist_length (cqa->queued_alarms))); + remove_alarms (cqa, TRUE); +} + +static gboolean +remove_comp_by_id (gpointer key, + gpointer value, + gpointer userdata) +{ + + ClientAlarms *ca = (ClientAlarms *) userdata; + + debug (("...")); + +/* if (!g_hash_table_size (ca->uid_alarms_hash)) */ +/* return; */ + + remove_cqa (ca, (ECalComponentId *) key, (CompQueuedAlarms *) value); + + return TRUE; +} + +/* Removes all the alarms queued for a particular calendar client */ +static void +remove_client_alarms (ClientAlarms *ca) +{ + debug (("size %d", g_hash_table_size (ca->uid_alarms_hash))); + + g_hash_table_foreach_remove ( + ca->uid_alarms_hash, (GHRFunc) remove_comp_by_id, ca); + + /* The hash table should be empty now */ + g_return_if_fail (g_hash_table_size (ca->uid_alarms_hash) == 0); +} + +/** + * alarm_queue_remove_client: + * @client: A calendar client. + * + * Removes a calendar client from the alarm queueing system. + **/ +static void +alarm_queue_remove_async (struct _alarm_client_msg *msg) +{ + ClientAlarms *ca; + ECalClient *cal_client = msg->cal_client; + + g_return_if_fail (alarm_queue_inited); + g_return_if_fail (cal_client != NULL); + g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); + + ca = lookup_client (cal_client); + g_return_if_fail (ca != NULL); + + debug (("...")); + remove_client_alarms (ca); + + /* Clean up */ + if (ca->cal_client) { + debug (("Disconnecting Client")); + + g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, ca); + g_object_unref (ca->cal_client); + ca->cal_client = NULL; + } + + if (ca->view) { + debug (("Disconnecting Query")); + + g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, ca); + g_object_unref (ca->view); + ca->view = NULL; + } + + g_hash_table_destroy (ca->uid_alarms_hash); + ca->uid_alarms_hash = NULL; + + g_free (ca); + + g_hash_table_remove (client_alarms_hash, cal_client); + + g_slice_free (struct _alarm_client_msg, msg); +} + +/** alarm_queue_remove_client + * + * asynchronously remove client from alarm queue. + * @cal_client: Client to remove. + * @immediately: Indicates whether use thread or do it right now. + */ + +void +alarm_queue_remove_client (ECalClient *cal_client, + gboolean immediately) +{ + struct _alarm_client_msg *msg; + + msg = g_slice_new0 (struct _alarm_client_msg); + msg->header.func = (MessageFunc) alarm_queue_remove_async; + msg->cal_client = cal_client; + + if (immediately) { + alarm_queue_remove_async (msg); + } else + message_push ((Message *) msg); +} + +/* Update non-time related variables for various structures on modification + * of an existing component to be called only from query_objects_changed_cb */ +static void +update_cqa (CompQueuedAlarms *cqa, + ECalComponent *newcomp) +{ + ECalComponent *oldcomp; + ECalComponentAlarms *alarms = NULL; + GSList *qa_list; /* List of current QueuedAlarms corresponding to cqa */ + time_t from, to; + icaltimezone *zone; + ECalComponentAlarmAction omit[] = {-1}; + + oldcomp = cqa->alarms->comp; + + zone = config_data_get_timezone (); + from = time_day_begin_with_zone (time (NULL), zone); + to = time_day_end_with_zone (time (NULL), zone); + + debug (("Generating alarms between %s and %s", e_ctime (&from), e_ctime (&to))); + alarms = e_cal_util_generate_alarms_for_comp ( + newcomp, from, to, omit, e_cal_client_resolve_tzid_cb, + cqa->parent_client->cal_client, zone); + + /* Update auids in Queued Alarms*/ + for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) { + QueuedAlarm *qa = qa_list->data; + gchar *check_auid = (gchar *) qa->instance->auid; + ECalComponentAlarm *alarm; + + alarm = e_cal_component_get_alarm (newcomp, check_auid); + if (alarm) { + e_cal_component_alarm_free (alarm); + continue; + } else { + alarm = e_cal_component_get_alarm (oldcomp, check_auid); + if (alarm) { /* Need to update QueuedAlarms */ + e_cal_component_alarm_free (alarm); + if (alarms == NULL) { + debug (("No alarms found in the modified component")); + break; + } + update_qa (alarms, qa); + } + else + g_warning ("Failed in auid lookup for old component also\n"); + } + } + + /* Update the actual component stored in CompQueuedAlarms structure */ + g_object_unref (cqa->alarms->comp); + cqa->alarms->comp = newcomp; + if (alarms != NULL ) + e_cal_component_alarms_free (alarms); +} + +static void +update_qa (ECalComponentAlarms *alarms, + QueuedAlarm *qa) +{ + ECalComponentAlarmInstance *al_inst; + GSList *instance_list; + + debug (("...")); + for (instance_list = alarms->alarms; + instance_list; + instance_list = instance_list->next) { + al_inst = instance_list->data; + /* FIXME If two or more alarm instances (audio, note) + * for same component have same trigger... */ + if (al_inst->trigger == qa->orig_trigger) { + g_free ((gchar *) qa->instance->auid); + qa->instance->auid = g_strdup (al_inst->auid); + break; + } + } +} diff --git a/calendar/alarm-notify/alarm-queue.h b/calendar/alarm-notify/alarm-queue.h new file mode 100644 index 0000000000..e43027bd60 --- /dev/null +++ b/calendar/alarm-notify/alarm-queue.h @@ -0,0 +1,37 @@ +/* + * + * Evolution calendar - Alarm queueing engine + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef ALARM_QUEUE_H +#define ALARM_QUEUE_H + +#include + +void alarm_queue_init (gpointer); +void alarm_queue_done (void); + +void alarm_queue_add_client (ECalClient *cal_client); +void alarm_queue_remove_client (ECalClient *cal_client, gboolean immediately); + +#endif diff --git a/calendar/alarm-notify/alarm.c b/calendar/alarm-notify/alarm.c new file mode 100644 index 0000000000..2797d44b0a --- /dev/null +++ b/calendar/alarm-notify/alarm.c @@ -0,0 +1,324 @@ +/* + * Evolution calendar - Low-level alarm timer mechanism + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Miguel de Icaza + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "alarm.h" +#include "config-data.h" + +/* Our glib timeout */ +static guint timeout_id; + +/* The list of pending alarms */ +static GList *alarms = NULL; + +/* A queued alarm structure */ +typedef struct { + time_t trigger; + AlarmFunction alarm_fn; + gpointer data; + AlarmDestroyNotify destroy_notify_fn; +} AlarmRecord; + +static void setup_timeout (void); + +/* Removes the head alarm from the queue. Does not touch the timeout_id. */ +static void +pop_alarm (void) +{ + AlarmRecord *ar; + GList *l; + + if (!alarms) { + g_warning ("Nothing to pop from the alarm queue"); + return; + } + + ar = alarms->data; + + l = alarms; + alarms = g_list_delete_link (alarms, l); + + g_free (ar); +} + +/* Callback from the alarm timeout */ +static gboolean +alarm_ready_cb (gpointer data) +{ + time_t now; + + if (!alarms) { + g_warning ("Alarm triggered, but no alarm present\n"); + return FALSE; + } + + timeout_id = 0; + + now = time (NULL); + + debug (("Alarm callback!")); + while (alarms) { + AlarmRecord *notify_id, *ar; + AlarmRecord ar_copy; + + ar = alarms->data; + + if (ar->trigger > now) + break; + + debug (("Process alarm with trigger %lu", ar->trigger)); + notify_id = ar; + + ar_copy = *ar; + ar = &ar_copy; + + /* This will free the original AlarmRecord; + * that's why we copy it. */ + pop_alarm (); + + (* ar->alarm_fn) (notify_id, ar->trigger, ar->data); + + if (ar->destroy_notify_fn) + (* ar->destroy_notify_fn) (notify_id, ar->data); + } + + /* We need this check because one of the alarm_fn above may have + * re-entered and added an alarm of its own, so the timer will + * already be set up. + */ + if (alarms) + setup_timeout (); + + return FALSE; +} + +/* Sets up a timeout for the next minute. We do not need to be concerned with + * timezones here, as this is just a periodic check on the alarm queue. + */ +static void +setup_timeout (void) +{ + const AlarmRecord *ar; + guint diff; + time_t now; + + if (!alarms) { + g_warning ("No alarm to setup\n"); + return; + } + + ar = alarms->data; + + /* Remove the existing time out */ + if (timeout_id != 0) { + g_source_remove (timeout_id); + timeout_id = 0; + } + + /* Ensure that if the trigger managed to get behind the + * current time we timeout immediately */ + diff = MAX (0, ar->trigger - time (NULL)); + now = time (NULL); + + /* Add the time out */ + debug (("Setting timeout for %d.%2d (from now) %lu %lu", + diff / 60, diff % 60, ar->trigger, now)); + debug ((" %s", ctime (&ar->trigger))); + debug ((" %s", ctime (&now))); + timeout_id = g_timeout_add_seconds (diff, alarm_ready_cb, NULL); + +} + +/* Used from g_list_insert_sorted(); compares the + * trigger times of two AlarmRecord structures. */ +static gint +compare_alarm_by_time (gconstpointer a, + gconstpointer b) +{ + const AlarmRecord *ara = a; + const AlarmRecord *arb = b; + time_t diff; + + diff = ara->trigger - arb->trigger; + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; +} + +/* Adds an alarm to the queue and sets up the timer */ +static void +queue_alarm (AlarmRecord *ar) +{ + GList *old_head; + + /* Track the current head of the list in case there are changes */ + old_head = alarms; + + /* Insert the new alarm in order if the alarm's trigger time is + * after the current time */ + alarms = g_list_insert_sorted (alarms, ar, compare_alarm_by_time); + + /* If there first item on the list didn't change, the time out is fine */ + if (old_head == alarms) + return; + + /* Set the timer for removal upon activation */ + setup_timeout (); +} + +/** + * alarm_add: + * @trigger: Time at which alarm will trigger. + * @alarm_fn: Callback for trigger. + * @data: Closure data for callback. + * @destroy_notify_fn: destroy notification callback. + * + * Adds an alarm to trigger at the specified time. The @alarm_fn will be called + * with the provided data and the alarm will be removed from the trigger list. + * + * Return value: An identifier for this alarm; it can be used to remove the + * alarm later with alarm_remove(). If the trigger time occurs in the past, then + * the alarm will not be queued and the function will return NULL. + **/ +gpointer +alarm_add (time_t trigger, + AlarmFunction alarm_fn, + gpointer data, + AlarmDestroyNotify destroy_notify_fn) +{ + AlarmRecord *ar; + + g_return_val_if_fail (trigger != -1, NULL); + g_return_val_if_fail (alarm_fn != NULL, NULL); + + ar = g_new (AlarmRecord, 1); + ar->trigger = trigger; + ar->alarm_fn = alarm_fn; + ar->data = data; + ar->destroy_notify_fn = destroy_notify_fn; + + queue_alarm (ar); + + return ar; +} + +/** + * alarm_remove: + * @alarm: A queued alarm identifier. + * + * Removes an alarm from the alarm queue. + **/ +void +alarm_remove (gpointer alarm) +{ + AlarmRecord *notify_id, *ar; + AlarmRecord ar_copy; + AlarmRecord *old_head; + GList *l; + + g_return_if_fail (alarm != NULL); + + ar = alarm; + + l = g_list_find (alarms, ar); + if (!l) { + g_warning (G_STRLOC ": Requested removal of nonexistent alarm!"); + return; + } + + old_head = alarms->data; + + notify_id = ar; + + if (old_head == ar) { + ar_copy = *ar; + ar = &ar_copy; + + /* This will free the original AlarmRecord; + * that's why we copy it. */ + pop_alarm (); + } else { + alarms = g_list_delete_link (alarms, l); + } + + /* Reset the timeout */ + if (!alarms) { + g_source_remove (timeout_id); + timeout_id = 0; + } + + /* Notify about destructiono of the alarm */ + + if (ar->destroy_notify_fn) + (* ar->destroy_notify_fn) (notify_id, ar->data); + +} + +/** + * alarm_done: + * + * Terminates the alarm timer mechanism. This should be called at the end of + * the program. + **/ +void +alarm_done (void) +{ + GList *l; + + if (timeout_id == 0) { + if (alarms) + g_warning ("No timeout, but queue is not NULL\n"); + return; + } + + g_source_remove (timeout_id); + timeout_id = 0; + + if (!alarms) { + g_warning ("timeout present, freed, but no alarms active\n"); + return; + } + + for (l = alarms; l; l = l->next) { + AlarmRecord *ar; + + ar = l->data; + + if (ar->destroy_notify_fn) + (* ar->destroy_notify_fn) (ar, ar->data); + + g_free (ar); + } + + g_list_free (alarms); + alarms = NULL; +} diff --git a/calendar/alarm-notify/alarm.h b/calendar/alarm-notify/alarm.h new file mode 100644 index 0000000000..d6e9ff4a4e --- /dev/null +++ b/calendar/alarm-notify/alarm.h @@ -0,0 +1,41 @@ +/* + * Evolution calendar - Low-level alarm timer mechanism + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Miguel de Icaza + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef ALARM_H +#define ALARM_H + +#include +#include + +typedef void (* AlarmFunction) (gpointer alarm_id, time_t trigger, gpointer data); +typedef void (* AlarmDestroyNotify) (gpointer alarm_id, gpointer data); + +void alarm_done (void); + +gpointer alarm_add (time_t trigger, AlarmFunction alarm_fn, gpointer data, + AlarmDestroyNotify destroy_notify_fn); +void alarm_remove (gpointer alarm); + +#endif diff --git a/calendar/alarm-notify/config-data.c b/calendar/alarm-notify/config-data.c new file mode 100644 index 0000000000..c8348f89fc --- /dev/null +++ b/calendar/alarm-notify/config-data.c @@ -0,0 +1,410 @@ +/* + * Evolution calendar - Configuration values for the alarm notification daemon + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include "config-data.h" + +/* Whether we have initied ourselves by reading + * the data from the configuration engine. */ +static gboolean inited = FALSE; +static GConfClient *conf_client = NULL; +static GSettings *calendar_settings = NULL; +static ESourceList *calendar_source_list = NULL, *tasks_source_list = NULL; + +/* Copied from ../calendar-config.c; returns whether the locale has 'am' and + * 'pm' strings defined. + */ +static gboolean +locale_supports_12_hour_format (void) +{ + gchar s[16]; + time_t t = 0; + + strftime (s, sizeof s, "%p", gmtime (&t)); + return s[0] != '\0'; +} + +static void +do_cleanup (void) +{ + if (calendar_source_list) { + g_object_unref (calendar_source_list); + calendar_source_list = NULL; + } + + if (tasks_source_list) { + g_object_unref (tasks_source_list); + tasks_source_list = NULL; + } + + g_object_unref (conf_client); + conf_client = NULL; + + g_object_unref (calendar_settings); + calendar_settings = FALSE; + + inited = FALSE; +} + +/* Ensures that the configuration values have been read */ +static void +ensure_inited (void) +{ + if (inited) + return; + + inited = TRUE; + + conf_client = gconf_client_get_default (); + if (!GCONF_IS_CLIENT (conf_client)) { + inited = FALSE; + return; + } + + calendar_settings = g_settings_new ("org.gnome.evolution.calendar"); + + g_atexit ((GVoidFunc) do_cleanup); + + /* load the sources for calendars and tasks */ + calendar_source_list = e_source_list_new_for_gconf (conf_client, + "/apps/evolution/calendar/sources"); + tasks_source_list = e_source_list_new_for_gconf (conf_client, + "/apps/evolution/tasks/sources"); + +} + +ESourceList * +config_data_get_calendars (const gchar *key) +{ + ESourceList *cal_sources; + gboolean state; + GSList *gconf_list; + + if (!inited) { + conf_client = gconf_client_get_default (); + calendar_settings = g_settings_new ("org.gnome.evolution.calendar"); + } + + gconf_list = gconf_client_get_list (conf_client, + key, + GCONF_VALUE_STRING, + NULL); + cal_sources = e_source_list_new_for_gconf (conf_client, key); + + if (cal_sources && g_slist_length (gconf_list)) { + g_slist_foreach (gconf_list, (GFunc) g_free, NULL); + g_slist_free (gconf_list); + return cal_sources; + } + + state = g_settings_get_boolean (calendar_settings, "notify-with-tray"); + if (!state) /* Should be old client */ { + GSList *source; + + g_settings_set_boolean (calendar_settings, "notify-with-tray", TRUE); + source = gconf_client_get_list (conf_client, + "/apps/evolution/calendar/sources", + GCONF_VALUE_STRING, + NULL); + gconf_client_set_list (conf_client, + key, + GCONF_VALUE_STRING, + source, + NULL); + cal_sources = e_source_list_new_for_gconf (conf_client, key); + + if (source) { + g_slist_foreach (source, (GFunc) g_free, NULL); + g_slist_free (source); + } + } + + if (gconf_list) { + g_slist_foreach (gconf_list, (GFunc) g_free, NULL); + g_slist_free (gconf_list); + } + + return cal_sources; + +} + +void +config_data_replace_string_list (const gchar *key, + const gchar *old, + const gchar *new) +{ + GSList *source, *tmp; + + if (!inited) + conf_client = gconf_client_get_default (); + + source = gconf_client_get_list (conf_client, + key, + GCONF_VALUE_STRING, + NULL); + + for (tmp = source; tmp; tmp = tmp->next) { + + if (strcmp (tmp->data, old) == 0) { + g_free (tmp->data); + tmp->data = g_strdup ((gchar *) new); + gconf_client_set_list (conf_client, + key, + GCONF_VALUE_STRING, + source, + NULL); + break; + } + } + + if (source) { + g_slist_foreach (source, (GFunc) g_free, NULL); + g_slist_free (source); + } +} + +icaltimezone * +config_data_get_timezone (void) +{ + gchar *location; + icaltimezone *local_timezone; + + ensure_inited (); + + if (g_settings_get_boolean (calendar_settings, "use-system-timezone")) + location = e_cal_util_get_system_timezone_location (); + else { + location = g_settings_get_string (calendar_settings, "timezone"); + } + + if (location && location[0]) + local_timezone = icaltimezone_get_builtin_timezone (location); + else + local_timezone = icaltimezone_get_utc_timezone (); + + g_free (location); + + return local_timezone; +} + +gboolean +config_data_get_24_hour_format (void) +{ + ensure_inited (); + + if (locale_supports_12_hour_format ()) { + return g_settings_get_boolean (calendar_settings, "use-24hour-format"); + } + + return TRUE; +} + +gboolean +config_data_get_notify_with_tray (void) +{ + ensure_inited (); + + return g_settings_get_boolean (calendar_settings, "notify-with-tray"); +} + +/** + * config_data_set_last_notification_time: + * @t: A time value. + * + * Saves the last notification time so that it can be fetched the next time the + * alarm daemon is run. This way the daemon can show alarms that should have + * triggered while it was not running. + **/ +void +config_data_set_last_notification_time (ECalClient *cal, + time_t t) +{ + time_t current_t, now = time (NULL); + + g_return_if_fail (t != -1); + + if (cal) { + ESource *source = e_client_get_source (E_CLIENT (cal)); + if (source) { + const gchar *prop_str; + GTimeVal curr_tv = {0}; + + prop_str = e_source_get_property (source, "last-notified"); + if (!prop_str || !g_time_val_from_iso8601 (prop_str, &curr_tv)) + curr_tv.tv_sec = 0; + + if (t > (time_t) curr_tv.tv_sec || (time_t) curr_tv.tv_sec > now) { + GTimeVal tmval = {0}; + gchar *as_text; + + tmval.tv_sec = (glong) t; + as_text = g_time_val_to_iso8601 (&tmval); + + if (as_text) { + e_source_set_property (source, "last-notified", as_text); + g_free (as_text); + /* pass through, thus the global last notification time is also changed */ + } + } + } + } + + /* we only store the new notification time if it is bigger + * than the already stored one */ + current_t = g_settings_get_int (calendar_settings, "last-notification-time"); + if (t > current_t || current_t > now) + g_settings_set_int (calendar_settings, "last-notification-time", t); +} + +/** + * config_data_get_last_notification_time: + * + * Queries the last saved value for alarm notification times. + * + * Return value: The last saved value, or -1 if no value had been saved before. + **/ +time_t +config_data_get_last_notification_time (ECalClient *cal) +{ + time_t value, now; + + if (cal) { + ESource *source = e_client_get_source (E_CLIENT (cal)); + if (source) { + const gchar *last_notified; + + GTimeVal tmval = {0}; + + last_notified = e_source_get_property ( + source, "last-notified"); + + if (last_notified && *last_notified && + g_time_val_from_iso8601 (last_notified, &tmval)) { + time_t now = time (NULL), value = (time_t) tmval.tv_sec; + + if (value > now) + value = now; + return value; + } + } + } + + value = g_settings_get_int (calendar_settings, "last-notification-time"); + now = time (NULL); + if (value > now) + value = now; + + return value; +} + +/** + * config_data_save_blessed_program: + * @program: a program name + * + * Saves a program name as "blessed" + **/ +void +config_data_save_blessed_program (const gchar *program) +{ + gchar **list; + gint i; + GPtrArray *array = g_ptr_array_new (); + + list = g_settings_get_strv (calendar_settings, "notify-programs"); + for (i = 0; i < g_strv_length (list); i++) + g_ptr_array_add (array, list[i]); + + g_ptr_array_add (array, (gpointer) program); + g_ptr_array_add (array, NULL); + g_settings_set_strv (calendar_settings, "notify-programs", (const gchar *const *) array->pdata); + + g_strfreev (list); + g_ptr_array_free (array, TRUE); +} + +/** + * config_data_is_blessed_program: + * @program: a program name + * + * Checks to see if a program is blessed + * + * Return value: TRUE if program is blessed, FALSE otherwise + **/ +gboolean +config_data_is_blessed_program (const gchar *program) +{ + gchar **list; + gint i = 0; + gboolean found = FALSE; + + list = g_settings_get_strv (calendar_settings, "notify-programs"); + if (!list) + return FALSE; + + while (list[i] != NULL) { + if (!found) + found = strcmp ((gchar *) list[i], program) == 0; + i++; + } + + g_strfreev (list); + + return found; +} + +static gboolean can_debug = FALSE; +static GStaticRecMutex rec_mutex = G_STATIC_REC_MUTEX_INIT; + +void +config_data_init_debugging (void) +{ + can_debug = g_getenv ("ALARMS_DEBUG") != NULL; +} + +/* returns whether started debugging; + * call config_data_stop_debugging() when started and you are done with it + */ +gboolean +config_data_start_debugging (void) +{ + g_static_rec_mutex_lock (&rec_mutex); + + if (can_debug) + return TRUE; + + g_static_rec_mutex_unlock (&rec_mutex); + + return FALSE; +} + +void +config_data_stop_debugging (void) +{ + g_static_rec_mutex_unlock (&rec_mutex); +} diff --git a/calendar/alarm-notify/config-data.h b/calendar/alarm-notify/config-data.h new file mode 100644 index 0000000000..a7a0ca58e0 --- /dev/null +++ b/calendar/alarm-notify/config-data.h @@ -0,0 +1,64 @@ +/* + * + * Evolution calendar - Configuration values for the alarm notification daemon + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef CONFIG_DATA_H +#define CONFIG_DATA_H + +#include +#include +#include + +icaltimezone * config_data_get_timezone (void); +gboolean config_data_get_24_hour_format (void); +gboolean config_data_get_notify_with_tray + (void); +void config_data_set_last_notification_time + (ECalClient *cal, + time_t t); +time_t config_data_get_last_notification_time + (ECalClient *cal); +void config_data_save_blessed_program + (const gchar *program); +gboolean config_data_is_blessed_program (const gchar *program); +ESourceList * config_data_get_calendars (const gchar *); +void config_data_replace_string_list (const gchar *, + const gchar *, + const gchar *); + +void config_data_init_debugging (void); +gboolean config_data_start_debugging (void); +void config_data_stop_debugging (void); + +#define debug(x) G_STMT_START { \ + if (config_data_start_debugging ()) { \ + g_print ("%s (%s): ", G_STRFUNC, G_STRLOC); \ + g_print x; \ + g_print ("\n"); \ + \ + config_data_stop_debugging (); \ + } \ + } G_STMT_END + +#endif diff --git a/calendar/alarm-notify/evolution-alarm-notify-icon.rc b/calendar/alarm-notify/evolution-alarm-notify-icon.rc new file mode 100644 index 0000000000..1f9ef65874 --- /dev/null +++ b/calendar/alarm-notify/evolution-alarm-notify-icon.rc @@ -0,0 +1 @@ +1 ICON "evolution-alarm-notify.ico" diff --git a/calendar/alarm-notify/evolution-alarm-notify.ico b/calendar/alarm-notify/evolution-alarm-notify.ico new file mode 100644 index 0000000000..6585452256 Binary files /dev/null and b/calendar/alarm-notify/evolution-alarm-notify.ico differ diff --git a/calendar/alarm-notify/notify-main.c b/calendar/alarm-notify/notify-main.c new file mode 100644 index 0000000000..a6d9d53fc5 --- /dev/null +++ b/calendar/alarm-notify/notify-main.c @@ -0,0 +1,110 @@ +/* + * Evolution calendar - Alarm notification service main file + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * Rodrigo Moya + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "alarm-notify.h" + +#ifdef G_OS_WIN32 +#include +#include +#ifndef PROCESS_DEP_ENABLE +#define PROCESS_DEP_ENABLE 0x00000001 +#endif +#ifndef PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION +#define PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION 0x00000002 +#endif +#endif + +#include "e-util/e-util-private.h" + +gint +main (gint argc, + gchar **argv) +{ + AlarmNotify *alarm_notify_service; + gint exit_status; + GError *error = NULL; +#ifdef G_OS_WIN32 + gchar *path; + + /* Reduce risks */ + { + typedef BOOL (WINAPI *t_SetDllDirectoryA) (LPCSTR lpPathName); + t_SetDllDirectoryA p_SetDllDirectoryA; + + p_SetDllDirectoryA = GetProcAddress (GetModuleHandle ("kernel32.dll"), "SetDllDirectoryA"); + if (p_SetDllDirectoryA) + (*p_SetDllDirectoryA) (""); + } +#ifndef _WIN64 + { + typedef BOOL (WINAPI *t_SetProcessDEPPolicy) (DWORD dwFlags); + t_SetProcessDEPPolicy p_SetProcessDEPPolicy; + + p_SetProcessDEPPolicy = GetProcAddress (GetModuleHandle ("kernel32.dll"), "SetProcessDEPPolicy"); + if (p_SetProcessDEPPolicy) + (*p_SetProcessDEPPolicy) (PROCESS_DEP_ENABLE | PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION); + } +#endif +#endif + + bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + gtk_init (&argc, &argv); + + e_gdbus_templates_init_main_thread (); + +#ifdef G_OS_WIN32 + path = g_build_path (";", _e_get_bindir (), g_getenv ("PATH"), NULL); + + if (!g_setenv ("PATH", path, TRUE)) + g_warning ("Could not set PATH for Evolution Alarm Notifier"); +#endif + + alarm_notify_service = alarm_notify_new (NULL, &error); + + if (error != NULL) { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (EXIT_FAILURE); + } + + exit_status = g_application_run ( + G_APPLICATION (alarm_notify_service), argc, argv); + + g_object_unref (alarm_notify_service); + + return exit_status; +} diff --git a/calendar/alarm-notify/util.c b/calendar/alarm-notify/util.c new file mode 100644 index 0000000000..891ea131bc --- /dev/null +++ b/calendar/alarm-notify/util.c @@ -0,0 +1,92 @@ +/* + * Evolution calendar - utility functions + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "config-data.h" +#include "util.h" + +/* Converts a time_t to a string, relative to the specified timezone */ +gchar * +timet_to_str_with_zone (time_t t, + icaltimezone *zone) +{ + struct icaltimetype itt; + struct tm tm; + gchar buf[256]; + + if (t == -1) + return g_strdup (_("invalid time")); + + itt = icaltime_from_timet_with_zone (t, FALSE, zone); + tm = icaltimetype_to_tm (&itt); + + e_time_format_date_and_time (&tm, config_data_get_24_hour_format (), + FALSE, FALSE, buf, sizeof (buf)); + return g_strdup (buf); +} + +gchar * +calculate_time (time_t start, + time_t end) +{ + time_t difference = end - start; + gchar *str; + gint hours, minutes; + gchar *times[4]; + gchar *joined; + gint i; + + i = 0; + if (difference >= 3600) { + hours = difference / 3600; + difference %= 3600; + + times[i++] = g_strdup_printf (ngettext("%d hour", "%d hours", hours), hours); + } + if (difference >= 60) { + minutes = difference / 60; + difference %= 60; + + times[i++] = g_strdup_printf (ngettext("%d minute", "%d minutes", minutes), minutes); + } + if (i == 0 || difference != 0) { + /* TRANSLATORS: here, "second" is the time division (like "minute"), not the ordinal number (like "third") */ + times[i++] = g_strdup_printf (ngettext("%d second", "%d seconds", difference), (gint)difference); + } + + times[i] = NULL; + joined = g_strjoinv (" ", times); + str = g_strconcat ("(", joined, ")", NULL); + while (i > 0) + g_free (times[--i]); + g_free (joined); + + return str; +} diff --git a/calendar/alarm-notify/util.h b/calendar/alarm-notify/util.h new file mode 100644 index 0000000000..bb6935729a --- /dev/null +++ b/calendar/alarm-notify/util.h @@ -0,0 +1,33 @@ +/* + * + * Evolution calendar - utility functions + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see + * + * + * Authors: + * Federico Mena-Quintero + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + */ + +#ifndef UTIL_H +#define UTIL_H + +#include + +gchar *timet_to_str_with_zone (time_t t, icaltimezone *zone); +gchar *calculate_time (time_t start, time_t end); +#endif diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index ce1f832ba3..a5f9a7013d 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = alarm-notify dialogs +SUBDIRS = dialogs privsolib_LTLIBRARIES = libevolution-calendar.la diff --git a/calendar/gui/alarm-notify/Makefile.am b/calendar/gui/alarm-notify/Makefile.am deleted file mode 100644 index 82fdeeb77e..0000000000 --- a/calendar/gui/alarm-notify/Makefile.am +++ /dev/null @@ -1,65 +0,0 @@ -if OS_WIN32 -bin_PROGRAMS = evolution-alarm-notify -else -privlibexec_PROGRAMS = evolution-alarm-notify -endif - -if HAVE_WINDRES -EVOLUTIONALARMNOTIFYICON = evolution-alarm-notify-icon.o -endif - -evolution_alarm_notify_CPPFLAGS = \ - $(AM_CPPFLAGS) \ - -DG_LOG_DOMAIN=\"evolution-alarm-notify\" \ - -I$(top_srcdir) \ - -I$(top_srcdir)/widgets \ - -I$(top_srcdir)/calendar \ - -DEVOLUTION_UIDIR=\""$(uidir)"\" \ - -DEVOLUTION_ICONDIR=\""$(icondir)"\" \ - -DEVOLUTION_LOCALEDIR=\""$(localedir)"\" \ - -DEVOLUTION_LIBEXECDIR=\""$(privlibexecdir)"\" \ - $(EVOLUTION_DATA_SERVER_CFLAGS) \ - $(GNOME_PLATFORM_CFLAGS) \ - $(LIBNOTIFY_CFLAGS) \ - $(CANBERRA_CFLAGS) - -ui_DATA = \ - alarm-notify.ui - -evolution_alarm_notify_SOURCES = \ - alarm.c \ - alarm.h \ - alarm-notify.c \ - alarm-notify.h \ - alarm-notify-dialog.c \ - alarm-notify-dialog.h \ - alarm-queue.c \ - alarm-queue.h \ - config-data.c \ - config-data.h \ - notify-main.c \ - util.c \ - util.h - -evolution_alarm_notify_LDADD = \ - $(top_builddir)/e-util/libeutil.la \ - $(top_builddir)/widgets/misc/libemiscwidgets.la \ - $(EVOLUTION_DATA_SERVER_LIBS) \ - $(GNOME_PLATFORM_LIBS) \ - $(LIBNOTIFY_LIBS) \ - $(CANBERRA_LIBS) \ - $(EVOLUTIONALARMNOTIFYICON) - -if OS_WIN32 -evolution_alarm_notify_LDFLAGS = -mwindows -endif - -EXTRA_DIST = $(ui_DATA) \ - evolution-alarm-notify-icon.rc \ - evolution-alarm-notify.ico - - -evolution-alarm-notify-icon.o: evolution-alarm-notify.ico evolution-alarm-notify-icon.rc - $(WINDRES) evolution-alarm-notify-icon.rc evolution-alarm-notify-icon.o - --include $(top_srcdir)/git.mk diff --git a/calendar/gui/alarm-notify/alarm-notify-dialog.c b/calendar/gui/alarm-notify/alarm-notify-dialog.c deleted file mode 100644 index 48d14f1a0f..0000000000 --- a/calendar/gui/alarm-notify/alarm-notify-dialog.c +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Evolution calendar - alarm notification dialog - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include "alarm-notify-dialog.h" -#include "config-data.h" -#include "util.h" - -#include "e-util/e-util.h" -#include "e-util/e-util-private.h" -#include "misc/e-buffer-tagger.h" - -enum { - ALARM_DISPLAY_COLUMN, - ALARM_SUMMARY_COLUMN, - ALARM_DESCRIPTION_COLUMN, - ALARM_LOCATION_COLUMN, - - ALARM_START_COLUMN, - ALARM_END_COLUMN, - - ALARM_FUNCINFO_COLUMN, - - N_ALARM_COLUMNS -}; - -/* The useful contents of the alarm notify dialog */ - -typedef struct { - AlarmNotifyFunc func; - gpointer func_data; -} AlarmFuncInfo; - -typedef struct { - GtkBuilder *builder; - - GtkWidget *dialog; - GtkWidget *snooze_time_min; - GtkWidget *snooze_time_hrs; - GtkWidget *snooze_time_days; - GtkWidget *snooze_btn; - GtkWidget *edit_btn; - GtkWidget *dismiss_btn; - GtkWidget *minutes_label; - GtkWidget *hrs_label; - GtkWidget *days_label; - GtkWidget *description; - GtkWidget *location; - GtkWidget *treeview; - - AlarmFuncInfo *cur_funcinfo; - -} AlarmNotify; - -static void tree_selection_changed_cb (GtkTreeSelection *selection, - gpointer data); -static void fill_in_labels (AlarmNotify *an, - const gchar *summary, - const gchar *description, - const gchar *location, - time_t occur_start, - time_t occur_end); - -static void edit_pressed_cb (GtkButton *button, - gpointer user_data); -static void snooze_pressed_cb (GtkButton *button, - gpointer user_data); - -static void -an_update_minutes_label (GtkSpinButton *sb, - gpointer data) -{ - AlarmNotify *an; - gint snooze_timeout_min; - - an = (AlarmNotify *) data; - - snooze_timeout_min = gtk_spin_button_get_value_as_int (sb); - gtk_label_set_text (GTK_LABEL (an->minutes_label), ngettext ("minute", "minutes", snooze_timeout_min)); -} - -static void -an_update_hrs_label (GtkSpinButton *sb, - gpointer data) -{ - AlarmNotify *an; - gint snooze_timeout_hrs; - - an = (AlarmNotify *) data; - - snooze_timeout_hrs = gtk_spin_button_get_value_as_int (sb); - gtk_label_set_text (GTK_LABEL (an->hrs_label), ngettext ("hour", "hours", snooze_timeout_hrs)); -} - -static void -an_update_days_label (GtkSpinButton *sb, - gpointer data) -{ - AlarmNotify *an; - gint snooze_timeout_days; - - an = (AlarmNotify *) data; - - snooze_timeout_days = gtk_spin_button_get_value_as_int (sb); - gtk_label_set_text (GTK_LABEL (an->days_label), ngettext ("day", "days", snooze_timeout_days)); -} - -static void -dialog_response_cb (GtkDialog *dialog, - guint response_id, - gpointer user_data) -{ - AlarmNotify *an = user_data; - GtkTreeIter iter; - GtkTreeModel *model = NULL; - AlarmFuncInfo *funcinfo = NULL; - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) { - gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); - } - - if (!funcinfo) { - GtkTreeModel *treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (an->treeview)); - if (!gtk_tree_model_get_iter_first (treemodel, &iter)) - return; - - gtk_tree_model_get (treemodel, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); - } - - g_return_if_fail (funcinfo); - - switch (response_id) { - case GTK_RESPONSE_CLOSE: - case GTK_RESPONSE_DELETE_EVENT: - (* funcinfo->func) (ALARM_NOTIFY_CLOSE, -1, funcinfo->func_data); - break; - } -} - -static void -edit_pressed_cb (GtkButton *button, - gpointer user_data) -{ - AlarmNotify *an = user_data; - AlarmFuncInfo *funcinfo = NULL; - GtkTreeIter iter; - GtkTreeModel *model = NULL; - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); - - g_return_if_fail (funcinfo); - - (* funcinfo->func) (ALARM_NOTIFY_EDIT, -1, funcinfo->func_data); -} - -#define DEFAULT_SNOOZE_MINS 5 - -static void -snooze_pressed_cb (GtkButton *button, - gpointer user_data) -{ - gint snooze_timeout; - AlarmNotify *an = user_data; - GtkTreeIter iter; - GtkTreeModel *model = NULL; - AlarmFuncInfo *funcinfo = NULL; - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); - - gtk_widget_grab_focus ((GtkWidget *) button); - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); - - g_return_if_fail (funcinfo); - - snooze_timeout = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_min)); - snooze_timeout += 60 * (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_hrs))); - snooze_timeout += 60 * 24 * (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (an->snooze_time_days))); - if (!snooze_timeout) - snooze_timeout = DEFAULT_SNOOZE_MINS; - (* funcinfo->func) (ALARM_NOTIFY_SNOOZE, snooze_timeout, funcinfo->func_data); -} - -static void -dismiss_pressed_cb (GtkButton *button, - gpointer user_data) -{ - AlarmNotify *an = user_data; - GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (an->treeview)); - - g_return_if_fail (model != NULL); - - if (gtk_tree_model_iter_n_children (model, NULL) <= 1) { - gtk_dialog_response (GTK_DIALOG (an->dialog), GTK_RESPONSE_CLOSE); - } else { - GtkTreeIter iter; - AlarmFuncInfo *funcinfo = NULL; - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &funcinfo, -1); - - g_return_if_fail (funcinfo); - - (* funcinfo->func) (ALARM_NOTIFY_DISMISS, -1, funcinfo->func_data); - } -} - -static void -dialog_destroyed_cb (GtkWidget *dialog, - gpointer user_data) -{ - AlarmNotify *an = user_data; - - g_object_unref (an->builder); - g_free (an); -} - -/** - * notified_alarms_dialog_new: - * - * Return value: a new dialog in which you can add alarm notifications - **/ -AlarmNotificationsDialog * -notified_alarms_dialog_new (void) -{ - GtkWidget *container; - GtkWidget *image; - GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); - AlarmNotificationsDialog *na = NULL; - AlarmNotify *an = g_new0 (AlarmNotify, 1); - GtkTreeViewColumn *column = NULL; - GtkTreeSelection *selection = NULL; - GtkTreeModel *model = GTK_TREE_MODEL (gtk_list_store_new ( - N_ALARM_COLUMNS, - - G_TYPE_STRING, /* Display */ - G_TYPE_STRING, /* Summary */ - G_TYPE_STRING, /* Description */ - G_TYPE_STRING, /* Location */ - - G_TYPE_POINTER, /* Start */ - G_TYPE_POINTER, /* End */ - - G_TYPE_POINTER /* FuncInfo */)); - - an->builder = gtk_builder_new (); - e_load_ui_builder_definition (an->builder, "alarm-notify.ui"); - - an->dialog = e_builder_get_widget (an->builder, "alarm-notify"); - an->snooze_time_min = e_builder_get_widget (an->builder, "snooze-time-min"); - an->minutes_label = e_builder_get_widget (an->builder, "minutes-label"); - an->snooze_time_hrs = e_builder_get_widget (an->builder, "snooze-time-hrs"); - an->hrs_label = e_builder_get_widget (an->builder, "hrs-label"); - an->snooze_time_days = e_builder_get_widget (an->builder, "snooze-time-days"); - an->days_label = e_builder_get_widget (an->builder, "days-label"); - an->description = e_builder_get_widget (an->builder, "description-label"); - an->location = e_builder_get_widget (an->builder, "location-label"); - an->treeview = e_builder_get_widget (an->builder, "appointments-treeview"); - an->snooze_btn = e_builder_get_widget (an->builder, "snooze-button"); - an->edit_btn = e_builder_get_widget (an->builder, "edit-button"); - an->dismiss_btn = e_builder_get_widget (an->builder, "dismiss-button"); - - if (!(an->dialog && an->treeview - && an->snooze_time_min && an->snooze_time_hrs && an->snooze_time_days - && an->description && an->location && an->edit_btn && an->snooze_btn && an->dismiss_btn)) { - g_warning ("alarm_notify_dialog(): Could not find all widgets in alarm-notify.ui file!"); - g_object_unref (an->builder); - g_free (an); - return NULL; - } - - e_buffer_tagger_connect (GTK_TEXT_VIEW (an->description)); - - gtk_tree_view_set_model (GTK_TREE_VIEW (an->treeview), model); - - gtk_window_set_keep_above (GTK_WINDOW (an->dialog), TRUE); - - column = gtk_tree_view_column_new_with_attributes (_("Start time"), - renderer, "text", ALARM_DISPLAY_COLUMN, NULL); - - gtk_tree_view_column_set_attributes (column, renderer, - "markup", ALARM_DISPLAY_COLUMN, NULL); - - gtk_tree_view_append_column (GTK_TREE_VIEW (an->treeview), column); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (an->treeview)); - gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); - g_signal_connect ( - selection, "changed", - G_CALLBACK (tree_selection_changed_cb), an); - tree_selection_changed_cb (selection, an); - - gtk_widget_realize (an->dialog); - - container = gtk_dialog_get_action_area (GTK_DIALOG (an->dialog)); - gtk_container_set_border_width (GTK_CONTAINER (container), 12); - - container = gtk_dialog_get_content_area (GTK_DIALOG (an->dialog)); - gtk_container_set_border_width (GTK_CONTAINER (container), 0); - - image = e_builder_get_widget (an->builder, "alarm-image"); - gtk_image_set_from_icon_name ( - GTK_IMAGE (image), "stock_alarm", GTK_ICON_SIZE_DIALOG); - - g_signal_connect (an->edit_btn, "clicked", G_CALLBACK (edit_pressed_cb), an); - g_signal_connect (an->snooze_btn, "clicked", G_CALLBACK (snooze_pressed_cb), an); - g_signal_connect (an->dismiss_btn, "clicked", G_CALLBACK (dismiss_pressed_cb), an); - g_signal_connect ( - an->dialog, "response", - G_CALLBACK (dialog_response_cb), an); - g_signal_connect ( - an->dialog, "destroy", - G_CALLBACK (dialog_destroyed_cb), an); - - if (!gtk_widget_get_realized (an->dialog)) - gtk_widget_realize (an->dialog); - - gtk_window_set_icon_name (GTK_WINDOW (an->dialog), "stock_alarm"); - - /* Set callback for updating the snooze "minutes" label */ - g_signal_connect ( - an->snooze_time_min, "value_changed", - G_CALLBACK (an_update_minutes_label), an); - - /* Set callback for updating the snooze "hours" label */ - g_signal_connect ( - an->snooze_time_hrs, "value_changed", - G_CALLBACK (an_update_hrs_label), an); - - /* Set callback for updating the snooze "days" label */ - g_signal_connect ( - an->snooze_time_days, "value_changed", - G_CALLBACK (an_update_days_label), an); - - na = g_new0 (AlarmNotificationsDialog, 1); - - na->treeview = an->treeview; - na->dialog = an->dialog; - - return na; -} - -/** - * add_alarm_to_notified_alarms_dialog: - * @na: Pointer to the dialog-info - * @trigger: Trigger time for the alarm. - * @occur_start: Start of occurrence time for the event. - * @occur_end: End of occurrence time for the event. - * @vtype: Type of the component which corresponds to the alarm. - * @summary: Short summary of the appointment - * @description: Long description of the appointment - * @location: Location of the appointment - * @func: Function to be called when a dialog action is invoked. - * @func_data: Closure data for @func. - * - * The specified @func will be used to notify the client about result of - * the actions in the dialog. - * - * Return value: the iter in the treeview of the dialog - **/ - -GtkTreeIter -add_alarm_to_notified_alarms_dialog (AlarmNotificationsDialog *na, - time_t trigger, - time_t occur_start, - time_t occur_end, - ECalComponentVType vtype, - const gchar *summary, - const gchar *description, - const gchar *location, - AlarmNotifyFunc func, - gpointer func_data) -{ - GtkTreeIter iter; - GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (na->treeview)); - AlarmFuncInfo *funcinfo = NULL; - gchar *to_display = NULL, *start, *end, *str_time; - icaltimezone *current_zone; - - /* Iter is not yet defined but still we return it in all the g_return_val_if_fail() calls? */ - g_return_val_if_fail (trigger != -1, iter); - - /* Only VEVENTs or VTODOs can have alarms */ - g_return_val_if_fail (vtype == E_CAL_COMPONENT_EVENT || vtype == E_CAL_COMPONENT_TODO, iter); - g_return_val_if_fail (summary != NULL, iter); - g_return_val_if_fail (description != NULL, iter); - g_return_val_if_fail (location != NULL, iter); - g_return_val_if_fail (func != NULL, iter); - - funcinfo = g_new0 (AlarmFuncInfo, 1); - funcinfo->func = func; - funcinfo->func_data = func_data; - - gtk_list_store_append (GTK_LIST_STORE (model), &iter); - - current_zone = config_data_get_timezone (); - start = timet_to_str_with_zone (occur_start, current_zone); - end = timet_to_str_with_zone (occur_end, current_zone); - str_time = calculate_time (occur_start, occur_end); - to_display = g_markup_printf_escaped ("%s\n%s %s", - summary, start, str_time); - g_free (start); - g_free (end); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, - ALARM_DISPLAY_COLUMN, to_display, -1); - g_free (to_display); - g_free (str_time); - - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_SUMMARY_COLUMN, summary, -1); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_DESCRIPTION_COLUMN, description, -1); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_LOCATION_COLUMN, location, -1); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_START_COLUMN, occur_start, -1); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_END_COLUMN, occur_end, -1); - gtk_list_store_set (GTK_LIST_STORE (model), &iter, ALARM_FUNCINFO_COLUMN, funcinfo, -1); - - return iter; -} - -static void -tree_selection_changed_cb (GtkTreeSelection *selection, - gpointer user_data) -{ - GtkTreeModel *model; - GtkTreeIter iter; - AlarmNotify *an = user_data; - - if (gtk_tree_selection_get_selected (selection, &model, &iter)) { - gchar *summary, *description, *location; - time_t occur_start, occur_end; - - gtk_widget_set_sensitive (an->snooze_btn, TRUE); - gtk_widget_set_sensitive (an->edit_btn, TRUE); - gtk_widget_set_sensitive (an->dismiss_btn, TRUE); - gtk_tree_model_get (model, &iter, ALARM_SUMMARY_COLUMN, &summary, -1); - gtk_tree_model_get (model, &iter, ALARM_DESCRIPTION_COLUMN, &description, -1); - gtk_tree_model_get (model, &iter, ALARM_LOCATION_COLUMN, &location, -1); - gtk_tree_model_get (model, &iter, ALARM_START_COLUMN, &occur_start, -1); - gtk_tree_model_get (model, &iter, ALARM_END_COLUMN, &occur_end, -1);\ - gtk_tree_model_get (model, &iter, ALARM_FUNCINFO_COLUMN, &an->cur_funcinfo, -1); - - fill_in_labels (an, summary, description, location, occur_start, occur_end); - } else { - gtk_widget_set_sensitive (an->snooze_btn, FALSE); - gtk_widget_set_sensitive (an->edit_btn, FALSE); - gtk_widget_set_sensitive (an->dismiss_btn, FALSE); - - fill_in_labels (an, "", "", "", -1, -1); - } -} - -static void -fill_in_labels (AlarmNotify *an, - const gchar *summary, - const gchar *description, - const gchar *location, - time_t occur_start, - time_t occur_end) -{ - GtkTextTagTable *table = gtk_text_tag_table_new (); - GtkTextBuffer *buffer = gtk_text_buffer_new (table); - gtk_text_buffer_set_text (buffer, description, -1); - e_buffer_tagger_disconnect (GTK_TEXT_VIEW (an->description)); - gtk_text_view_set_buffer (GTK_TEXT_VIEW (an->description), buffer); - gtk_label_set_text (GTK_LABEL (an->location), location); - e_buffer_tagger_connect (GTK_TEXT_VIEW (an->description)); - e_buffer_tagger_update_tags (GTK_TEXT_VIEW (an->description)); - g_object_unref (table); - g_object_unref (buffer); -} diff --git a/calendar/gui/alarm-notify/alarm-notify-dialog.h b/calendar/gui/alarm-notify/alarm-notify-dialog.h deleted file mode 100644 index ad7db99432..0000000000 --- a/calendar/gui/alarm-notify/alarm-notify-dialog.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef ALARM_NOTIFY_DIALOG_H -#define ALARM_NOTIFY_DIALOG_H - -#include -#include -#include - -typedef enum { - ALARM_NOTIFY_CLOSE, - ALARM_NOTIFY_SNOOZE, - ALARM_NOTIFY_EDIT, - ALARM_NOTIFY_DISMISS -} AlarmNotifyResult; - -typedef struct _AlarmNotificationsDialog AlarmNotificationsDialog; - -struct _AlarmNotificationsDialog { - GtkWidget *dialog; - GtkWidget *treeview; -}; - -typedef void (*AlarmNotifyFunc) (AlarmNotifyResult result, - gint snooze_mins, - gpointer data); - -AlarmNotificationsDialog * - notified_alarms_dialog_new (void); -GtkTreeIter add_alarm_to_notified_alarms_dialog - (AlarmNotificationsDialog *na, - time_t trigger, - time_t occur_start, - time_t occur_end, - ECalComponentVType vtype, - const gchar *summary, - const gchar *description, - const gchar *location, - AlarmNotifyFunc func, - gpointer func_data); - -#endif /* ALARM_NOTIFY_DIALOG_H */ diff --git a/calendar/gui/alarm-notify/alarm-notify.c b/calendar/gui/alarm-notify/alarm-notify.c deleted file mode 100644 index eeca88e1b6..0000000000 --- a/calendar/gui/alarm-notify/alarm-notify.c +++ /dev/null @@ -1,566 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include "alarm.h" -#include "alarm-notify.h" -#include "alarm-queue.h" -#include "config-data.h" - -#define ALARM_NOTIFY_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), TYPE_ALARM_NOTIFY, AlarmNotifyPrivate)) - -#define APPLICATION_ID "org.gnome.EvolutionAlarmNotify" - -struct _AlarmNotifyPrivate { - /* Mapping from EUri's to LoadedClient structures */ - /* FIXME do we need per source type uri hashes? or perhaps we - * just need to hash based on source */ - GHashTable *uri_client_hash[E_CAL_CLIENT_SOURCE_TYPE_LAST]; - ESourceList *source_lists[E_CAL_CLIENT_SOURCE_TYPE_LAST]; - ESourceList *selected_calendars; - GMutex *mutex; - - GSList *offline_sources; - guint offline_timeout_id; -}; - -typedef struct { - AlarmNotify *an; - ESourceList *source_list; - GList *removals; -} ProcessRemovalsData; - -/* Forward Declarations */ -static void alarm_notify_initable_init (GInitableIface *interface); - -G_DEFINE_TYPE_WITH_CODE ( - AlarmNotify, alarm_notify, GTK_TYPE_APPLICATION, - G_IMPLEMENT_INTERFACE ( - G_TYPE_INITABLE, alarm_notify_initable_init)) - -static void -process_removal_in_hash (const gchar *uri, - gpointer value, - ProcessRemovalsData *prd) -{ - GSList *groups, *sources, *p, *q; - gboolean found = FALSE; - - /* search the list of selected calendars */ - groups = e_source_list_peek_groups (prd->source_list); - for (p = groups; p != NULL; p = p->next) { - ESourceGroup *group = E_SOURCE_GROUP (p->data); - - sources = e_source_group_peek_sources (group); - for (q = sources; q != NULL; q = q->next) { - ESource *source = E_SOURCE (q->data); - gchar *source_uri; - const gchar *completion = e_source_get_property (source, "alarm"); - - source_uri = e_source_get_uri (source); - if (strcmp (source_uri, uri) == 0) - if (!completion || !g_ascii_strcasecmp (completion, "true")) - found = TRUE; - - g_free (source_uri); - - if (found) - return; - } - } - - /* not found, so list it for removal */ - prd->removals = g_list_prepend (prd->removals, (gpointer) uri); -} - -static gint -find_slist_source_uri_cb (gconstpointer a, - gconstpointer b) -{ - ESource *asource = (ESource *) a; - const gchar *buri = b; - gchar *auri; - gint res; - - auri = e_source_get_uri (asource); - res = g_strcmp0 (auri, buri); - g_free (auri); - - return res; -} - -static void -alarm_notify_list_changed_cb (ESourceList *source_list, - AlarmNotify *an) -{ - GSList *groups, *sources, *p, *q; - ECalClientSourceType source_type = E_CAL_CLIENT_SOURCE_TYPE_LAST; - ProcessRemovalsData prd; - GList *l; - gint i; - - g_signal_handlers_block_by_func ( - source_list, alarm_notify_list_changed_cb, an); - - /* Figure out the source type */ - for (i = 0; i < E_CAL_CLIENT_SOURCE_TYPE_LAST; i++) { - if (source_list == an->priv->source_lists[i]) { - source_type = i; - break; - } - } - if (source_type == E_CAL_CLIENT_SOURCE_TYPE_LAST) - return; - - /* process the additions */ - groups = e_source_list_peek_groups (source_list); - for (p = groups; p != NULL; p = p->next) { - ESourceGroup *group = E_SOURCE_GROUP (p->data); - - sources = e_source_group_peek_sources (group); - for (q = sources; q != NULL; q = q->next) { - ESource *source = E_SOURCE (q->data); - gchar *uri; - const gchar *alarm = e_source_get_property (source, "alarm"); - - if (alarm && (!g_ascii_strcasecmp (alarm, "false") || - !g_ascii_strcasecmp (alarm, "never"))) - continue; - - uri = e_source_get_uri (source); - if (!g_hash_table_lookup (an->priv->uri_client_hash[source_type], uri) && - !g_slist_find_custom (an->priv->offline_sources, uri, find_slist_source_uri_cb)) { - debug (("Adding Calendar %s", uri)); - alarm_notify_add_calendar (an, source_type, source); - } - g_free (uri); - } - } - - /* process the removals */ - prd.an = an; - prd.source_list = an->priv->source_lists[source_type]; - prd.removals = NULL; - g_hash_table_foreach ( - an->priv->uri_client_hash[source_type], - (GHFunc) process_removal_in_hash, &prd); - - for (l = prd.removals; l; l = l->next) { - debug (("Removing Calendar %s", (gchar *)l->data)); - alarm_notify_remove_calendar (an, source_type, l->data); - } - g_list_free (prd.removals); - g_signal_handlers_unblock_by_func ( - source_list, alarm_notify_list_changed_cb, an); - -} - -static void -alarm_notify_load_calendars (AlarmNotify *an, - ECalClientSourceType source_type) -{ - ESourceList *source_list; - GSList *groups, *sources, *p, *q; - - if (!e_cal_client_get_sources (&source_list, source_type, NULL)) { - debug (("Cannont get sources")); - an->priv->source_lists[source_type] = NULL; - - return; - } - - groups = e_source_list_peek_groups (source_list); - for (p = groups; p != NULL; p = p->next) { - ESourceGroup *group = E_SOURCE_GROUP (p->data); - - sources = e_source_group_peek_sources (group); - for (q = sources; q != NULL; q = q->next) { - ESource *source = E_SOURCE (q->data); - gchar *uri; - const gchar *alarm = e_source_get_property (source, "alarm"); - - if (alarm && (!g_ascii_strcasecmp (alarm, "false") || !g_ascii_strcasecmp (alarm, "never"))) - continue; - - uri = e_source_get_uri (source); - debug (("Loading Calendar %s", uri)); - alarm_notify_add_calendar (an, source_type, source); - g_free (uri); - - } - } - - e_source_list_sync (source_list, NULL); - g_signal_connect_object ( - source_list, "changed", - G_CALLBACK (alarm_notify_list_changed_cb), an, 0); - an->priv->source_lists[source_type] = source_list; -} - -static void -alarm_notify_dequeue_client (gpointer key, - ECalClient *client) -{ - alarm_queue_remove_client (client, TRUE); -} - -static void -alarm_notify_finalize (GObject *object) -{ - AlarmNotifyPrivate *priv; - gint ii; - - priv = ALARM_NOTIFY_GET_PRIVATE (object); - - if (priv->offline_timeout_id) - g_source_remove (priv->offline_timeout_id); - priv->offline_timeout_id = 0; - g_slist_free_full (priv->offline_sources, g_object_unref); - priv->offline_sources = NULL; - - for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) { - g_hash_table_foreach ( - priv->uri_client_hash[ii], - (GHFunc) alarm_notify_dequeue_client, NULL); - g_hash_table_destroy (priv->uri_client_hash[ii]); - } - - alarm_queue_done (); - alarm_done (); - - e_passwords_shutdown (); - - g_mutex_free (priv->mutex); - - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (alarm_notify_parent_class)->finalize (object); -} - -static void -alarm_notify_startup (GApplication *application) -{ - GtkIconTheme *icon_theme; - - /* Chain up to parent's startup() method. */ - G_APPLICATION_CLASS (alarm_notify_parent_class)->startup (application); - - /* Keep the application running. */ - g_application_hold (application); - - config_data_init_debugging (); - - /* FIXME Ideally we should not use Camel libraries in calendar, - * though it is the case currently for attachments. Remove - * this once that is fixed. */ - - /* Initialize Camel's type system. */ - camel_object_get_type (); - - icon_theme = gtk_icon_theme_get_default (); - gtk_icon_theme_append_search_path (icon_theme, EVOLUTION_ICONDIR); -} - -static void -alarm_notify_activate (GApplication *application) -{ - /* Disregard. This is just here to prevent the default - * activate method from running, which issues a warning - * if there are no handlers connected to this signal. */ -} - -static gboolean -alarm_notify_initable (GInitable *initable, - GCancellable *cancellable, - GError **error) -{ - /* XXX Just return TRUE for now. We'll have use for this soon. */ - return TRUE; -} - -static void -alarm_notify_class_init (AlarmNotifyClass *class) -{ - GObjectClass *object_class; - GApplicationClass *application_class; - - g_type_class_add_private (class, sizeof (AlarmNotifyPrivate)); - - object_class = G_OBJECT_CLASS (class); - object_class->finalize = alarm_notify_finalize; - - application_class = G_APPLICATION_CLASS (class); - application_class->startup = alarm_notify_startup; - application_class->activate = alarm_notify_activate; -} - -static void -alarm_notify_initable_init (GInitableIface *interface) -{ - /* XXX Awkward name since we're missing an 'E' prefix. */ - interface->init = alarm_notify_initable; -} - -static void -alarm_notify_init (AlarmNotify *an) -{ - gint ii; - - an->priv = ALARM_NOTIFY_GET_PRIVATE (an); - an->priv->mutex = g_mutex_new (); - an->priv->selected_calendars = config_data_get_calendars ( - "/apps/evolution/calendar/sources"); - - for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) - an->priv->uri_client_hash[ii] = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_object_unref); - - alarm_queue_init (an); - - for (ii = 0; ii < E_CAL_CLIENT_SOURCE_TYPE_LAST; ii++) - alarm_notify_load_calendars (an, ii); -} - -ESourceList * -alarm_notify_get_selected_calendars (AlarmNotify *an) -{ - return an->priv->selected_calendars; -} - -/** - * alarm_notify_new: - * - * Creates a new #AlarmNotify object. - * - * Returns: a newly-created #AlarmNotify - **/ -AlarmNotify * -alarm_notify_new (GCancellable *cancellable, - GError **error) -{ - return g_initable_new ( - TYPE_ALARM_NOTIFY, cancellable, error, - "application-id", APPLICATION_ID, NULL); -} - -static gboolean -try_open_offline_timeout_cb (gpointer user_data) -{ - AlarmNotify *an = ALARM_NOTIFY (user_data); - GSList *sources, *iter; - - g_return_val_if_fail (an != NULL, FALSE); - g_return_val_if_fail (an->priv != NULL, FALSE); - - sources = an->priv->offline_sources; - an->priv->offline_sources = NULL; - an->priv->offline_timeout_id = 0; - - for (iter = sources; iter; iter = iter->next) { - ESource *source = iter->data; - - alarm_notify_add_calendar (an, - GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (source), "source-type")), - source); - } - - g_slist_free_full (sources, g_object_unref); - - return FALSE; -} - -static void -client_opened_cb (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - ESource *source = E_SOURCE (source_object); - AlarmNotify *an = ALARM_NOTIFY (user_data); - EClient *client = NULL; - ECalClient *cal_client; - ECalClientSourceType source_type; - const gchar *uri; - GError *error = NULL; - - e_client_utils_open_new_finish (source, result, &client, &error); - - if (client == NULL) { - if (g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_REPOSITORY_OFFLINE)) { - if (an->priv->offline_timeout_id) - g_source_remove (an->priv->offline_timeout_id); - an->priv->offline_sources = g_slist_append (an->priv->offline_sources, g_object_ref (source)); - an->priv->offline_timeout_id = g_timeout_add_seconds (5 * 60, try_open_offline_timeout_cb, an); - } - - g_clear_error (&error); - - return; - } - - cal_client = E_CAL_CLIENT (client); - source_type = e_cal_client_get_source_type (cal_client); - uri = e_client_get_uri (client); - - g_hash_table_insert ( - an->priv->uri_client_hash[source_type], - g_strdup (uri), cal_client); - - /* to resolve floating DATE-TIME properly */ - e_cal_client_set_default_timezone ( - cal_client, config_data_get_timezone ()); - - alarm_queue_add_client (cal_client); -} - -/** - * alarm_notify_add_calendar: - * @an: An alarm notification service. - * @uri: URI of the calendar to load. - * - * Tells the alarm notification service to load a calendar and start monitoring - * its alarms. It can optionally be made to save the URI of this calendar so - * that it can be loaded in the future when the alarm daemon starts up. - **/ -void -alarm_notify_add_calendar (AlarmNotify *an, - ECalClientSourceType source_type, - ESource *source) -{ - AlarmNotifyPrivate *priv; - EClientSourceType client_source_type; - EUri *e_uri; - gchar *str_uri; - gchar *pass_key; - g_return_if_fail (an != NULL); - g_return_if_fail (IS_ALARM_NOTIFY (an)); - - /* Make sure the key used in for getting password is - * properly generated for all types of backends. */ - priv = an->priv; - str_uri = e_source_get_uri (source); - e_uri = e_uri_new (str_uri); - if (e_source_get_property (source, "auth-type")) - pass_key = e_uri_to_string (e_uri, FALSE); - else - pass_key = g_strdup (str_uri); - e_uri_free (e_uri); - - g_mutex_lock (an->priv->mutex); - /* See if we already know about this uri */ - if (g_hash_table_lookup (priv->uri_client_hash[source_type], str_uri)) { - g_mutex_unlock (an->priv->mutex); - g_free (str_uri); - g_free (pass_key); - return; - } - - /* If loading of this requires password and password is not - * currently availble in e-password session, skip this source - * loading. We do not really want to prompt for auth from - * the alarm dameon. */ - - if (e_source_get_property (source, "auth")) { - - if (!e_passwords_get_password (NULL, pass_key)) { - g_mutex_unlock (an->priv->mutex); - g_free (str_uri); - g_free (pass_key); - - return; - } - } - - debug (("%s - Calendar Open Async... %p", str_uri, source)); - - switch (source_type) { - case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: - client_source_type = E_CLIENT_SOURCE_TYPE_EVENTS; - break; - case E_CAL_CLIENT_SOURCE_TYPE_TASKS: - client_source_type = E_CLIENT_SOURCE_TYPE_TASKS; - break; - case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: - client_source_type = E_CLIENT_SOURCE_TYPE_MEMOS; - break; - default: - g_warn_if_reached (); - client_source_type = E_CLIENT_SOURCE_TYPE_LAST; - } - - g_object_set_data (G_OBJECT (source), "source-type", GUINT_TO_POINTER (source_type)); - - e_client_utils_open_new ( - source, client_source_type, TRUE, NULL, - e_client_utils_authenticate_handler, NULL, - client_opened_cb, an); - - g_free (str_uri); - g_free (pass_key); - g_mutex_unlock (an->priv->mutex); -} - -void -alarm_notify_remove_calendar (AlarmNotify *an, - ECalClientSourceType source_type, - const gchar *str_uri) -{ - AlarmNotifyPrivate *priv; - ECalClient *cal_client; - GSList *in_offline; - - priv = an->priv; - - cal_client = g_hash_table_lookup ( - priv->uri_client_hash[source_type], str_uri); - if (cal_client) { - debug (("Removing Client %p", cal_client)); - alarm_queue_remove_client (cal_client, FALSE); - g_hash_table_remove (priv->uri_client_hash[source_type], str_uri); - } - - in_offline = g_slist_find_custom (priv->offline_sources, str_uri, find_slist_source_uri_cb); - if (in_offline) { - ESource *source = in_offline->data; - - priv->offline_sources = g_slist_remove (priv->offline_sources, source); - if (!priv->offline_sources && priv->offline_timeout_id) { - g_source_remove (priv->offline_timeout_id); - priv->offline_timeout_id = 0; - } - - g_object_unref (source); - } -} diff --git a/calendar/gui/alarm-notify/alarm-notify.h b/calendar/gui/alarm-notify/alarm-notify.h deleted file mode 100644 index b48cd5a602..0000000000 --- a/calendar/gui/alarm-notify/alarm-notify.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * - * Evolution calendar - Alarm notification service object - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef ALARM_NOTIFY_H -#define ALARM_NOTIFY_H - -#include -#include -#include - -/* Standard GObject macros */ -#define TYPE_ALARM_NOTIFY \ - (alarm_notify_get_type ()) -#define ALARM_NOTIFY(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST \ - ((obj), TYPE_ALARM_NOTIFY, AlarmNotify)) -#define ALARM_NOTIFY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_CAST \ - ((cls), TYPE_ALARM_NOTIFY, AlarmNotifyClass)) -#define IS_ALARM_NOTIFY(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE \ - ((obj), TYPE_ALARM_NOTIFY)) -#define IS_ALARM_NOTIFY_CLASS(cls) \ - (G_TYPE_CHECK_CLASS_TYPE \ - ((cls), TYPE_ALARM_NOTIFY)) -#define ALARM_NOTIFY_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS \ - ((obj), TYPE_ALARM_NOTIFY, AlarmNotifyClass)) - -G_BEGIN_DECLS - -typedef struct _AlarmNotify AlarmNotify; -typedef struct _AlarmNotifyClass AlarmNotifyClass; -typedef struct _AlarmNotifyPrivate AlarmNotifyPrivate; - -struct _AlarmNotify { - GtkApplication parent; - AlarmNotifyPrivate *priv; -}; - -struct _AlarmNotifyClass { - GtkApplicationClass parent_class; -}; - -GType alarm_notify_get_type (void); -AlarmNotify * alarm_notify_new (GCancellable *cancellable, - GError **error); -void alarm_notify_add_calendar (AlarmNotify *an, - ECalClientSourceType source_type, - ESource *source); -void alarm_notify_remove_calendar (AlarmNotify *an, - ECalClientSourceType source_type, - const gchar *str_uri); -ESourceList * alarm_notify_get_selected_calendars - (AlarmNotify *an); - -G_END_DECLS - -#endif /* ALARM_NOTIFY_H */ diff --git a/calendar/gui/alarm-notify/alarm-notify.ui b/calendar/gui/alarm-notify/alarm-notify.ui deleted file mode 100644 index bb418ab569..0000000000 --- a/calendar/gui/alarm-notify/alarm-notify.ui +++ /dev/null @@ -1,362 +0,0 @@ - - - - - - 65535 - 1 - 5 - - - 65535 - 1 - 5 - - - 65535 - 5 - 1 - 10 - - - True - 5 - Appointments - 384 - 200 - dialog - - - True - vertical - - - True - 5 - 12 - - - True - 0 - gtk-dialog-info - 6 - - - False - 0 - - - - - True - 3 - 2 - 6 - 12 - - - 375 - True - True - automatic - automatic - in - - - True - True - False - - - - - 2 - - - - - True - True - automatic - automatic - in - - - True - True - False - word - - - - - 1 - 2 - - - - - True - vertical - 6 - start - - - _Snooze - True - True - True - True - refresh-icon - True - - - False - False - 0 - - - - - gtk-edit - True - True - True - True - True - - - False - False - 1 - - - - - _Dismiss - True - True - True - apply-icon - True - - - False - False - 2 - - - - - 1 - 2 - 1 - 2 - - - - - - - True - 2 - 2 - 6 - 6 - - - True - 0 - Snooze _time: - True - - - 1 - 2 - GTK_FILL - - - - - - True - 6 - - - True - True - - adjustment1 - 1 - - - False - False - 0 - - - - - True - days - - - False - False - 1 - - - - - - True - True - - adjustment2 - 1 - - - False - False - 2 - - - - - True - hours - - - False - False - 3 - - - - - - - True - True - - adjustment3 - 1 - - - False - False - 4 - - - - - True - minutes - - - False - False - 5 - - - - - 1 - 2 - 1 - 2 - GTK_FILL - - - - - True - 0 - location of appointment - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - Location: - True - - - GTK_FILL - - - - - - 2 - 2 - 3 - - - - - - - - - 1 - - - - - 1 - - - - - True - end - - - Dismiss _All - True - True - True - False - close-icon - True - - - False - False - 0 - - - - - False - end - 0 - - - - - - dismiss-all-button - - - - True - gtk-close - - - True - gtk-refresh - - - True - gtk-apply - - diff --git a/calendar/gui/alarm-notify/alarm-queue.c b/calendar/gui/alarm-notify/alarm-queue.c deleted file mode 100644 index 8a4ae2face..0000000000 --- a/calendar/gui/alarm-notify/alarm-queue.c +++ /dev/null @@ -1,2400 +0,0 @@ -/* - * Alarm queueing engine - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include - -#ifdef HAVE_CANBERRA -#include -#endif - -#include -#include -#include - -#ifdef HAVE_LIBNOTIFY -#include -#endif - -#include "alarm.h" -#include "alarm-notify-dialog.h" -#include "alarm-queue.h" -#include "alarm-notify.h" -#include "config-data.h" -#include "util.h" - -/* The dialog with alarm nofications */ -static AlarmNotificationsDialog *alarm_notifications_dialog = NULL; - -/* Whether the queueing system has been initialized */ -static gboolean alarm_queue_inited = FALSE; - -/* Clients we are monitoring for alarms */ -static GHashTable *client_alarms_hash = NULL; - -/* List of tray icons being displayed */ -static GList *tray_icons_list = NULL; - -/* Top Tray Image */ -static GtkStatusIcon *tray_icon = NULL; -static gint tray_blink_id = -1; -static gint tray_blink_countdown = 0; -static AlarmNotify *an; - -/* Structure that stores a client we are monitoring */ -typedef struct { - /* Monitored client */ - ECalClient *cal_client; - - /* The live view to the calendar */ - ECalClientView *view; - - /* Hash table of component UID -> CompQueuedAlarms. If an element is - * present here, then it means its cqa->queued_alarms contains at least - * one queued alarm. When all the alarms for a component have been - * dequeued, the CompQueuedAlarms structure is removed from the hash - * table. Thus a CQA exists <=> it has queued alarms. - */ - GHashTable *uid_alarms_hash; -} ClientAlarms; - -/* Pair of a ECalComponentAlarms and the mapping from queued alarm IDs to the - * actual alarm instance structures. - */ -typedef struct { - /* The parent client alarms structure */ - ClientAlarms *parent_client; - - /* The component's UID */ - ECalComponentId *id; - - /* The actual component and its alarm instances */ - ECalComponentAlarms *alarms; - - /* List of QueuedAlarm structures */ - GSList *queued_alarms; - - /* Flags */ - gboolean expecting_update; -} CompQueuedAlarms; - -/* Pair of a queued alarm ID and the alarm trigger instance it refers to */ -typedef struct { - /* Alarm ID from alarm.h */ - gpointer alarm_id; - - /* Instance from our parent CompQueuedAlarms->alarms->alarms list */ - ECalComponentAlarmInstance *instance; - - /* original trigger of the instance from component */ - time_t orig_trigger; - - /* Whether this is a snoozed queued alarm or a normal one */ - guint snooze : 1; -} QueuedAlarm; - -/* Alarm ID for the midnight refresh function */ -static gpointer midnight_refresh_id = NULL; -static time_t midnight = 0; - -static void remove_client_alarms (ClientAlarms *ca); -static void display_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id, - gboolean use_description); -static void audio_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id); -static void mail_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id); -static void procedure_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id); -#ifdef HAVE_LIBNOTIFY -static void popup_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id, - gboolean use_description); -#endif -static void query_objects_modified_cb (ECalClientView *view, - const GSList *objects, - gpointer data); -static void query_objects_removed_cb (ECalClientView *view, - const GSList *uids, - gpointer data); - -static void update_cqa (CompQueuedAlarms *cqa, - ECalComponent *comp); -static void update_qa (ECalComponentAlarms *alarms, - QueuedAlarm *qa); -static void tray_list_remove_cqa (CompQueuedAlarms *cqa); -static void on_dialog_objs_removed_cb (ECalClientView *view, - const GSList *uids, - gpointer data); - -/* Alarm queue engine */ - -static void load_alarms_for_today (ClientAlarms *ca); -static void midnight_refresh_cb (gpointer alarm_id, - time_t trigger, - gpointer data); - -/* Simple asynchronous message dispatcher */ - -typedef struct _Message Message; -typedef void (*MessageFunc) (Message *msg); - -struct _Message { - MessageFunc func; -}; - -/* -static void -message_proxy (Message *msg) -{ - g_return_if_fail (msg->func != NULL); - * - msg->func (msg); -} - * -static gpointer -create_thread_pool (void) -{ - return g_thread_pool_new ((GFunc) message_proxy, NULL, 1, FALSE, NULL); -}*/ - -static void -message_push (Message *msg) -{ - /* This used be pushed through the thread pool. This fix is made to - * work-around the crashers in dbus due to threading. The threading - * is not completely removed as its better to have alarm daemon - * running in a thread rather than blocking main thread. This is - * the reason the creation of thread pool is commented out. */ - msg->func (msg); -} - -/* - * use a static ring-buffer so we can call this twice - * in a printf without getting nonsense results. - */ -static const gchar * -e_ctime (const time_t *timep) -{ - static gchar *buffer[4] = { 0, }; - static gint next = 0; - const gchar *ret; - - g_free (buffer[next]); - ret = buffer[next++] = g_strdup (ctime (timep)); - if (buffer[next - 1] && *buffer[next - 1]) { - gint len = strlen (buffer[next - 1]); - while (len > 0 && (buffer[next - 1][len - 1] == '\n' || - buffer[next - 1][len - 1] == '\r' || - g_ascii_isspace (buffer[next - 1][len - 1]))) - len--; - - buffer[next - 1][len - 1] = 0; - } - - if (next >= G_N_ELEMENTS (buffer)) - next = 0; - - return ret; -} - -/* Queues an alarm trigger for midnight so that we can load the next - * day's worth of alarms. */ -static void -queue_midnight_refresh (void) -{ - icaltimezone *zone; - - if (midnight_refresh_id != NULL) { - alarm_remove (midnight_refresh_id); - midnight_refresh_id = NULL; - } - - zone = config_data_get_timezone (); - midnight = time_day_end_with_zone (time (NULL), zone); - - debug (("Refresh at %s", e_ctime (&midnight))); - - midnight_refresh_id = alarm_add ( - midnight, midnight_refresh_cb, NULL, NULL); - if (!midnight_refresh_id) { - debug (("Could not setup the midnight refresh alarm")); - /* FIXME: what to do? */ - } -} - -/* Loads a client's alarms; called from g_hash_table_foreach() */ -static void -add_client_alarms_cb (gpointer key, - gpointer value, - gpointer data) -{ - ClientAlarms *ca = (ClientAlarms *) value; - - debug (("Adding %p", ca)); - - load_alarms_for_today (ca); -} - -struct _midnight_refresh_msg { - Message header; - gboolean remove; -}; - -/* Loads the alarms for the new day every midnight */ -static void -midnight_refresh_async (struct _midnight_refresh_msg *msg) -{ - debug (("...")); - - /* Re-load the alarms for all clients */ - g_hash_table_foreach (client_alarms_hash, add_client_alarms_cb, NULL); - - /* Re-schedule the midnight update */ - if (msg->remove && midnight_refresh_id != NULL) { - debug (("Reschedule the midnight update")); - alarm_remove (midnight_refresh_id); - midnight_refresh_id = NULL; - } - - queue_midnight_refresh (); - - g_slice_free (struct _midnight_refresh_msg, msg); -} - -static void -midnight_refresh_cb (gpointer alarm_id, - time_t trigger, - gpointer data) -{ - struct _midnight_refresh_msg *msg; - - msg = g_slice_new0 (struct _midnight_refresh_msg); - msg->header.func = (MessageFunc) midnight_refresh_async; - msg->remove = TRUE; - - message_push ((Message *) msg); -} - -/* Looks up a client in the client alarms hash table */ -static ClientAlarms * -lookup_client (ECalClient *cal_client) -{ - return g_hash_table_lookup (client_alarms_hash, cal_client); -} - -/* Looks up a queued alarm based on its alarm ID */ -static QueuedAlarm * -lookup_queued_alarm (CompQueuedAlarms *cqa, - gpointer alarm_id) -{ - GSList *l; - QueuedAlarm *qa; - - qa = NULL; - - for (l = cqa->queued_alarms; l; l = l->next) { - qa = l->data; - if (qa->alarm_id == alarm_id) - return qa; - } - - /* not found, might have been updated/removed */ - return NULL; -} - -/* Removes an alarm from the list of alarms of a component. If the alarm was - * the last one listed for the component, it removes the component itself. - */ -static gboolean -remove_queued_alarm (CompQueuedAlarms *cqa, - gpointer alarm_id, - gboolean free_object, - gboolean remove_alarm) -{ - QueuedAlarm *qa = NULL; - GSList *l; - - debug (("...")); - - for (l = cqa->queued_alarms; l; l = l->next) { - qa = l->data; - if (qa->alarm_id == alarm_id) - break; - } - - if (!l) - return FALSE; - - cqa->queued_alarms = g_slist_delete_link (cqa->queued_alarms, l); - - if (remove_alarm) { - GError *error = NULL; - ECalComponentId *id; - - id = e_cal_component_get_id (cqa->alarms->comp); - if (id) { - cqa->expecting_update = TRUE; - e_cal_client_discard_alarm_sync ( - cqa->parent_client->cal_client, id->uid, - id->rid, qa->instance->auid, NULL, &error); - cqa->expecting_update = FALSE; - - if (error) { - if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) - g_warning ( - "%s: Failed to discard alarm: %s", - G_STRFUNC, error->message); - g_error_free (error); - } - e_cal_component_free_id (id); - } - } - - g_free (qa); - - /* If this was the last queued alarm for this component, remove the - * component itself. - */ - - if (cqa->queued_alarms != NULL) - return FALSE; - - debug (("Last Component. Removing CQA- Free=%d", free_object)); - if (free_object) { - cqa->id = NULL; - cqa->parent_client = NULL; - e_cal_component_alarms_free (cqa->alarms); - g_free (cqa); - } else { - e_cal_component_alarms_free (cqa->alarms); - cqa->alarms = NULL; - } - - return TRUE; -} - -/** - * has_known_notification: - * Test for notification method and returns if it knows it or not. - * @comp: Component with an alarm. - * @alarm_uid: ID of the alarm in the comp to test. - * - * Returns: %TRUE when we know the notification type, %FALSE otherwise. - */ -static gboolean -has_known_notification (ECalComponent *comp, - const gchar *alarm_uid) -{ - ECalComponentAlarm *alarm; - ECalComponentAlarmAction action; - - g_return_val_if_fail (comp != NULL, FALSE); - g_return_val_if_fail (alarm_uid != NULL, FALSE); - - alarm = e_cal_component_get_alarm (comp, alarm_uid); - if (!alarm) - return FALSE; - - e_cal_component_alarm_get_action (alarm, &action); - e_cal_component_alarm_free (alarm); - - switch (action) { - case E_CAL_COMPONENT_ALARM_AUDIO: - case E_CAL_COMPONENT_ALARM_DISPLAY: - case E_CAL_COMPONENT_ALARM_EMAIL: - case E_CAL_COMPONENT_ALARM_PROCEDURE: - return TRUE; - default: - break; - } - - return FALSE; -} - -/* Callback used when an alarm triggers */ -static void -alarm_trigger_cb (gpointer alarm_id, - time_t trigger, - gpointer data) -{ - CompQueuedAlarms *cqa; - ECalComponent *comp; - QueuedAlarm *qa; - ECalComponentAlarm *alarm; - ECalComponentAlarmAction action; - - cqa = data; - comp = cqa->alarms->comp; - - config_data_set_last_notification_time ( - cqa->parent_client->cal_client, trigger); - debug (("Setting Last notification time to %s", e_ctime (&trigger))); - - qa = lookup_queued_alarm (cqa, alarm_id); - if (!qa) - return; - - /* Decide what to do based on the alarm action. We use the trigger that - * is passed to us instead of the one from the instance structure - * because this may be a snoozed alarm instead of an original - * occurrence. - */ - - alarm = e_cal_component_get_alarm (comp, qa->instance->auid); - if (!alarm) - return; - - e_cal_component_alarm_get_action (alarm, &action); - e_cal_component_alarm_free (alarm); - - switch (action) { - case E_CAL_COMPONENT_ALARM_AUDIO: - audio_notification (trigger, cqa, alarm_id); - break; - - case E_CAL_COMPONENT_ALARM_DISPLAY: -#ifdef HAVE_LIBNOTIFY - popup_notification (trigger, cqa, alarm_id, TRUE); -#endif - display_notification (trigger, cqa, alarm_id, TRUE); - break; - - case E_CAL_COMPONENT_ALARM_EMAIL: - mail_notification (trigger, cqa, alarm_id); - break; - - case E_CAL_COMPONENT_ALARM_PROCEDURE: - procedure_notification (trigger, cqa, alarm_id); - break; - - default: - g_return_if_reached (); - } - debug (("Notification sent: %d", action)); -} - -/* Adds the alarms in a ECalComponentAlarms structure to the alarms queued for a - * particular client. Also puts the triggers in the alarm timer queue. - */ -static void -add_component_alarms (ClientAlarms *ca, - ECalComponentAlarms *alarms) -{ - ECalComponentId *id; - CompQueuedAlarms *cqa; - GSList *l; - - /* No alarms? */ - if (alarms == NULL || alarms->alarms == NULL) { - debug (("No alarms to add")); - if (alarms) - e_cal_component_alarms_free (alarms); - return; - } - - cqa = g_new (CompQueuedAlarms, 1); - cqa->parent_client = ca; - cqa->alarms = alarms; - cqa->expecting_update = FALSE; - - cqa->queued_alarms = NULL; - debug (("Creating CQA %p", cqa)); - - for (l = alarms->alarms; l; l = l->next) { - ECalComponentAlarmInstance *instance; - gpointer alarm_id; - QueuedAlarm *qa; - - instance = l->data; - - if (!has_known_notification (cqa->alarms->comp, instance->auid)) - continue; - - alarm_id = alarm_add ( - instance->trigger, alarm_trigger_cb, cqa, NULL); - if (!alarm_id) - continue; - - qa = g_new (QueuedAlarm, 1); - qa->alarm_id = alarm_id; - qa->instance = instance; - qa->orig_trigger = instance->trigger; - qa->snooze = FALSE; - - cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa); - } - - id = e_cal_component_get_id (alarms->comp); - - /* If we failed to add all the alarms, then we should get rid of the cqa */ - if (cqa->queued_alarms == NULL) { - e_cal_component_alarms_free (cqa->alarms); - cqa->alarms = NULL; - debug (("Failed to add all : %p", cqa)); - g_free (cqa); - return; - } - - cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms); - cqa->id = id; - debug (("Alarm added for %s", id->uid)); - g_hash_table_insert (ca->uid_alarms_hash, cqa->id, cqa); -} - -/* Loads the alarms of a client for a given range of time */ -static void -load_alarms (ClientAlarms *ca, - time_t start, - time_t end) -{ - gchar *str_query, *iso_start, *iso_end; - GError *error = NULL; - - debug (("...")); - - iso_start = isodate_from_time_t (start); - if (!iso_start) - return; - - iso_end = isodate_from_time_t (end); - if (!iso_end) { - g_free (iso_start); - return; - } - - str_query = g_strdup_printf ( - "(has-alarms-in-range? (make-time \"%s\") " - "(make-time \"%s\"))", iso_start, iso_end); - g_free (iso_start); - g_free (iso_end); - - /* create the live query */ - if (ca->view) { - debug (("Disconnecting old queries")); - g_signal_handlers_disconnect_matched ( - ca->view, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, ca); - g_object_unref (ca->view); - ca->view = NULL; - } - - e_cal_client_get_view_sync ( - ca->cal_client, str_query, &ca->view, NULL, &error); - - if (error != NULL) { - g_warning ( - "%s: Could not get query for client: %s", - G_STRFUNC, error->message); - g_error_free (error); - } else { - debug (("Setting Call backs")); - - g_signal_connect ( - ca->view, "objects-added", - G_CALLBACK (query_objects_modified_cb), ca); - g_signal_connect ( - ca->view, "objects-modified", - G_CALLBACK (query_objects_modified_cb), ca); - g_signal_connect ( - ca->view, "objects-removed", - G_CALLBACK (query_objects_removed_cb), ca); - - e_cal_client_view_start (ca->view, &error); - - if (error != NULL) { - g_warning ( - "%s: Failed to start view: %s", - G_STRFUNC, error->message); - g_error_free (error); - } - } - - g_free (str_query); -} - -/* Loads today's remaining alarms for a client */ -static void -load_alarms_for_today (ClientAlarms *ca) -{ - time_t now, from, day_end, day_start; - icaltimezone *zone; - - now = time (NULL); - zone = config_data_get_timezone (); - day_start = time_day_begin_with_zone (now, zone); - - /* Make sure we don't miss some events from the last notification. - * We add 1 to the saved notification time to make the time ranges - * half-open; we do not want to display the "last" displayed alarm - * twice, once when it occurs and once when the alarm daemon restarts. - */ - from = config_data_get_last_notification_time (ca->cal_client) + 1; - if (from <= 0) - from = MAX (from, day_start); - - day_end = time_day_end_with_zone (now, zone); - debug (("From %s to %s", e_ctime (&from), e_ctime (&day_end))); - load_alarms (ca, from, day_end); -} - -/* Looks up a component's queued alarm structure in a client alarms structure */ -static CompQueuedAlarms * -lookup_comp_queued_alarms (ClientAlarms *ca, - const ECalComponentId *id) -{ - return g_hash_table_lookup (ca->uid_alarms_hash, id); -} - -static void -remove_alarms (CompQueuedAlarms *cqa, - gboolean free_object) -{ - GSList *l; - - debug (("Removing for %p", cqa)); - for (l = cqa->queued_alarms; l;) { - QueuedAlarm *qa; - - qa = l->data; - - /* Get the next element here because the list element will go - * away in remove_queued_alarm(). The qa will be freed there as - * well. - */ - l = l->next; - - alarm_remove (qa->alarm_id); - remove_queued_alarm (cqa, qa->alarm_id, free_object, FALSE); - } -} - -/* Removes a component an its alarms */ -static void -remove_comp (ClientAlarms *ca, - ECalComponentId *id) -{ - CompQueuedAlarms *cqa; - - debug (("Removing uid %s", id->uid)); - - if (id->rid && !(*(id->rid))) { - g_free (id->rid); - id->rid = NULL; - } - - cqa = lookup_comp_queued_alarms (ca, id); - if (!cqa) - return; - - /* If a component is present, then it means we must have alarms queued - * for it. - */ - g_return_if_fail (cqa->queued_alarms != NULL); - - debug (("Removing CQA %p", cqa)); - remove_alarms (cqa, TRUE); -} - -/* Called when a calendar component changes; we must reload its corresponding - * alarms. - */ -struct _query_msg { - Message header; - GSList *objects; - gpointer data; -}; - -static GSList * -duplicate_ical (const GSList *in_list) -{ - const GSList *l; - GSList *out_list = NULL; - for (l = in_list; l; l = l->next) { - out_list = g_slist_prepend ( - out_list, icalcomponent_new_clone (l->data)); - } - - return g_slist_reverse (out_list); -} - -static GSList * -duplicate_ecal (const GSList *in_list) -{ - const GSList *l; - GSList *out_list = NULL; - for (l = in_list; l; l = l->next) { - ECalComponentId *id, *old; - old = l->data; - id = g_new0 (ECalComponentId, 1); - id->uid = g_strdup (old->uid); - id->rid = g_strdup (old->rid); - out_list = g_slist_prepend (out_list, id); - } - - return g_slist_reverse (out_list); -} - -static gboolean -get_alarms_for_object (ECalClient *cal_client, - const ECalComponentId *id, - time_t start, - time_t end, - ECalComponentAlarms **alarms) -{ - icalcomponent *icalcomp; - ECalComponent *comp; - ECalComponentAlarmAction omit[] = {-1}; - - g_return_val_if_fail (cal_client != NULL, FALSE); - g_return_val_if_fail (id != NULL, FALSE); - g_return_val_if_fail (alarms != NULL, FALSE); - g_return_val_if_fail (start >= 0 && end >= 0, FALSE); - g_return_val_if_fail (start <= end, FALSE); - - if (!e_cal_client_get_object_sync ( - cal_client, id->uid, id->rid, &icalcomp, NULL, NULL)) - return FALSE; - - if (!icalcomp) - return FALSE; - - comp = e_cal_component_new (); - if (!e_cal_component_set_icalcomponent (comp, icalcomp)) { - icalcomponent_free (icalcomp); - g_object_unref (comp); - return FALSE; - } - - *alarms = e_cal_util_generate_alarms_for_comp ( - comp, start, end, omit, e_cal_client_resolve_tzid_cb, - cal_client, e_cal_client_get_default_timezone (cal_client)); - - g_object_unref (comp); - - return TRUE; -} - -static void -query_objects_changed_async (struct _query_msg *msg) -{ - ClientAlarms *ca; - time_t from, day_end; - ECalComponentAlarms *alarms; - gboolean found; - icaltimezone *zone; - CompQueuedAlarms *cqa; - GSList *l; - GSList *objects; - - ca = msg->data; - objects = msg->objects; - - from = config_data_get_last_notification_time (ca->cal_client); - if (from == -1) - from = time (NULL); - else - from += 1; /* we add 1 to make sure the alarm is not displayed twice */ - - zone = config_data_get_timezone (); - - day_end = time_day_end_with_zone (time (NULL), zone); - - for (l = objects; l != NULL; l = l->next) { - ECalComponentId *id; - GSList *sl; - ECalComponent *comp = e_cal_component_new (); - - e_cal_component_set_icalcomponent (comp, l->data); - - id = e_cal_component_get_id (comp); - found = get_alarms_for_object (ca->cal_client, id, from, day_end, &alarms); - - if (!found) { - debug (("No Alarm found for client %p", ca->cal_client)); - tray_list_remove_cqa (lookup_comp_queued_alarms (ca, id)); - remove_comp (ca, id); - g_hash_table_remove (ca->uid_alarms_hash, id); - e_cal_component_free_id (id); - g_object_unref (comp); - comp = NULL; - continue; - } - - cqa = lookup_comp_queued_alarms (ca, id); - if (!cqa) { - debug (("No currently queued alarms for %s", id->uid)); - add_component_alarms (ca, alarms); - g_object_unref (comp); - comp = NULL; - continue; - } - - debug (("Alarm Already Exist for %s", id->uid)); - /* If the alarms or the alarms list is empty, - * remove it after updating the cqa structure. */ - if (alarms == NULL || alarms->alarms == NULL) { - - /* Update the cqa and its queued alarms - * for changes in summary and alarm_uid. */ - update_cqa (cqa, comp); - - if (alarms) - e_cal_component_alarms_free (alarms); - continue; - } - - /* if already in the list, just update it */ - remove_alarms (cqa, FALSE); - cqa->alarms = alarms; - cqa->queued_alarms = NULL; - - /* add the new alarms */ - for (sl = cqa->alarms->alarms; sl; sl = sl->next) { - ECalComponentAlarmInstance *instance; - gpointer alarm_id; - QueuedAlarm *qa; - - instance = sl->data; - - if (!has_known_notification (cqa->alarms->comp, instance->auid)) - continue; - - alarm_id = alarm_add (instance->trigger, alarm_trigger_cb, cqa, NULL); - if (!alarm_id) - continue; - - qa = g_new (QueuedAlarm, 1); - qa->alarm_id = alarm_id; - qa->instance = instance; - qa->snooze = FALSE; - qa->orig_trigger = instance->trigger; - cqa->queued_alarms = g_slist_prepend (cqa->queued_alarms, qa); - debug (("Adding %p to queue", qa)); - } - - cqa->queued_alarms = g_slist_reverse (cqa->queued_alarms); - g_object_unref (comp); - comp = NULL; - } - g_slist_free (objects); - - g_slice_free (struct _query_msg, msg); -} - -static void -query_objects_modified_cb (ECalClientView *view, - const GSList *objects, - gpointer data) -{ - struct _query_msg *msg; - - msg = g_slice_new0 (struct _query_msg); - msg->header.func = (MessageFunc) query_objects_changed_async; - msg->objects = duplicate_ical (objects); - msg->data = data; - - message_push ((Message *) msg); -} - -/* Called when a calendar component is removed; we must delete its corresponding - * alarms. - */ -static void -query_objects_removed_async (struct _query_msg *msg) -{ - ClientAlarms *ca; - GSList *l; - GSList *objects; - - ca = msg->data; - objects = msg->objects; - - debug (("Removing %d objects", g_slist_length (objects))); - - for (l = objects; l != NULL; l = l->next) { - /* If the alarm is already triggered remove it. */ - tray_list_remove_cqa (lookup_comp_queued_alarms (ca, l->data)); - remove_comp (ca, l->data); - g_hash_table_remove (ca->uid_alarms_hash, l->data); - e_cal_component_free_id (l->data); - } - - g_slist_free (objects); - - g_slice_free (struct _query_msg, msg); -} - -static void -query_objects_removed_cb (ECalClientView *view, - const GSList *uids, - gpointer data) -{ - struct _query_msg *msg; - - msg = g_slice_new0 (struct _query_msg); - msg->header.func = (MessageFunc) query_objects_removed_async; - msg->objects = duplicate_ecal (uids); - msg->data = data; - - message_push ((Message *) msg); -} - -/* Notification functions */ - -/* Creates a snooze alarm based on an existing one. The snooze offset is - * compued with respect to the current time. - */ -static void -create_snooze (CompQueuedAlarms *cqa, - gpointer alarm_id, - gint snooze_mins) -{ - QueuedAlarm *orig_qa; - time_t t; - gpointer new_id; - - orig_qa = lookup_queued_alarm (cqa, alarm_id); - if (!orig_qa) - return; - - t = time (NULL); - t += snooze_mins * 60; - - new_id = alarm_add (t, alarm_trigger_cb, cqa, NULL); - if (!new_id) { - debug (("Unable to schedule trigger for %s", e_ctime (&t))); - return; - } - - orig_qa->instance->trigger = t; - orig_qa->alarm_id = new_id; - orig_qa->snooze = TRUE; - debug (("Adding a alarm at %s", e_ctime (&t))); -} - -/* Launches a component editor for a component */ -static void -edit_component (ECalClient *cal_client, - ECalComponent *comp) -{ - ESource *source; - gchar *command_line; - const gchar *scheme; - const gchar *comp_uid; - const gchar *source_uid; - GError *error = NULL; - - /* XXX Don't we have a function to construct these URIs? - * How are other apps expected to know this stuff? */ - - source = e_client_get_source (E_CLIENT (cal_client)); - source_uid = e_source_peek_uid (source); - - e_cal_component_get_uid (comp, &comp_uid); - - switch (e_cal_client_get_source_type (cal_client)) { - case E_CAL_CLIENT_SOURCE_TYPE_EVENTS: - scheme = "calendar:"; - break; - case E_CAL_CLIENT_SOURCE_TYPE_TASKS: - scheme = "task:"; - break; - case E_CAL_CLIENT_SOURCE_TYPE_MEMOS: - scheme = "memo:"; - break; - default: - g_return_if_reached (); - } - - command_line = g_strdup_printf ( - "%s %s///?source-uid=%s&comp-uid=%s", - PACKAGE, scheme, source_uid, comp_uid); - - if (!g_spawn_command_line_async (command_line, &error)) { - g_critical ("%s", error->message); - g_error_free (error); - } - - g_free (command_line); -} - -typedef struct { - gchar *summary; - gchar *description; - gchar *location; - gboolean blink_state; - gboolean snooze_set; - gint blink_id; - time_t trigger; - CompQueuedAlarms *cqa; - gpointer alarm_id; - ECalComponent *comp; - ECalClient *cal_client; - ECalClientView *view; - GdkPixbuf *image; - GtkTreeIter iter; -} TrayIconData; - -static void -free_tray_icon_data (TrayIconData *tray_data) -{ - g_return_if_fail (tray_data != NULL); - - if (tray_data->summary) { - g_free (tray_data->summary); - tray_data->summary = NULL; - } - - if (tray_data->description) { - g_free (tray_data->description); - tray_data->description = NULL; - } - - if (tray_data->location) { - g_free (tray_data->location); - tray_data->location = NULL; - } - - g_object_unref (tray_data->cal_client); - tray_data->cal_client = NULL; - - g_signal_handlers_disconnect_matched ( - tray_data->view, G_SIGNAL_MATCH_FUNC, - 0, 0, NULL, on_dialog_objs_removed_cb, NULL); - g_object_unref (tray_data->view); - tray_data->view = NULL; - - g_object_unref (tray_data->comp); - tray_data->comp = NULL; - - tray_data->cqa = NULL; - tray_data->alarm_id = NULL; - tray_data->image = NULL; - - g_free (tray_data); -} - -static void -on_dialog_objs_removed_async (struct _query_msg *msg) -{ - TrayIconData *tray_data; - GSList *l, *objects; - ECalComponentId *our_id; - - debug (("...")); - - tray_data = msg->data; - objects = msg->objects; - - our_id = e_cal_component_get_id (tray_data->comp); - g_return_if_fail (our_id); - - for (l = objects; l != NULL; l = l->next) { - ECalComponentId *id = l->data; - - if (!id) - continue; - - if (g_strcmp0 (id->uid, our_id->uid) == 0&& g_strcmp0 (id->rid, our_id->rid) == 0) { - tray_data->cqa = NULL; - tray_data->alarm_id = NULL; - tray_icons_list = g_list_remove (tray_icons_list, tray_data); - tray_data = NULL; - } - - e_cal_component_free_id (id); - } - - e_cal_component_free_id (our_id); - g_slist_free (objects); - g_slice_free (struct _query_msg, msg); -} - -static void -on_dialog_objs_removed_cb (ECalClientView *view, - const GSList *uids, - gpointer data) -{ - struct _query_msg *msg; - - msg = g_slice_new0 (struct _query_msg); - msg->header.func = (MessageFunc) on_dialog_objs_removed_async; - msg->objects = duplicate_ecal (uids); - msg->data = data; - - message_push ((Message *) msg); -} - -struct _tray_cqa_msg { - Message header; - CompQueuedAlarms *cqa; -}; - -static void -tray_list_remove_cqa_async (struct _tray_cqa_msg *msg) -{ - CompQueuedAlarms *cqa = msg->cqa; - GList *list = tray_icons_list; - - debug (("Removing CQA %p from tray list", cqa)); - - while (list) { - TrayIconData *tray_data = list->data; - GList *tmp = list; - GtkTreeModel *model; - - list = list->next; - if (tray_data->cqa == cqa) { - debug (("Found")); - tray_icons_list = g_list_delete_link (tray_icons_list, tmp); - if (alarm_notifications_dialog) { - model = gtk_tree_view_get_model ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - gtk_list_store_remove (GTK_LIST_STORE (model), &(tray_data->iter)); - } - free_tray_icon_data (tray_data); - } - } - - debug (("%d alarms left", g_list_length (tray_icons_list))); - - if (alarm_notifications_dialog) { - if (!g_list_length (tray_icons_list)) { - gtk_widget_destroy (alarm_notifications_dialog->dialog); - g_free (alarm_notifications_dialog); - alarm_notifications_dialog = NULL; - } else { - GtkTreeIter iter; - GtkTreeModel *model; - GtkTreeSelection *sel; - - model = gtk_tree_view_get_model ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - gtk_tree_model_get_iter_first (model, &iter); - sel = gtk_tree_view_get_selection ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - gtk_tree_selection_select_iter (sel, &iter); - } - } - - g_slice_free (struct _tray_cqa_msg, msg); -} - -static void -tray_list_remove_cqa (CompQueuedAlarms *cqa) -{ - struct _tray_cqa_msg *msg; - - msg = g_slice_new0 (struct _tray_cqa_msg); - msg->header.func = (MessageFunc) tray_list_remove_cqa_async; - msg->cqa = cqa; - - message_push ((Message *) msg); -} - -/* Callback used from the alarm notify dialog */ -static void -tray_list_remove_async (Message *msg) -{ - GList *list = tray_icons_list; - - debug (("Removing %d alarms", g_list_length (list))); - while (list != NULL) { - - TrayIconData *tray_data = list->data; - - if (!tray_data->snooze_set) { - GList *temp = list->next; - gboolean status; - - tray_icons_list = g_list_remove_link (tray_icons_list, list); - status = remove_queued_alarm ( - tray_data->cqa, - tray_data->alarm_id, FALSE, TRUE); - if (status) { - g_hash_table_remove ( - tray_data->cqa->parent_client->uid_alarms_hash, - tray_data->cqa->id); - e_cal_component_free_id (tray_data->cqa->id); - g_free (tray_data->cqa); - } - free_tray_icon_data (tray_data); - tray_data = NULL; - g_list_free_1 (list); - if (tray_icons_list != list) /* List head is modified */ - list = tray_icons_list; - else - list = temp; - } else - list = list->next; - } - - g_slice_free (Message, msg); -} - -static void -tray_list_remove_icons (void) -{ - Message *msg; - - msg = g_slice_new0 (Message); - msg->func = tray_list_remove_async; - - message_push (msg); -} - -struct _tray_msg { - Message header; - TrayIconData *data; -}; - -static void -tray_list_remove_data_async (struct _tray_msg *msg) -{ - TrayIconData *tray_data = msg->data; - - debug (("Removing %p from tray list", tray_data)); - - tray_icons_list = g_list_remove_all (tray_icons_list, tray_data); - free_tray_icon_data (tray_data); - tray_data = NULL; - - g_slice_free (struct _tray_msg, msg); -} - -static void -tray_list_remove_data (TrayIconData *data) -{ - struct _tray_msg *msg; - - msg = g_slice_new0 (struct _tray_msg); - msg->header.func = (MessageFunc) tray_list_remove_data_async; - msg->data = data; - - message_push ((Message *) msg); -} - -static void -notify_dialog_cb (AlarmNotifyResult result, - gint snooze_mins, - gpointer data) -{ - TrayIconData *tray_data = data; - - debug (("Received from dialog")); - - g_signal_handlers_disconnect_matched ( - tray_data->view, G_SIGNAL_MATCH_FUNC, - 0, 0, NULL, on_dialog_objs_removed_cb, NULL); - - switch (result) { - case ALARM_NOTIFY_SNOOZE: - debug (("Creating a snooze")); - create_snooze (tray_data->cqa, tray_data->alarm_id, snooze_mins); - tray_data->snooze_set = TRUE; - tray_list_remove_data (tray_data); - if (alarm_notifications_dialog) { - GtkTreeSelection *selection = - gtk_tree_view_get_selection ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - GtkTreeIter iter; - GtkTreeModel *model = NULL; - - /* We can also use tray_data->iter */ - if (gtk_tree_selection_get_selected (selection, &model, &iter)) { - gtk_list_store_remove (GTK_LIST_STORE (model), &iter); - if (!gtk_tree_model_get_iter_first (model, &iter)) { - /* We removed the last one */ - gtk_widget_destroy (alarm_notifications_dialog->dialog); - g_free (alarm_notifications_dialog); - alarm_notifications_dialog = NULL; - } else { - /* Select the first */ - gtk_tree_selection_select_iter (selection, &iter); - } - } - - } - - break; - - case ALARM_NOTIFY_EDIT: - edit_component (tray_data->cal_client, tray_data->comp); - - break; - - case ALARM_NOTIFY_DISMISS: - if (alarm_notifications_dialog) { - GtkTreeModel *model; - - model = gtk_tree_view_get_model ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - gtk_list_store_remove (GTK_LIST_STORE (model), &tray_data->iter); - } - break; - - case ALARM_NOTIFY_CLOSE: - debug (("Dialog close")); - if (alarm_notifications_dialog) { - GtkTreeIter iter; - GtkTreeModel *model = - gtk_tree_view_get_model ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - gboolean valid = gtk_tree_model_get_iter_first (model, &iter); - - /* Maybe we should warn about this first? */ - while (valid) { - valid = gtk_list_store_remove (GTK_LIST_STORE (model), &iter); - } - - gtk_widget_destroy (alarm_notifications_dialog->dialog); - g_free (alarm_notifications_dialog); - alarm_notifications_dialog = NULL; - - /* Task to remove the tray icons */ - tray_list_remove_icons (); - } - - break; - - default: - g_return_if_reached (); - } - - return; -} - -static void -remove_tray_icon (void) -{ - if (tray_blink_id > -1) - g_source_remove (tray_blink_id); - tray_blink_id = -1; - - if (tray_icon) { - gtk_status_icon_set_visible (tray_icon, FALSE); - g_object_unref (tray_icon); - tray_icon = NULL; - } -} - -/* Callbacks. */ -static gboolean -open_alarm_dialog (TrayIconData *tray_data) -{ - QueuedAlarm *qa; - - debug (("...")); - qa = lookup_queued_alarm (tray_data->cqa, tray_data->alarm_id); - if (qa) { - remove_tray_icon (); - - if (!alarm_notifications_dialog) - alarm_notifications_dialog = notified_alarms_dialog_new (); - - if (alarm_notifications_dialog) { - - GtkTreeSelection *selection = NULL; - - selection = gtk_tree_view_get_selection ( - GTK_TREE_VIEW (alarm_notifications_dialog->treeview)); - - tray_data->iter = add_alarm_to_notified_alarms_dialog ( - alarm_notifications_dialog, - tray_data->trigger, - qa->instance->occur_start, - qa->instance->occur_end, - e_cal_component_get_vtype (tray_data->comp), - tray_data->summary, - tray_data->description, - tray_data->location, - notify_dialog_cb, tray_data); - - gtk_tree_selection_select_iter ( - selection, &tray_data->iter); - - } - - } else { - remove_tray_icon (); - } - - return TRUE; -} - -static gint -tray_icon_clicked_cb (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data) -{ - if (event->type == GDK_BUTTON_PRESS) { - debug (("left click and %d alarms", g_list_length (tray_icons_list))); - if (event->button == 1 && g_list_length (tray_icons_list) > 0) { - GList *tmp; - for (tmp = tray_icons_list; tmp; tmp = tmp->next) { - open_alarm_dialog (tmp->data); - } - - return TRUE; - } else if (event->button == 3) { - debug (("right click")); - - remove_tray_icon (); - return TRUE; - } - } - - return FALSE; -} - -static void -icon_activated (GtkStatusIcon *icon) -{ - GdkEventButton event; - - event.type = GDK_BUTTON_PRESS; - event.button = 1; - event.time = gtk_get_current_event_time (); - - tray_icon_clicked_cb (NULL, &event, NULL); -} - -static void -popup_menu (GtkStatusIcon *icon, - guint button, - guint activate_time) -{ - if (button == 3) { - /* right click */ - GdkEventButton event; - - event.type = GDK_BUTTON_PRESS; - event.button = 3; - event.time = gtk_get_current_event_time (); - - tray_icon_clicked_cb (NULL, &event, NULL); - } -} - -static gboolean -tray_icon_blink_cb (gpointer data) -{ - static gboolean tray_blink_state = FALSE; - const gchar *icon_name; - - tray_blink_countdown--; - tray_blink_state = !tray_blink_state; - - if (tray_blink_state || tray_blink_countdown <= 0) - icon_name = "appointment-missed"; - else - icon_name = "appointment-soon"; - - if (tray_icon) - gtk_status_icon_set_from_icon_name (tray_icon, icon_name); - - if (tray_blink_countdown <= 0) - tray_blink_id = -1; - - return tray_blink_countdown > 0; -} - -/* Add a new data to tray list */ - -static void -tray_list_add_async (struct _tray_msg *msg) -{ - tray_icons_list = g_list_prepend (tray_icons_list, msg->data); - - g_slice_free (struct _tray_msg, msg); -} - -static void -tray_list_add_new (TrayIconData *data) -{ - struct _tray_msg *msg; - - msg = g_slice_new0 (struct _tray_msg); - msg->header.func = (MessageFunc) tray_list_add_async; - msg->data = data; - - message_push ((Message *) msg); -} - -/* Performs notification of a display alarm */ -static void -display_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id, - gboolean use_description) -{ - QueuedAlarm *qa; - ECalComponent *comp; - const gchar *summary, *description, *location; - TrayIconData *tray_data; - ECalComponentText text; - GSList *text_list; - gchar *str, *start_str, *end_str, *alarm_str, *time_str; - icaltimezone *current_zone; - ECalComponentOrganizer organiser; - - debug (("...")); - - comp = cqa->alarms->comp; - qa = lookup_queued_alarm (cqa, alarm_id); - if (!qa) - return; - - /* get a sensible description for the event */ - e_cal_component_get_summary (comp, &text); - e_cal_component_get_organizer (comp, &organiser); - - if (text.value) - summary = text.value; - else - summary = _("No summary available."); - - e_cal_component_get_description_list (comp, &text_list); - - if (text_list) { - text = *((ECalComponentText *) text_list->data); - if (text.value) - description = text.value; - else - description = _("No description available."); - } else { - description = _("No description available."); - } - - e_cal_component_free_text_list (text_list); - - e_cal_component_get_location (comp, &location); - - if (!location) - location = _("No location information available."); - - /* create the tray icon */ - if (tray_icon == NULL) { - tray_icon = gtk_status_icon_new (); - gtk_status_icon_set_from_icon_name ( - tray_icon, "appointment-soon"); - g_signal_connect ( - tray_icon, "activate", - G_CALLBACK (icon_activated), NULL); - g_signal_connect ( - tray_icon, "popup-menu", - G_CALLBACK (popup_menu), NULL); - } - - current_zone = config_data_get_timezone (); - alarm_str = timet_to_str_with_zone (trigger, current_zone); - start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); - end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); - time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); - - str = g_strdup_printf ("%s\n%s %s", - summary, start_str, time_str); - - /* create the private structure */ - tray_data = g_new0 (TrayIconData, 1); - tray_data->summary = g_strdup (summary); - tray_data->description = g_strdup (description); - tray_data->location = g_strdup (location); - tray_data->trigger = trigger; - tray_data->cqa = cqa; - tray_data->alarm_id = alarm_id; - tray_data->comp = g_object_ref (e_cal_component_clone (comp)); - tray_data->cal_client = cqa->parent_client->cal_client; - tray_data->view = g_object_ref (cqa->parent_client->view); - tray_data->blink_state = FALSE; - tray_data->snooze_set = FALSE; - g_object_ref (tray_data->cal_client); - - /* Task to add tray_data to the global tray_icon_list */ - tray_list_add_new (tray_data); - - if (g_list_length (tray_icons_list) > 1) { - gchar *tip; - - tip = g_strdup_printf (ngettext ( - "You have %d reminder", "You have %d reminders", - g_list_length (tray_icons_list)), - g_list_length (tray_icons_list)); - gtk_status_icon_set_tooltip_text (tray_icon, tip); - } - else { - gtk_status_icon_set_tooltip_text (tray_icon, str); - } - - g_free (start_str); - g_free (end_str); - g_free (alarm_str); - g_free (time_str); - g_free (str); - - g_signal_connect ( - tray_data->view, "objects_removed", - G_CALLBACK (on_dialog_objs_removed_cb), tray_data); - - /* FIXME: We should remove this check */ - if (!config_data_get_notify_with_tray ()) { - tray_blink_id = -1; - open_alarm_dialog (tray_data); - if (alarm_notifications_dialog) - gtk_window_stick (GTK_WINDOW ( - alarm_notifications_dialog->dialog)); - } else { - if (tray_blink_id == -1) { - tray_blink_countdown = 30; - tray_blink_id = g_timeout_add (500, tray_icon_blink_cb, tray_data); - } - } -} - -#ifdef HAVE_LIBNOTIFY -static void -popup_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id, - gboolean use_description) -{ - QueuedAlarm *qa; - ECalComponent *comp; - const gchar *summary, *location; - ECalComponentText text; - gchar *str, *start_str, *end_str, *alarm_str, *time_str; - icaltimezone *current_zone; - ECalComponentOrganizer organiser; - NotifyNotification *n; - gchar *body; - - debug (("...")); - - comp = cqa->alarms->comp; - qa = lookup_queued_alarm (cqa, alarm_id); - if (!qa) - return; - if (!notify_is_initted ()) - notify_init("Evolution Alarm Notify"); - - /* get a sensible description for the event */ - e_cal_component_get_summary (comp, &text); - e_cal_component_get_organizer (comp, &organiser); - - if (text.value) - summary = text.value; - else - summary = _("No summary available."); - - e_cal_component_get_location (comp, &location); - - /* create the tray icon */ - - current_zone = config_data_get_timezone (); - alarm_str = timet_to_str_with_zone (trigger, current_zone); - start_str = timet_to_str_with_zone (qa->instance->occur_start, current_zone); - end_str = timet_to_str_with_zone (qa->instance->occur_end, current_zone); - time_str = calculate_time (qa->instance->occur_start, qa->instance->occur_end); - - str = g_strdup_printf ("%s %s", - start_str, time_str); - - if (organiser.cn) { - if (location) - body = g_strdup_printf ( - "%s\n%s %s\n%s %s", - organiser.cn, _("Location:"), - location, start_str, time_str); - else - body = g_strdup_printf ( - "%s\n%s %s", - organiser.cn, start_str, time_str); - } - else { - if (location) - body = g_strdup_printf ( - "%s %s\n%s %s", _("Location:"), - location, start_str, time_str); - else - body = g_strdup_printf ( - "%s %s", start_str, time_str); - } - -#ifdef HAVE_LIBNOTIFY_07 - n = notify_notification_new (summary, body, "appointment-soon"); -#else - n = notify_notification_new (summary, body, "appointment-soon", NULL); -#endif /* HAVE_LIBNOTIFY_07 */ - if (!notify_notification_show (n, NULL)) - g_warning ("Could not send notification to daemon\n"); - - /* create the private structure */ - g_free (start_str); - g_free (end_str); - g_free (alarm_str); - g_free (time_str); - g_free (str); - -} -#endif - -/* Performs notification of an audio alarm */ -static void -audio_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id) -{ - QueuedAlarm *qa; - ECalComponent *comp; - ECalComponentAlarm *alarm; - icalattach *attach; - gint flag = 0; - - debug (("...")); - - comp = cqa->alarms->comp; - qa = lookup_queued_alarm (cqa, alarm_id); - if (!qa) - return; - - alarm = e_cal_component_get_alarm (comp, qa->instance->auid); - g_return_if_fail (alarm != NULL); - - e_cal_component_alarm_get_attach (alarm, &attach); - e_cal_component_alarm_free (alarm); - - if (attach && icalattach_get_is_url (attach)) { - const gchar *url; - - url = icalattach_get_url (attach); - if (url && *url) { - gchar *filename; - GError *error = NULL; - - filename = g_filename_from_uri (url, NULL, &error); - - if (error != NULL) { - g_warning ("%s: %s", G_STRFUNC, error->message); - g_error_free (error); - } else if (filename && g_file_test (filename, G_FILE_TEST_EXISTS)) { -#ifdef HAVE_CANBERRA - flag = 1; - ca_context_play ( - ca_gtk_context_get (), 0, - CA_PROP_MEDIA_FILENAME, filename, NULL); -#endif - } - - g_free (filename); - } - } - - if (!flag) - gdk_beep (); - - if (attach) - icalattach_unref (attach); - -} - -/* Performs notification of a mail alarm */ -static void -mail_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id) -{ - GtkWidget *container; - GtkWidget *dialog; - GtkWidget *label; - - /* FIXME */ - - debug (("...")); - - if (!e_client_check_capability ( - E_CLIENT (cqa->parent_client->cal_client), - CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS)) - return; - - dialog = gtk_dialog_new_with_buttons ( - _("Warning"), NULL, 0, - GTK_STOCK_OK, GTK_RESPONSE_CANCEL, - NULL); - label = gtk_label_new ( - _("Evolution does not support calendar reminders with\n" - "email notifications yet, but this reminder was\n" - "configured to send an email. Evolution will display\n" - "a normal reminder dialog box instead.")); - gtk_widget_show (label); - - container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); - - gtk_dialog_run (GTK_DIALOG (dialog)); - gtk_widget_destroy (dialog); -} - -/* Performs notification of a procedure alarm */ -static gboolean -procedure_notification_dialog (const gchar *cmd, - const gchar *url) -{ - GtkWidget *container; - GtkWidget *dialog; - GtkWidget *label; - GtkWidget *checkbox; - gchar *str; - gint btn; - - debug (("...")); - - if (config_data_is_blessed_program (url)) - return TRUE; - - dialog = gtk_dialog_new_with_buttons ( - _("Warning"), NULL, 0, - GTK_STOCK_NO, GTK_RESPONSE_CANCEL, - GTK_STOCK_YES, GTK_RESPONSE_OK, - NULL); - - str = g_strdup_printf ( - _("An Evolution Calendar reminder is about to trigger. " - "This reminder is configured to run the following program:\n\n" - " %s\n\n" - "Are you sure you want to run this program?"), - cmd); - label = gtk_label_new (str); - gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); - gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); - gtk_widget_show (label); - - container = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4); - g_free (str); - - checkbox = gtk_check_button_new_with_label - (_("Do not ask me about this program again.")); - gtk_widget_show (checkbox); - gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4); - - /* Run the dialog */ - btn = gtk_dialog_run (GTK_DIALOG (dialog)); - if (btn == GTK_RESPONSE_OK && - gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox))) - config_data_save_blessed_program (url); - gtk_widget_destroy (dialog); - - return (btn == GTK_RESPONSE_OK); -} - -static void -procedure_notification (time_t trigger, - CompQueuedAlarms *cqa, - gpointer alarm_id) -{ - QueuedAlarm *qa; - ECalComponent *comp; - ECalComponentAlarm *alarm; - ECalComponentText description; - icalattach *attach; - const gchar *url; - gchar *cmd; - gboolean result = TRUE; - - debug (("...")); - - comp = cqa->alarms->comp; - qa = lookup_queued_alarm (cqa, alarm_id); - if (!qa) - return; - - alarm = e_cal_component_get_alarm (comp, qa->instance->auid); - g_return_if_fail (alarm != NULL); - - e_cal_component_alarm_get_attach (alarm, &attach); - e_cal_component_alarm_get_description (alarm, &description); - e_cal_component_alarm_free (alarm); - - /* If the alarm has no attachment, simply display a notification dialog. */ - if (!attach) - goto fallback; - - if (!icalattach_get_is_url (attach)) { - icalattach_unref (attach); - goto fallback; - } - - url = icalattach_get_url (attach); - g_return_if_fail (url != NULL); - - /* Ask for confirmation before executing the stuff */ - if (description.value) - cmd = g_strconcat (url, " ", description.value, NULL); - else - cmd = (gchar *) url; - - if (procedure_notification_dialog (cmd, url)) - result = g_spawn_command_line_async (cmd, NULL); - - if (cmd != (gchar *) url) - g_free (cmd); - - icalattach_unref (attach); - - /* Fall back to display notification if we got an error */ - if (result == FALSE) - goto fallback; - - return; - - fallback: - - display_notification (trigger, cqa, alarm_id, FALSE); -} - -static gboolean -check_midnight_refresh (gpointer user_data) -{ - time_t new_midnight; - icaltimezone *zone; - - debug (("...")); - - zone = config_data_get_timezone (); - new_midnight = time_day_end_with_zone (time (NULL), zone); - - if (new_midnight > midnight) { - struct _midnight_refresh_msg *msg; - - msg = g_slice_new0 (struct _midnight_refresh_msg); - msg->header.func = (MessageFunc) midnight_refresh_async; - msg->remove = FALSE; - - message_push ((Message *) msg); - } - - return TRUE; -} - -/** - * alarm_queue_init: - * - * Initializes the alarm queueing system. This should be called near the - * beginning of the program. - **/ -void -alarm_queue_init (gpointer data) -{ - an = data; - g_return_if_fail (alarm_queue_inited == FALSE); - - debug (("...")); - - client_alarms_hash = g_hash_table_new (g_direct_hash, g_direct_equal); - queue_midnight_refresh (); - - if (config_data_get_last_notification_time (NULL) == -1) { - time_t tmval = time_day_begin (time (NULL)); - debug (("Setting last notification time to %s", e_ctime (&tmval))); - config_data_set_last_notification_time (NULL, tmval); - } - - /* install timeout handler (every 30 mins) for not missing the midnight refresh */ - g_timeout_add_seconds (1800, (GSourceFunc) check_midnight_refresh, NULL); - -#ifdef HAVE_LIBNOTIFY - notify_init("Evolution Alarms"); -#endif - - alarm_queue_inited = TRUE; -} - -static gboolean -free_client_alarms_cb (gpointer key, - gpointer value, - gpointer user_data) -{ - ClientAlarms *ca = value; - - debug (("ca=%p", ca)); - - if (ca) { - remove_client_alarms (ca); - if (ca->cal_client) { - debug (("Disconnecting Client")); - - g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA, - 0, 0, NULL, NULL, ca); - g_object_unref (ca->cal_client); - } - - if (ca->view) { - debug (("Disconnecting Query")); - - g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA, - 0, 0, NULL, NULL, ca); - g_object_unref (ca->view); - } - - g_hash_table_destroy (ca->uid_alarms_hash); - - g_free (ca); - return TRUE; - } - - return FALSE; -} - -/** - * alarm_queue_done: - * - * Shuts down the alarm queueing system. This should be called near the end - * of the program. All the monitored calendar clients should already have been - * unregistered with alarm_queue_remove_client(). - **/ -void -alarm_queue_done (void) -{ - g_return_if_fail (alarm_queue_inited); - - /* All clients must be unregistered by now */ - g_return_if_fail (g_hash_table_size (client_alarms_hash) == 0); - - debug (("...")); - - g_hash_table_foreach_remove ( - client_alarms_hash, (GHRFunc) free_client_alarms_cb, NULL); - g_hash_table_destroy (client_alarms_hash); - client_alarms_hash = NULL; - - if (midnight_refresh_id != NULL) { - alarm_remove (midnight_refresh_id); - midnight_refresh_id = NULL; - } - - alarm_queue_inited = FALSE; -} - -static gboolean -compare_ids (gpointer a, - gpointer b) -{ - ECalComponentId *id, *id1; - - id = a; - id1 = b; - if (id->uid != NULL && id1->uid != NULL) { - if (g_str_equal (id->uid, id1->uid)) { - - if (id->rid && id1->rid) - return g_str_equal (id->rid, id1->rid); - else if (!(id->rid && id1->rid)) - return TRUE; - } - } - return FALSE; -} - -static guint -hash_ids (gpointer a) -{ - ECalComponentId *id =a; - - return g_str_hash (id->uid); -} - -struct _alarm_client_msg { - Message header; - ECalClient *cal_client; -}; - -static void -alarm_queue_add_async (struct _alarm_client_msg *msg) -{ - ClientAlarms *ca; - ECalClient *cal_client = msg->cal_client; - - g_return_if_fail (alarm_queue_inited); - g_return_if_fail (cal_client != NULL); - g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); - - ca = lookup_client (cal_client); - if (ca) { - /* We already have it. Unref the passed one*/ - g_object_unref (cal_client); - return; - } - - debug (("client=%p", cal_client)); - - ca = g_new (ClientAlarms, 1); - - ca->cal_client = cal_client; - ca->view = NULL; - - g_hash_table_insert (client_alarms_hash, cal_client, ca); - - ca->uid_alarms_hash = g_hash_table_new ( - (GHashFunc) hash_ids, (GEqualFunc) compare_ids); - - load_alarms_for_today (ca); - - g_slice_free (struct _alarm_client_msg, msg); -} - -/** - * alarm_queue_add_client: - * @cal_client: A calendar client. - * - * Adds a calendar client to the alarm queueing system. Alarm trigger - * notifications will be presented at the appropriate times. The client should - * be removed with alarm_queue_remove_client() when receiving notifications - * from it is no longer desired. - * - * A client can be added any number of times to the alarm queueing system, - * but any single alarm trigger will only be presented once for a particular - * client. The client must still be removed the same number of times from the - * queueing system when it is no longer wanted. - **/ -void -alarm_queue_add_client (ECalClient *cal_client) -{ - struct _alarm_client_msg *msg; - - msg = g_slice_new0 (struct _alarm_client_msg); - msg->header.func = (MessageFunc) alarm_queue_add_async; - msg->cal_client = g_object_ref (cal_client); - - message_push ((Message *) msg); -} - -/* Removes a component an its alarms */ -static void -remove_cqa (ClientAlarms *ca, - ECalComponentId *id, - CompQueuedAlarms *cqa) -{ - - /* If a component is present, then it means we must have alarms queued - * for it. - */ - g_return_if_fail (cqa->queued_alarms != NULL); - - debug (("removing %d alarms", g_slist_length (cqa->queued_alarms))); - remove_alarms (cqa, TRUE); -} - -static gboolean -remove_comp_by_id (gpointer key, - gpointer value, - gpointer userdata) -{ - - ClientAlarms *ca = (ClientAlarms *) userdata; - - debug (("...")); - -/* if (!g_hash_table_size (ca->uid_alarms_hash)) */ -/* return; */ - - remove_cqa (ca, (ECalComponentId *) key, (CompQueuedAlarms *) value); - - return TRUE; -} - -/* Removes all the alarms queued for a particular calendar client */ -static void -remove_client_alarms (ClientAlarms *ca) -{ - debug (("size %d", g_hash_table_size (ca->uid_alarms_hash))); - - g_hash_table_foreach_remove ( - ca->uid_alarms_hash, (GHRFunc) remove_comp_by_id, ca); - - /* The hash table should be empty now */ - g_return_if_fail (g_hash_table_size (ca->uid_alarms_hash) == 0); -} - -/** - * alarm_queue_remove_client: - * @client: A calendar client. - * - * Removes a calendar client from the alarm queueing system. - **/ -static void -alarm_queue_remove_async (struct _alarm_client_msg *msg) -{ - ClientAlarms *ca; - ECalClient *cal_client = msg->cal_client; - - g_return_if_fail (alarm_queue_inited); - g_return_if_fail (cal_client != NULL); - g_return_if_fail (E_IS_CAL_CLIENT (cal_client)); - - ca = lookup_client (cal_client); - g_return_if_fail (ca != NULL); - - debug (("...")); - remove_client_alarms (ca); - - /* Clean up */ - if (ca->cal_client) { - debug (("Disconnecting Client")); - - g_signal_handlers_disconnect_matched (ca->cal_client, G_SIGNAL_MATCH_DATA, - 0, 0, NULL, NULL, ca); - g_object_unref (ca->cal_client); - ca->cal_client = NULL; - } - - if (ca->view) { - debug (("Disconnecting Query")); - - g_signal_handlers_disconnect_matched (ca->view, G_SIGNAL_MATCH_DATA, - 0, 0, NULL, NULL, ca); - g_object_unref (ca->view); - ca->view = NULL; - } - - g_hash_table_destroy (ca->uid_alarms_hash); - ca->uid_alarms_hash = NULL; - - g_free (ca); - - g_hash_table_remove (client_alarms_hash, cal_client); - - g_slice_free (struct _alarm_client_msg, msg); -} - -/** alarm_queue_remove_client - * - * asynchronously remove client from alarm queue. - * @cal_client: Client to remove. - * @immediately: Indicates whether use thread or do it right now. - */ - -void -alarm_queue_remove_client (ECalClient *cal_client, - gboolean immediately) -{ - struct _alarm_client_msg *msg; - - msg = g_slice_new0 (struct _alarm_client_msg); - msg->header.func = (MessageFunc) alarm_queue_remove_async; - msg->cal_client = cal_client; - - if (immediately) { - alarm_queue_remove_async (msg); - } else - message_push ((Message *) msg); -} - -/* Update non-time related variables for various structures on modification - * of an existing component to be called only from query_objects_changed_cb */ -static void -update_cqa (CompQueuedAlarms *cqa, - ECalComponent *newcomp) -{ - ECalComponent *oldcomp; - ECalComponentAlarms *alarms = NULL; - GSList *qa_list; /* List of current QueuedAlarms corresponding to cqa */ - time_t from, to; - icaltimezone *zone; - ECalComponentAlarmAction omit[] = {-1}; - - oldcomp = cqa->alarms->comp; - - zone = config_data_get_timezone (); - from = time_day_begin_with_zone (time (NULL), zone); - to = time_day_end_with_zone (time (NULL), zone); - - debug (("Generating alarms between %s and %s", e_ctime (&from), e_ctime (&to))); - alarms = e_cal_util_generate_alarms_for_comp ( - newcomp, from, to, omit, e_cal_client_resolve_tzid_cb, - cqa->parent_client->cal_client, zone); - - /* Update auids in Queued Alarms*/ - for (qa_list = cqa->queued_alarms; qa_list; qa_list = qa_list->next) { - QueuedAlarm *qa = qa_list->data; - gchar *check_auid = (gchar *) qa->instance->auid; - ECalComponentAlarm *alarm; - - alarm = e_cal_component_get_alarm (newcomp, check_auid); - if (alarm) { - e_cal_component_alarm_free (alarm); - continue; - } else { - alarm = e_cal_component_get_alarm (oldcomp, check_auid); - if (alarm) { /* Need to update QueuedAlarms */ - e_cal_component_alarm_free (alarm); - if (alarms == NULL) { - debug (("No alarms found in the modified component")); - break; - } - update_qa (alarms, qa); - } - else - g_warning ("Failed in auid lookup for old component also\n"); - } - } - - /* Update the actual component stored in CompQueuedAlarms structure */ - g_object_unref (cqa->alarms->comp); - cqa->alarms->comp = newcomp; - if (alarms != NULL ) - e_cal_component_alarms_free (alarms); -} - -static void -update_qa (ECalComponentAlarms *alarms, - QueuedAlarm *qa) -{ - ECalComponentAlarmInstance *al_inst; - GSList *instance_list; - - debug (("...")); - for (instance_list = alarms->alarms; - instance_list; - instance_list = instance_list->next) { - al_inst = instance_list->data; - /* FIXME If two or more alarm instances (audio, note) - * for same component have same trigger... */ - if (al_inst->trigger == qa->orig_trigger) { - g_free ((gchar *) qa->instance->auid); - qa->instance->auid = g_strdup (al_inst->auid); - break; - } - } -} diff --git a/calendar/gui/alarm-notify/alarm-queue.h b/calendar/gui/alarm-notify/alarm-queue.h deleted file mode 100644 index e43027bd60..0000000000 --- a/calendar/gui/alarm-notify/alarm-queue.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * - * Evolution calendar - Alarm queueing engine - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef ALARM_QUEUE_H -#define ALARM_QUEUE_H - -#include - -void alarm_queue_init (gpointer); -void alarm_queue_done (void); - -void alarm_queue_add_client (ECalClient *cal_client); -void alarm_queue_remove_client (ECalClient *cal_client, gboolean immediately); - -#endif diff --git a/calendar/gui/alarm-notify/alarm.c b/calendar/gui/alarm-notify/alarm.c deleted file mode 100644 index 2797d44b0a..0000000000 --- a/calendar/gui/alarm-notify/alarm.c +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Evolution calendar - Low-level alarm timer mechanism - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Miguel de Icaza - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include "alarm.h" -#include "config-data.h" - -/* Our glib timeout */ -static guint timeout_id; - -/* The list of pending alarms */ -static GList *alarms = NULL; - -/* A queued alarm structure */ -typedef struct { - time_t trigger; - AlarmFunction alarm_fn; - gpointer data; - AlarmDestroyNotify destroy_notify_fn; -} AlarmRecord; - -static void setup_timeout (void); - -/* Removes the head alarm from the queue. Does not touch the timeout_id. */ -static void -pop_alarm (void) -{ - AlarmRecord *ar; - GList *l; - - if (!alarms) { - g_warning ("Nothing to pop from the alarm queue"); - return; - } - - ar = alarms->data; - - l = alarms; - alarms = g_list_delete_link (alarms, l); - - g_free (ar); -} - -/* Callback from the alarm timeout */ -static gboolean -alarm_ready_cb (gpointer data) -{ - time_t now; - - if (!alarms) { - g_warning ("Alarm triggered, but no alarm present\n"); - return FALSE; - } - - timeout_id = 0; - - now = time (NULL); - - debug (("Alarm callback!")); - while (alarms) { - AlarmRecord *notify_id, *ar; - AlarmRecord ar_copy; - - ar = alarms->data; - - if (ar->trigger > now) - break; - - debug (("Process alarm with trigger %lu", ar->trigger)); - notify_id = ar; - - ar_copy = *ar; - ar = &ar_copy; - - /* This will free the original AlarmRecord; - * that's why we copy it. */ - pop_alarm (); - - (* ar->alarm_fn) (notify_id, ar->trigger, ar->data); - - if (ar->destroy_notify_fn) - (* ar->destroy_notify_fn) (notify_id, ar->data); - } - - /* We need this check because one of the alarm_fn above may have - * re-entered and added an alarm of its own, so the timer will - * already be set up. - */ - if (alarms) - setup_timeout (); - - return FALSE; -} - -/* Sets up a timeout for the next minute. We do not need to be concerned with - * timezones here, as this is just a periodic check on the alarm queue. - */ -static void -setup_timeout (void) -{ - const AlarmRecord *ar; - guint diff; - time_t now; - - if (!alarms) { - g_warning ("No alarm to setup\n"); - return; - } - - ar = alarms->data; - - /* Remove the existing time out */ - if (timeout_id != 0) { - g_source_remove (timeout_id); - timeout_id = 0; - } - - /* Ensure that if the trigger managed to get behind the - * current time we timeout immediately */ - diff = MAX (0, ar->trigger - time (NULL)); - now = time (NULL); - - /* Add the time out */ - debug (("Setting timeout for %d.%2d (from now) %lu %lu", - diff / 60, diff % 60, ar->trigger, now)); - debug ((" %s", ctime (&ar->trigger))); - debug ((" %s", ctime (&now))); - timeout_id = g_timeout_add_seconds (diff, alarm_ready_cb, NULL); - -} - -/* Used from g_list_insert_sorted(); compares the - * trigger times of two AlarmRecord structures. */ -static gint -compare_alarm_by_time (gconstpointer a, - gconstpointer b) -{ - const AlarmRecord *ara = a; - const AlarmRecord *arb = b; - time_t diff; - - diff = ara->trigger - arb->trigger; - return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; -} - -/* Adds an alarm to the queue and sets up the timer */ -static void -queue_alarm (AlarmRecord *ar) -{ - GList *old_head; - - /* Track the current head of the list in case there are changes */ - old_head = alarms; - - /* Insert the new alarm in order if the alarm's trigger time is - * after the current time */ - alarms = g_list_insert_sorted (alarms, ar, compare_alarm_by_time); - - /* If there first item on the list didn't change, the time out is fine */ - if (old_head == alarms) - return; - - /* Set the timer for removal upon activation */ - setup_timeout (); -} - -/** - * alarm_add: - * @trigger: Time at which alarm will trigger. - * @alarm_fn: Callback for trigger. - * @data: Closure data for callback. - * @destroy_notify_fn: destroy notification callback. - * - * Adds an alarm to trigger at the specified time. The @alarm_fn will be called - * with the provided data and the alarm will be removed from the trigger list. - * - * Return value: An identifier for this alarm; it can be used to remove the - * alarm later with alarm_remove(). If the trigger time occurs in the past, then - * the alarm will not be queued and the function will return NULL. - **/ -gpointer -alarm_add (time_t trigger, - AlarmFunction alarm_fn, - gpointer data, - AlarmDestroyNotify destroy_notify_fn) -{ - AlarmRecord *ar; - - g_return_val_if_fail (trigger != -1, NULL); - g_return_val_if_fail (alarm_fn != NULL, NULL); - - ar = g_new (AlarmRecord, 1); - ar->trigger = trigger; - ar->alarm_fn = alarm_fn; - ar->data = data; - ar->destroy_notify_fn = destroy_notify_fn; - - queue_alarm (ar); - - return ar; -} - -/** - * alarm_remove: - * @alarm: A queued alarm identifier. - * - * Removes an alarm from the alarm queue. - **/ -void -alarm_remove (gpointer alarm) -{ - AlarmRecord *notify_id, *ar; - AlarmRecord ar_copy; - AlarmRecord *old_head; - GList *l; - - g_return_if_fail (alarm != NULL); - - ar = alarm; - - l = g_list_find (alarms, ar); - if (!l) { - g_warning (G_STRLOC ": Requested removal of nonexistent alarm!"); - return; - } - - old_head = alarms->data; - - notify_id = ar; - - if (old_head == ar) { - ar_copy = *ar; - ar = &ar_copy; - - /* This will free the original AlarmRecord; - * that's why we copy it. */ - pop_alarm (); - } else { - alarms = g_list_delete_link (alarms, l); - } - - /* Reset the timeout */ - if (!alarms) { - g_source_remove (timeout_id); - timeout_id = 0; - } - - /* Notify about destructiono of the alarm */ - - if (ar->destroy_notify_fn) - (* ar->destroy_notify_fn) (notify_id, ar->data); - -} - -/** - * alarm_done: - * - * Terminates the alarm timer mechanism. This should be called at the end of - * the program. - **/ -void -alarm_done (void) -{ - GList *l; - - if (timeout_id == 0) { - if (alarms) - g_warning ("No timeout, but queue is not NULL\n"); - return; - } - - g_source_remove (timeout_id); - timeout_id = 0; - - if (!alarms) { - g_warning ("timeout present, freed, but no alarms active\n"); - return; - } - - for (l = alarms; l; l = l->next) { - AlarmRecord *ar; - - ar = l->data; - - if (ar->destroy_notify_fn) - (* ar->destroy_notify_fn) (ar, ar->data); - - g_free (ar); - } - - g_list_free (alarms); - alarms = NULL; -} diff --git a/calendar/gui/alarm-notify/alarm.h b/calendar/gui/alarm-notify/alarm.h deleted file mode 100644 index d6e9ff4a4e..0000000000 --- a/calendar/gui/alarm-notify/alarm.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Evolution calendar - Low-level alarm timer mechanism - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Miguel de Icaza - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef ALARM_H -#define ALARM_H - -#include -#include - -typedef void (* AlarmFunction) (gpointer alarm_id, time_t trigger, gpointer data); -typedef void (* AlarmDestroyNotify) (gpointer alarm_id, gpointer data); - -void alarm_done (void); - -gpointer alarm_add (time_t trigger, AlarmFunction alarm_fn, gpointer data, - AlarmDestroyNotify destroy_notify_fn); -void alarm_remove (gpointer alarm); - -#endif diff --git a/calendar/gui/alarm-notify/config-data.c b/calendar/gui/alarm-notify/config-data.c deleted file mode 100644 index c8348f89fc..0000000000 --- a/calendar/gui/alarm-notify/config-data.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Evolution calendar - Configuration values for the alarm notification daemon - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include "config-data.h" - -/* Whether we have initied ourselves by reading - * the data from the configuration engine. */ -static gboolean inited = FALSE; -static GConfClient *conf_client = NULL; -static GSettings *calendar_settings = NULL; -static ESourceList *calendar_source_list = NULL, *tasks_source_list = NULL; - -/* Copied from ../calendar-config.c; returns whether the locale has 'am' and - * 'pm' strings defined. - */ -static gboolean -locale_supports_12_hour_format (void) -{ - gchar s[16]; - time_t t = 0; - - strftime (s, sizeof s, "%p", gmtime (&t)); - return s[0] != '\0'; -} - -static void -do_cleanup (void) -{ - if (calendar_source_list) { - g_object_unref (calendar_source_list); - calendar_source_list = NULL; - } - - if (tasks_source_list) { - g_object_unref (tasks_source_list); - tasks_source_list = NULL; - } - - g_object_unref (conf_client); - conf_client = NULL; - - g_object_unref (calendar_settings); - calendar_settings = FALSE; - - inited = FALSE; -} - -/* Ensures that the configuration values have been read */ -static void -ensure_inited (void) -{ - if (inited) - return; - - inited = TRUE; - - conf_client = gconf_client_get_default (); - if (!GCONF_IS_CLIENT (conf_client)) { - inited = FALSE; - return; - } - - calendar_settings = g_settings_new ("org.gnome.evolution.calendar"); - - g_atexit ((GVoidFunc) do_cleanup); - - /* load the sources for calendars and tasks */ - calendar_source_list = e_source_list_new_for_gconf (conf_client, - "/apps/evolution/calendar/sources"); - tasks_source_list = e_source_list_new_for_gconf (conf_client, - "/apps/evolution/tasks/sources"); - -} - -ESourceList * -config_data_get_calendars (const gchar *key) -{ - ESourceList *cal_sources; - gboolean state; - GSList *gconf_list; - - if (!inited) { - conf_client = gconf_client_get_default (); - calendar_settings = g_settings_new ("org.gnome.evolution.calendar"); - } - - gconf_list = gconf_client_get_list (conf_client, - key, - GCONF_VALUE_STRING, - NULL); - cal_sources = e_source_list_new_for_gconf (conf_client, key); - - if (cal_sources && g_slist_length (gconf_list)) { - g_slist_foreach (gconf_list, (GFunc) g_free, NULL); - g_slist_free (gconf_list); - return cal_sources; - } - - state = g_settings_get_boolean (calendar_settings, "notify-with-tray"); - if (!state) /* Should be old client */ { - GSList *source; - - g_settings_set_boolean (calendar_settings, "notify-with-tray", TRUE); - source = gconf_client_get_list (conf_client, - "/apps/evolution/calendar/sources", - GCONF_VALUE_STRING, - NULL); - gconf_client_set_list (conf_client, - key, - GCONF_VALUE_STRING, - source, - NULL); - cal_sources = e_source_list_new_for_gconf (conf_client, key); - - if (source) { - g_slist_foreach (source, (GFunc) g_free, NULL); - g_slist_free (source); - } - } - - if (gconf_list) { - g_slist_foreach (gconf_list, (GFunc) g_free, NULL); - g_slist_free (gconf_list); - } - - return cal_sources; - -} - -void -config_data_replace_string_list (const gchar *key, - const gchar *old, - const gchar *new) -{ - GSList *source, *tmp; - - if (!inited) - conf_client = gconf_client_get_default (); - - source = gconf_client_get_list (conf_client, - key, - GCONF_VALUE_STRING, - NULL); - - for (tmp = source; tmp; tmp = tmp->next) { - - if (strcmp (tmp->data, old) == 0) { - g_free (tmp->data); - tmp->data = g_strdup ((gchar *) new); - gconf_client_set_list (conf_client, - key, - GCONF_VALUE_STRING, - source, - NULL); - break; - } - } - - if (source) { - g_slist_foreach (source, (GFunc) g_free, NULL); - g_slist_free (source); - } -} - -icaltimezone * -config_data_get_timezone (void) -{ - gchar *location; - icaltimezone *local_timezone; - - ensure_inited (); - - if (g_settings_get_boolean (calendar_settings, "use-system-timezone")) - location = e_cal_util_get_system_timezone_location (); - else { - location = g_settings_get_string (calendar_settings, "timezone"); - } - - if (location && location[0]) - local_timezone = icaltimezone_get_builtin_timezone (location); - else - local_timezone = icaltimezone_get_utc_timezone (); - - g_free (location); - - return local_timezone; -} - -gboolean -config_data_get_24_hour_format (void) -{ - ensure_inited (); - - if (locale_supports_12_hour_format ()) { - return g_settings_get_boolean (calendar_settings, "use-24hour-format"); - } - - return TRUE; -} - -gboolean -config_data_get_notify_with_tray (void) -{ - ensure_inited (); - - return g_settings_get_boolean (calendar_settings, "notify-with-tray"); -} - -/** - * config_data_set_last_notification_time: - * @t: A time value. - * - * Saves the last notification time so that it can be fetched the next time the - * alarm daemon is run. This way the daemon can show alarms that should have - * triggered while it was not running. - **/ -void -config_data_set_last_notification_time (ECalClient *cal, - time_t t) -{ - time_t current_t, now = time (NULL); - - g_return_if_fail (t != -1); - - if (cal) { - ESource *source = e_client_get_source (E_CLIENT (cal)); - if (source) { - const gchar *prop_str; - GTimeVal curr_tv = {0}; - - prop_str = e_source_get_property (source, "last-notified"); - if (!prop_str || !g_time_val_from_iso8601 (prop_str, &curr_tv)) - curr_tv.tv_sec = 0; - - if (t > (time_t) curr_tv.tv_sec || (time_t) curr_tv.tv_sec > now) { - GTimeVal tmval = {0}; - gchar *as_text; - - tmval.tv_sec = (glong) t; - as_text = g_time_val_to_iso8601 (&tmval); - - if (as_text) { - e_source_set_property (source, "last-notified", as_text); - g_free (as_text); - /* pass through, thus the global last notification time is also changed */ - } - } - } - } - - /* we only store the new notification time if it is bigger - * than the already stored one */ - current_t = g_settings_get_int (calendar_settings, "last-notification-time"); - if (t > current_t || current_t > now) - g_settings_set_int (calendar_settings, "last-notification-time", t); -} - -/** - * config_data_get_last_notification_time: - * - * Queries the last saved value for alarm notification times. - * - * Return value: The last saved value, or -1 if no value had been saved before. - **/ -time_t -config_data_get_last_notification_time (ECalClient *cal) -{ - time_t value, now; - - if (cal) { - ESource *source = e_client_get_source (E_CLIENT (cal)); - if (source) { - const gchar *last_notified; - - GTimeVal tmval = {0}; - - last_notified = e_source_get_property ( - source, "last-notified"); - - if (last_notified && *last_notified && - g_time_val_from_iso8601 (last_notified, &tmval)) { - time_t now = time (NULL), value = (time_t) tmval.tv_sec; - - if (value > now) - value = now; - return value; - } - } - } - - value = g_settings_get_int (calendar_settings, "last-notification-time"); - now = time (NULL); - if (value > now) - value = now; - - return value; -} - -/** - * config_data_save_blessed_program: - * @program: a program name - * - * Saves a program name as "blessed" - **/ -void -config_data_save_blessed_program (const gchar *program) -{ - gchar **list; - gint i; - GPtrArray *array = g_ptr_array_new (); - - list = g_settings_get_strv (calendar_settings, "notify-programs"); - for (i = 0; i < g_strv_length (list); i++) - g_ptr_array_add (array, list[i]); - - g_ptr_array_add (array, (gpointer) program); - g_ptr_array_add (array, NULL); - g_settings_set_strv (calendar_settings, "notify-programs", (const gchar *const *) array->pdata); - - g_strfreev (list); - g_ptr_array_free (array, TRUE); -} - -/** - * config_data_is_blessed_program: - * @program: a program name - * - * Checks to see if a program is blessed - * - * Return value: TRUE if program is blessed, FALSE otherwise - **/ -gboolean -config_data_is_blessed_program (const gchar *program) -{ - gchar **list; - gint i = 0; - gboolean found = FALSE; - - list = g_settings_get_strv (calendar_settings, "notify-programs"); - if (!list) - return FALSE; - - while (list[i] != NULL) { - if (!found) - found = strcmp ((gchar *) list[i], program) == 0; - i++; - } - - g_strfreev (list); - - return found; -} - -static gboolean can_debug = FALSE; -static GStaticRecMutex rec_mutex = G_STATIC_REC_MUTEX_INIT; - -void -config_data_init_debugging (void) -{ - can_debug = g_getenv ("ALARMS_DEBUG") != NULL; -} - -/* returns whether started debugging; - * call config_data_stop_debugging() when started and you are done with it - */ -gboolean -config_data_start_debugging (void) -{ - g_static_rec_mutex_lock (&rec_mutex); - - if (can_debug) - return TRUE; - - g_static_rec_mutex_unlock (&rec_mutex); - - return FALSE; -} - -void -config_data_stop_debugging (void) -{ - g_static_rec_mutex_unlock (&rec_mutex); -} diff --git a/calendar/gui/alarm-notify/config-data.h b/calendar/gui/alarm-notify/config-data.h deleted file mode 100644 index a7a0ca58e0..0000000000 --- a/calendar/gui/alarm-notify/config-data.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Evolution calendar - Configuration values for the alarm notification daemon - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef CONFIG_DATA_H -#define CONFIG_DATA_H - -#include -#include -#include - -icaltimezone * config_data_get_timezone (void); -gboolean config_data_get_24_hour_format (void); -gboolean config_data_get_notify_with_tray - (void); -void config_data_set_last_notification_time - (ECalClient *cal, - time_t t); -time_t config_data_get_last_notification_time - (ECalClient *cal); -void config_data_save_blessed_program - (const gchar *program); -gboolean config_data_is_blessed_program (const gchar *program); -ESourceList * config_data_get_calendars (const gchar *); -void config_data_replace_string_list (const gchar *, - const gchar *, - const gchar *); - -void config_data_init_debugging (void); -gboolean config_data_start_debugging (void); -void config_data_stop_debugging (void); - -#define debug(x) G_STMT_START { \ - if (config_data_start_debugging ()) { \ - g_print ("%s (%s): ", G_STRFUNC, G_STRLOC); \ - g_print x; \ - g_print ("\n"); \ - \ - config_data_stop_debugging (); \ - } \ - } G_STMT_END - -#endif diff --git a/calendar/gui/alarm-notify/evolution-alarm-notify-icon.rc b/calendar/gui/alarm-notify/evolution-alarm-notify-icon.rc deleted file mode 100644 index 1f9ef65874..0000000000 --- a/calendar/gui/alarm-notify/evolution-alarm-notify-icon.rc +++ /dev/null @@ -1 +0,0 @@ -1 ICON "evolution-alarm-notify.ico" diff --git a/calendar/gui/alarm-notify/evolution-alarm-notify.ico b/calendar/gui/alarm-notify/evolution-alarm-notify.ico deleted file mode 100644 index 6585452256..0000000000 Binary files a/calendar/gui/alarm-notify/evolution-alarm-notify.ico and /dev/null differ diff --git a/calendar/gui/alarm-notify/notify-main.c b/calendar/gui/alarm-notify/notify-main.c deleted file mode 100644 index a6d9d53fc5..0000000000 --- a/calendar/gui/alarm-notify/notify-main.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Evolution calendar - Alarm notification service main file - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * Rodrigo Moya - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include - -#include - -#include "alarm-notify.h" - -#ifdef G_OS_WIN32 -#include -#include -#ifndef PROCESS_DEP_ENABLE -#define PROCESS_DEP_ENABLE 0x00000001 -#endif -#ifndef PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION -#define PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION 0x00000002 -#endif -#endif - -#include "e-util/e-util-private.h" - -gint -main (gint argc, - gchar **argv) -{ - AlarmNotify *alarm_notify_service; - gint exit_status; - GError *error = NULL; -#ifdef G_OS_WIN32 - gchar *path; - - /* Reduce risks */ - { - typedef BOOL (WINAPI *t_SetDllDirectoryA) (LPCSTR lpPathName); - t_SetDllDirectoryA p_SetDllDirectoryA; - - p_SetDllDirectoryA = GetProcAddress (GetModuleHandle ("kernel32.dll"), "SetDllDirectoryA"); - if (p_SetDllDirectoryA) - (*p_SetDllDirectoryA) (""); - } -#ifndef _WIN64 - { - typedef BOOL (WINAPI *t_SetProcessDEPPolicy) (DWORD dwFlags); - t_SetProcessDEPPolicy p_SetProcessDEPPolicy; - - p_SetProcessDEPPolicy = GetProcAddress (GetModuleHandle ("kernel32.dll"), "SetProcessDEPPolicy"); - if (p_SetProcessDEPPolicy) - (*p_SetProcessDEPPolicy) (PROCESS_DEP_ENABLE | PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION); - } -#endif -#endif - - bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - gtk_init (&argc, &argv); - - e_gdbus_templates_init_main_thread (); - -#ifdef G_OS_WIN32 - path = g_build_path (";", _e_get_bindir (), g_getenv ("PATH"), NULL); - - if (!g_setenv ("PATH", path, TRUE)) - g_warning ("Could not set PATH for Evolution Alarm Notifier"); -#endif - - alarm_notify_service = alarm_notify_new (NULL, &error); - - if (error != NULL) { - g_printerr ("%s\n", error->message); - g_error_free (error); - exit (EXIT_FAILURE); - } - - exit_status = g_application_run ( - G_APPLICATION (alarm_notify_service), argc, argv); - - g_object_unref (alarm_notify_service); - - return exit_status; -} diff --git a/calendar/gui/alarm-notify/util.c b/calendar/gui/alarm-notify/util.c deleted file mode 100644 index 891ea131bc..0000000000 --- a/calendar/gui/alarm-notify/util.c +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Evolution calendar - utility functions - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include "config-data.h" -#include "util.h" - -/* Converts a time_t to a string, relative to the specified timezone */ -gchar * -timet_to_str_with_zone (time_t t, - icaltimezone *zone) -{ - struct icaltimetype itt; - struct tm tm; - gchar buf[256]; - - if (t == -1) - return g_strdup (_("invalid time")); - - itt = icaltime_from_timet_with_zone (t, FALSE, zone); - tm = icaltimetype_to_tm (&itt); - - e_time_format_date_and_time (&tm, config_data_get_24_hour_format (), - FALSE, FALSE, buf, sizeof (buf)); - return g_strdup (buf); -} - -gchar * -calculate_time (time_t start, - time_t end) -{ - time_t difference = end - start; - gchar *str; - gint hours, minutes; - gchar *times[4]; - gchar *joined; - gint i; - - i = 0; - if (difference >= 3600) { - hours = difference / 3600; - difference %= 3600; - - times[i++] = g_strdup_printf (ngettext("%d hour", "%d hours", hours), hours); - } - if (difference >= 60) { - minutes = difference / 60; - difference %= 60; - - times[i++] = g_strdup_printf (ngettext("%d minute", "%d minutes", minutes), minutes); - } - if (i == 0 || difference != 0) { - /* TRANSLATORS: here, "second" is the time division (like "minute"), not the ordinal number (like "third") */ - times[i++] = g_strdup_printf (ngettext("%d second", "%d seconds", difference), (gint)difference); - } - - times[i] = NULL; - joined = g_strjoinv (" ", times); - str = g_strconcat ("(", joined, ")", NULL); - while (i > 0) - g_free (times[--i]); - g_free (joined); - - return str; -} diff --git a/calendar/gui/alarm-notify/util.h b/calendar/gui/alarm-notify/util.h deleted file mode 100644 index bb6935729a..0000000000 --- a/calendar/gui/alarm-notify/util.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * - * Evolution calendar - utility functions - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with the program; if not, see - * - * - * Authors: - * Federico Mena-Quintero - * - * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) - * - */ - -#ifndef UTIL_H -#define UTIL_H - -#include - -gchar *timet_to_str_with_zone (time_t t, icaltimezone *zone); -gchar *calculate_time (time_t start, time_t end); -#endif diff --git a/configure.ac b/configure.ac index a0101c6410..ce88950d6d 100644 --- a/configure.ac +++ b/configure.ac @@ -1636,9 +1636,9 @@ widgets/misc/Makefile widgets/text/Makefile widgets/table/Makefile calendar/Makefile +calendar/alarm-notify/Makefile calendar/importers/Makefile calendar/gui/Makefile -calendar/gui/alarm-notify/Makefile calendar/gui/dialogs/Makefile composer/Makefile m4/Makefile diff --git a/po/POTFILES.in b/po/POTFILES.in index 36bf948e96..4ffe4684c9 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -36,11 +36,11 @@ addressbook/printing/test-print.c addressbook/tools/evolution-addressbook-export.c addressbook/tools/evolution-addressbook-export-list-cards.c addressbook/tools/evolution-addressbook-export-list-folders.c +calendar/alarm-notify/alarm-notify-dialog.c +[type: gettext/glade]calendar/alarm-notify/alarm-notify.ui +calendar/alarm-notify/alarm-queue.c +calendar/alarm-notify/util.c calendar/calendar.error.xml -calendar/gui/alarm-notify/alarm-notify-dialog.c -[type: gettext/glade]calendar/gui/alarm-notify/alarm-notify.ui -calendar/gui/alarm-notify/alarm-queue.c -calendar/gui/alarm-notify/util.c calendar/gui/calendar-view-factory.c calendar/gui/caltypes.xml calendar/gui/dialogs/alarm-dialog.c -- cgit v1.2.3