/* Evolution calendar - Alarm page of the calendar component dialogs
*
* Copyright (C) 2001 Ximian, Inc.
*
* Authors: Federico Mena-Quintero <federico@ximian.com>
* Miguel de Icaza <miguel@ximian.com>
* Seth Alves <alves@hungry.com>
* JP Rosevear <jpr@ximian.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <gtk/gtksignal.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
#include <glade/glade.h>
#include <gal/widgets/e-unicode.h>
#include "e-util/e-dialog-widgets.h"
#include "cal-util/cal-util.h"
#include "comp-editor-util.h"
#include "alarm-options.h"
#include "alarm-page.h"
/* Private part of the AlarmPage structure */
struct _AlarmPagePrivate {
/* Glade XML data */
GladeXML *xml;
/* Widgets from the Glade file */
GtkWidget *main;
GtkWidget *summary;
GtkWidget *date_time;
GtkWidget *list;
GtkWidget *add;
GtkWidget *delete;
GtkWidget *action;
GtkWidget *interval_value;
GtkWidget *value_units;
GtkWidget *relative;
GtkWidget *time;
GtkWidget *button_options;
/* Alarm options dialog and the alarm we maintain */
CalComponentAlarm *alarm;
gboolean updating;
};
/* "relative" types */
enum {
BEFORE,
AFTER
};
/* Time units */
enum {
MINUTES,
HOURS,
DAYS
};
/* Option menu maps */
static const int action_map[] = {
CAL_ALARM_DISPLAY,
CAL_ALARM_AUDIO,
CAL_ALARM_PROCEDURE,
-1
};
static const int value_map[] = {
MINUTES,
HOURS,
DAYS,
-1
};
static const int relative_map[] = {
BEFORE,
AFTER,
-1
};
static const int time_map[] = {
CAL_ALARM_TRIGGER_RELATIVE_START,
CAL_ALARM_TRIGGER_RELATIVE_END,
-1
};
static void alarm_page_class_init (AlarmPageClass *class);
static void alarm_page_init (AlarmPage *apage);
static void alarm_page_destroy (GtkObject *object);
static GtkWidget *alarm_page_get_widget (CompEditorPage *page);
static void alarm_page_focus_main_widget (CompEditorPage *page);
static void alarm_page_fill_widgets (CompEditorPage *page, CalComponent *comp);
static void alarm_page_fill_component (CompEditorPage *page, CalComponent *comp);
static void alarm_page_set_summary (CompEditorPage *page, const char *summary);
static void alarm_page_set_dates (CompEditorPage *page, CompEditorPageDates *dates);
static CompEditorPageClass *parent_class = NULL;
/**
* alarm_page_get_type:
*
* Registers the #AlarmPage class if necessary, and returns the type ID
* associated to it.
*
* Return value: The type ID of the #AlarmPage class.
**/
GtkType
alarm_page_get_type (void)
{
static GtkType alarm_page_type;
if (!alarm_page_type) {
static const GtkTypeInfo alarm_page_info = {
"AlarmPage",
sizeof (AlarmPage),
sizeof (AlarmPageClass),
(GtkClassInitFunc) alarm_page_class_init,
(GtkObjectInitFunc) alarm_page_init,
NULL, /* reserved_1 */
NULL, /* reserved_2 */
(GtkClassInitFunc) NULL
};
alarm_page_type = gtk_type_unique (TYPE_COMP_EDITOR_PAGE,
&alarm_page_info);
}
return alarm_page_type;
}
/* Class initialization function for the alarm page */
static void
alarm_page_class_init (AlarmPageClass *class)
{
CompEditorPageClass *editor_page_class;
GtkObjectClass *object_class;
editor_page_class = (CompEditorPageClass *) class;
object_class = (GtkObjectClass *) class;
parent_class = gtk_type_class (TYPE_COMP_EDITOR_PAGE);
editor_page_class->get_widget = alarm_page_get_widget;
editor_page_class->focus_main_widget = alarm_page_focus_main_widget;
editor_page_class->fill_widgets = alarm_page_fill_widgets;
editor_page_class->fill_component = alarm_page_fill_component;
editor_page_class->set_summary = alarm_page_set_summary;
editor_page_class->set_dates = alarm_page_set_dates;
object_class->destroy = alarm_page_destroy;
}
/* Object initialization function for the alarm page */
static void
alarm_page_init (AlarmPage *apage)
{
AlarmPagePrivate *priv;
priv = g_new0 (AlarmPagePrivate, 1);
apage->priv = priv;
priv->xml = NULL;
priv->main = NULL;
priv->summary = NULL;
priv->date_time = NULL;
priv->list = NULL;
priv->add = NULL;
priv->delete = NULL;
priv->action = NULL;
priv->interval_value = NULL;
priv->value_units = NULL;
priv->relative = NULL;
priv->time = NULL;
priv->button_options = NULL;
priv->alarm = cal_component_alarm_new ();
priv->updating = FALSE;
}
/* Destroy handler for the alarm page */
static void
alarm_page_destroy (GtkObject *object)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (IS_ALARM_PAGE (object));
apage = ALARM_PAGE (object);
priv = apage->priv;
if (priv->xml) {
gtk_object_unref (GTK_OBJECT (priv->xml));
priv->xml = NULL;
}
if (priv->alarm) {
cal_component_alarm_free (priv->alarm);
priv->alarm = NULL;
}
g_free (priv);
apage->priv = NULL;
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
/* get_widget handler for the alarm page */
static GtkWidget *
alarm_page_get_widget (CompEditorPage *page)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
apage = ALARM_PAGE (page);
priv = apage->priv;
return priv->main;
}
/* focus_main_widget handler for the alarm page */
static void
alarm_page_focus_main_widget (CompEditorPage *page)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
apage = ALARM_PAGE (page);
priv = apage->priv;
gtk_widget_grab_focus (priv->action);
}
/* Fills the widgets with default values */
static void
clear_widgets (AlarmPage *apage)
{
AlarmPagePrivate *priv;
priv = apage->priv;
/* Summary */
gtk_label_set_text (GTK_LABEL (priv->summary), "");
/* Start date */
gtk_label_set_text (GTK_LABEL (priv->date_time), "");
/* Sane defaults */
e_dialog_option_menu_set (priv->action, CAL_ALARM_DISPLAY, action_map);
e_dialog_spin_set (priv->interval_value, 15);
e_dialog_option_menu_set (priv->value_units, MINUTES, value_map);
e_dialog_option_menu_set (priv->relative, BEFORE, relative_map);
e_dialog_option_menu_set (priv->time, CAL_ALARM_TRIGGER_RELATIVE_START, time_map);
/* List data */
gtk_clist_clear (GTK_CLIST (priv->list));
}
/* Builds a string for the duration of the alarm. If the duration is zero, returns NULL. */
static char *
get_alarm_duration_string (struct icaldurationtype *duration)
{
GString *string = g_string_new (NULL);
char *ret;
gboolean have_something;
have_something = FALSE;
if (duration->days > 1) {
g_string_sprintf (string, _("%d days"), duration->days);
have_something = TRUE;
} else if (duration->days == 1) {
g_string_append (string, _("1 day"));
have_something = TRUE;
}
if (duration->weeks > 1) {
g_string_sprintf (string, _("%d weeks"), duration->weeks);
have_something = TRUE;
} else if (duration->weeks == 1) {
g_string_append (string, _("1 week"));
have_something = TRUE;
}
if (duration->hours > 1) {
g_string_sprintf (string, _("%d hours"), duration->hours);
have_something = TRUE;
} else if (duration->hours == 1) {
g_string_append (string, _("1 hour"));
have_something = TRUE;
}
if (duration->minutes > 1) {
g_string_sprintf (string, _("%d minutes"), duration->minutes);
have_something = TRUE;
} else if (duration->minutes == 1) {
g_string_append (string, _("1 minute"));
have_something = TRUE;
}
if (duration->seconds > 1) {
g_string_sprintf (string, _("%d seconds"), duration->seconds);
have_something = TRUE;
} else if (duration->seconds == 1) {
g_string_append (string, _("1 second"));
have_something = TRUE;
}
if (have_something) {
ret = string->str;
g_string_free (string, FALSE);
return ret;
} else {
g_string_free (string, TRUE);
return NULL;
}
}
static char *
get_alarm_string (CalComponentAlarm *alarm)
{
CalAlarmAction action;
CalAlarmTrigger trigger;
char string[256];
char *base;
char *str;
char *dur;
string [0] = '\0';
cal_component_alarm_get_action (alarm, &action);
cal_component_alarm_get_trigger (alarm, &trigger);
switch (action) {
case CAL_ALARM_AUDIO:
base = _("Play a sound");
break;
case CAL_ALARM_DISPLAY:
base = _("Display a message");
break;
case CAL_ALARM_EMAIL:
base = _("Send an email");
break;
case CAL_ALARM_PROCEDURE:
base = _("Run a program");
break;
case CAL_ALARM_NONE:
case CAL_ALARM_UNKNOWN:
default:
base = _("Unknown action to be performed");
break;
}
/* FIXME: This does not look like it will localize correctly. */
switch (trigger.type) {
case CAL_ALARM_TRIGGER_RELATIVE_START:
dur = get_alarm_duration_string (&trigger.u.rel_duration);
if (dur) {
if (trigger.u.rel_duration.is_neg)
str = g_strdup_printf (_("%s %s before the start of the appointment"),
base, dur);
else
str = g_strdup_printf (_("%s %s after the start of the appointment"),
base, dur);
g_free (dur);
} else
str = g_strdup_printf (_("%s at the start of the appointment"), base);
break;
case CAL_ALARM_TRIGGER_RELATIVE_END:
dur = get_alarm_duration_string (&trigger.u.rel_duration);
if (dur) {
if (trigger.u.rel_duration.is_neg)
str = g_strdup_printf (_("%s %s before the end of the appointment"),
base, dur);
else
str = g_strdup_printf (_("%s %s after the end of the appointment"),
base, dur);
g_free (dur);
} else
str = g_strdup_printf (_("%s at the end of the appointment"), base);
break;
case CAL_ALARM_TRIGGER_ABSOLUTE: {
time_t t;
struct tm tm;
char *date;
t = icaltime_as_timet (trigger.u.abs_time);
if (t == -1)
date = g_strdup_printf (_("%s at an unknown time"), base);
else {
char buf[256];
tm = *localtime (&t);
strftime (buf, sizeof (buf), "%A %b %d %Y %H:%M", &tm);
date = g_strdup_printf (_("%s at %s"), base, buf);
}
break; }
case CAL_ALARM_TRIGGER_NONE:
default:
str = g_strdup_printf (_("%s for an unknown trigger type"), base);
break;
}
return str;
}
/* Appends an alarm to the list */
static void
append_reminder (AlarmPage *apage, CalComponentAlarm *alarm)
{
AlarmPagePrivate *priv;
GtkCList *clist;
char *c[1];
int i;
priv = apage->priv;
clist = GTK_CLIST (priv->list);
c[0] = get_alarm_string (alarm);
i = gtk_clist_append (clist, c);
gtk_clist_set_row_data_full (clist, i, alarm, (GtkDestroyNotify) cal_component_alarm_free);
gtk_clist_select_row (clist, i, 0);
g_free (c[0]);
gtk_widget_set_sensitive (priv->delete, TRUE);
}
/* fill_widgets handler for the alarm page */
static void
alarm_page_fill_widgets (CompEditorPage *page, CalComponent *comp)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
CalComponentText text;
GList *alarms, *l;
GtkCList *clist;
CompEditorPageDates dates;
apage = ALARM_PAGE (page);
priv = apage->priv;
/* Don't send off changes during this time */
priv->updating = TRUE;
/* Clean the page */
clear_widgets (apage);
/* Summary */
cal_component_get_summary (comp, &text);
alarm_page_set_summary (page, text.value);
/* Dates */
comp_editor_dates (&dates, comp);
alarm_page_set_dates (page, &dates);
comp_editor_free_dates (&dates);
/* List */
if (!cal_component_has_alarms (comp))
goto out;
alarms = cal_component_get_alarm_uids (comp);
clist = GTK_CLIST (priv->list);
for (l = alarms; l != NULL; l = l->next) {
CalComponentAlarm *ca, *ca_copy;
const char *auid;
auid = l->data;
ca = cal_component_get_alarm (comp, auid);
g_assert (ca != NULL);
ca_copy = cal_component_alarm_clone (ca);
cal_component_alarm_free (ca);
append_reminder (apage, ca_copy);
}
cal_obj_uid_list_free (alarms);
out:
priv->updating = FALSE;
}
/* fill_component handler for the alarm page */
static void
alarm_page_fill_component (CompEditorPage *page, CalComponent *comp)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
GList *list, *l;
GtkCList *clist;
int i;
apage = ALARM_PAGE (page);
priv = apage->priv;
/* Remove all the alarms from the component */
list = cal_component_get_alarm_uids (comp);
for (l = list; l; l = l->next) {
const char *auid;
auid = l->data;
cal_component_remove_alarm (comp, auid);
}
cal_obj_uid_list_free (list);
/* Add the new alarms */
clist = GTK_CLIST (priv->list);
for (i = 0; i < clist->rows; i++) {
CalComponentAlarm *alarm, *alarm_copy;
alarm = gtk_clist_get_row_data (clist, i);
g_assert (alarm != NULL);
/* We clone the alarm to maintain the invariant that the alarm
* structures in the list did *not* come from the component.
*/
alarm_copy = cal_component_alarm_clone (alarm);
cal_component_add_alarm (comp, alarm_copy);
cal_component_alarm_free (alarm_copy);
}
}
/* set_summary handler for the alarm page */
static void
alarm_page_set_summary (CompEditorPage *page, const char *summary)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
gchar *s;
apage = ALARM_PAGE (page);
priv = apage->priv;
s = e_utf8_to_gtk_string (priv->summary, summary);
gtk_label_set_text (GTK_LABEL (priv->summary), s);
g_free (s);
}
/* set_dates handler for the alarm page */
static void
alarm_page_set_dates (CompEditorPage *page, CompEditorPageDates *dates)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
apage = ALARM_PAGE (page);
priv = apage->priv;
comp_editor_date_label (dates, priv->date_time);
}
/* Gets the widgets from the XML file and returns if they are all available. */
static gboolean
get_widgets (AlarmPage *apage)
{
AlarmPagePrivate *priv;
priv = apage->priv;
#define GW(name) glade_xml_get_widget (priv->xml, name)
priv->main = GW ("alarm-page");
if (!priv->main)
return FALSE;
gtk_widget_ref (priv->main);
gtk_widget_unparent (priv->main);
priv->summary = GW ("summary");
priv->date_time = GW ("date-time");
priv->list = GW ("list");
priv->add = GW ("add");
priv->delete = GW ("delete");
priv->action = GW ("action");
priv->interval_value = GW ("interval-value");
priv->value_units = GW ("value-units");
priv->relative = GW ("relative");
priv->time = GW ("time");
priv->button_options = GW ("button-options");
#undef GW
return (priv->summary
&& priv->date_time
&& priv->list
&& priv->add
&& priv->delete
&& priv->action
&& priv->interval_value
&& priv->value_units
&& priv->relative
&& priv->time
&& priv->button_options);
}
/* This is called when any field is changed; it notifies upstream. */
static void
field_changed_cb (GtkWidget *widget, gpointer data)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
apage = ALARM_PAGE (data);
priv = apage->priv;
if (!priv->updating)
comp_editor_page_notify_changed (COMP_EDITOR_PAGE (apage));
}
/* Callback used for the "add reminder" button */
static void
add_clicked_cb (GtkButton *button, gpointer data)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
CalComponentAlarm *alarm;
CalAlarmTrigger trigger;
apage = ALARM_PAGE (data);
priv = apage->priv;
alarm = cal_component_alarm_clone (priv->alarm);
memset (&trigger, 0, sizeof (CalAlarmTrigger));
trigger.type = e_dialog_option_menu_get (priv->time, time_map);
if (e_dialog_option_menu_get (priv->relative, relative_map) == BEFORE)
trigger.u.rel_duration.is_neg = 1;
else
trigger.u.rel_duration.is_neg = 0;
switch (e_dialog_option_menu_get (priv->value_units, value_map)) {
case MINUTES:
trigger.u.rel_duration.minutes =
e_dialog_spin_get_int (priv->interval_value);
break;
case HOURS:
trigger.u.rel_duration.hours =
e_dialog_spin_get_int (priv->interval_value);
break;
case DAYS:
trigger.u.rel_duration.days =
e_dialog_spin_get_int (priv->interval_value);
break;
default:
g_assert_not_reached ();
}
cal_component_alarm_set_trigger (alarm, trigger);
cal_component_alarm_set_action (alarm, e_dialog_option_menu_get (priv->action, action_map));
append_reminder (apage, alarm);
}
/* Callback used for the "delete reminder" button */
static void
delete_clicked_cb (GtkButton *button, gpointer data)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
GtkCList *clist;
int sel;
apage = ALARM_PAGE (data);
priv = apage->priv;
clist = GTK_CLIST (priv->list);
if (!clist->selection)
return;
sel = GPOINTER_TO_INT (clist->selection->data);
gtk_clist_remove (clist, sel);
if (sel >= clist->rows)
sel--;
if (clist->rows > 0)
gtk_clist_select_row (clist, sel, 0);
else
gtk_widget_set_sensitive (priv->delete, FALSE);
}
/* Callback used when the alarm options button is clicked */
static void
button_options_clicked_cb (GtkWidget *widget, gpointer data)
{
AlarmPage *apage;
AlarmPagePrivate *priv;
apage = ALARM_PAGE (data);
priv = apage->priv;
cal_component_alarm_set_action (priv->alarm,
e_dialog_option_menu_get (priv->action, action_map));
if (!alarm_options_dialog_run (priv->alarm))
g_message ("button_options_clicked_cb(): Could not create the alarm options dialog");
}
/* Hooks the widget signals */
static void
init_widgets (AlarmPage *apage)
{
AlarmPagePrivate *priv;
priv = apage->priv;
/* Reminder buttons */
gtk_signal_connect (GTK_OBJECT (priv->add), "clicked",
GTK_SIGNAL_FUNC (add_clicked_cb), apage);
gtk_signal_connect (GTK_OBJECT (priv->delete), "clicked",
GTK_SIGNAL_FUNC (delete_clicked_cb), apage);
/* Connect the default signal handler to use to make sure we notify
* upstream of changes to the widget values.
*/
gtk_signal_connect (GTK_OBJECT (priv->add), "clicked",
GTK_SIGNAL_FUNC (field_changed_cb), apage);
gtk_signal_connect (GTK_OBJECT (priv->delete), "clicked",
GTK_SIGNAL_FUNC (field_changed_cb), apage);
/* Options button */
gtk_signal_connect (GTK_OBJECT (priv->button_options), "clicked",
GTK_SIGNAL_FUNC (button_options_clicked_cb), apage);
}
/**
* alarm_page_construct:
* @apage: An alarm page.
*
* Constructs an alarm page by loading its Glade data.
*
* Return value: The same object as @apage, or NULL if the widgets could not be
* created.
**/
AlarmPage *
alarm_page_construct (AlarmPage *apage)
{
AlarmPagePrivate *priv;
priv = apage->priv;
priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/alarm-page.glade",
NULL);
if (!priv->xml) {
g_message ("alarm_page_construct(): "
"Could not load the Glade XML file!");
return NULL;
}
if (!get_widgets (apage)) {
g_message ("alarm_page_construct(): "
"Could not find all widgets in the XML file!");
return NULL;
}
init_widgets (apage);
return apage;
}
/**
* alarm_page_new:
*
* Creates a new alarm page.
*
* Return value: A newly-created alarm page, or NULL if the page could not be
* created.
**/
AlarmPage *
alarm_page_new (void)
{
AlarmPage *apage;
apage = gtk_type_new (TYPE_ALARM_PAGE);
if (!alarm_page_construct (apage)) {
gtk_object_unref (GTK_OBJECT (apage));
return NULL;
}
return apage;
}