diff --git a/src/calendar.gresource.xml b/src/calendar.gresource.xml index e2a6502b..df7cc7e5 100644 --- a/src/calendar.gresource.xml +++ b/src/calendar.gresource.xml @@ -1,7 +1,7 @@ - gui/alarm-row.ui + gui/gcal-alarm-row.ui gui/gcal-calendar-popover.ui gui/gcal-date-chooser.ui gui/gcal-date-selector.ui diff --git a/src/core/gcal-event.c b/src/core/gcal-event.c index 3ae3fcad..0ff8fe5e 100644 --- a/src/core/gcal-event.c +++ b/src/core/gcal-event.c @@ -1183,68 +1183,76 @@ gcal_event_get_alarms (GcalEvent *self) } /** - * gcal_event_add_alarm: + * gcal_event_remove_all_alarms: * @self: a #GcalEvent - * @type: the minutes before the start date to trigger the alarm - * @has_sound: whether the alarm plays a sound or not * - * Adds an alarm to @self that triggers @type minutes before the event's - * start date. - * - * If there's already an alarm for @type, it'll be replaced. + * Removes all alarms from @self. */ void -gcal_event_add_alarm (GcalEvent *self, - guint type, - gboolean has_sound) +gcal_event_remove_all_alarms (GcalEvent *self) { - ECalComponentAlarmTrigger *trigger; - ECalComponentAlarmAction action; - ECalComponentAlarm *alarm; - ICalDuration *duration; - gchar *alarm_uid; + GHashTableIter iter; + const gchar *alarm_uid; + gint minutes; + + GCAL_ENTRY; g_return_if_fail (GCAL_IS_EVENT (self)); - /* Only 1 alarm per relative time */ - if (g_hash_table_contains (self->alarms, GINT_TO_POINTER (type))) + g_hash_table_iter_init (&iter, self->alarms); + while (g_hash_table_iter_next (&iter, (gpointer*) &minutes, (gpointer*) &alarm_uid)) { - alarm_uid = g_hash_table_lookup (self->alarms, GINT_TO_POINTER (type)); + GCAL_TRACE_MSG ("Removing alarm %s from event %s", alarm_uid, gcal_event_get_uid (self)); e_cal_component_remove_alarm (self->component, alarm_uid); + g_hash_table_iter_remove (&iter); + } + + GCAL_EXIT; +} + +/** + * gcal_event_add_alarm: + * @self: a #GcalEvent + * @alarm: a #ECalComponentAlarm + * + * Adds @alarm to @self. If there's already an alarm scheduled for the + * same time, it'll be replaced. + */ +void +gcal_event_add_alarm (GcalEvent *self, + ECalComponentAlarm *alarm) +{ + ECalComponentAlarm *new_alarm; + gchar *alarm_uid; + guint minutes; + + GCAL_ENTRY; + + g_return_if_fail (GCAL_IS_EVENT (self)); + + new_alarm = e_cal_component_alarm_copy (alarm); + minutes = get_alarm_trigger_minutes (self, alarm); + + /* Only 1 alarm per relative time */ + if (g_hash_table_contains (self->alarms, GINT_TO_POINTER (minutes))) + { + alarm_uid = g_hash_table_lookup (self->alarms, GINT_TO_POINTER (minutes)); + e_cal_component_remove_alarm (self->component, alarm_uid); } - /* Alarm */ - alarm = e_cal_component_alarm_new (); - - /* Setup the alarm trigger */ - duration = i_cal_duration_new_null_duration (); - i_cal_duration_set_is_neg (duration, TRUE); - i_cal_duration_set_minutes (duration, type); - - trigger = e_cal_component_alarm_trigger_new_relative (E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration); - - g_clear_object (&duration); - - e_cal_component_alarm_take_trigger (alarm, trigger); - - /* Action */ - if (has_sound) - action = E_CAL_COMPONENT_ALARM_AUDIO; - else - action = E_CAL_COMPONENT_ALARM_DISPLAY; - - e_cal_component_alarm_set_action (alarm, action); - /* Add the alarm to the component */ - e_cal_component_add_alarm (self->component, alarm); + e_cal_component_add_alarm (self->component, new_alarm); /* Add to the hash table */ - alarm_uid = g_strdup (e_cal_component_alarm_get_uid (alarm)); + alarm_uid = g_strdup (e_cal_component_alarm_get_uid (new_alarm)); + g_hash_table_insert (self->alarms, GINT_TO_POINTER (minutes), alarm_uid); - g_hash_table_insert (self->alarms, GINT_TO_POINTER (type), alarm_uid); + GCAL_TRACE_MSG ("Added alarm %s in event %s", alarm_uid, gcal_event_get_uid (self)); - e_cal_component_alarm_free (alarm); + e_cal_component_alarm_free (new_alarm); + + GCAL_EXIT; } /** diff --git a/src/core/gcal-event.h b/src/core/gcal-event.h index 37339096..7ec8a4af 100644 --- a/src/core/gcal-event.h +++ b/src/core/gcal-event.h @@ -85,12 +85,10 @@ gboolean gcal_event_has_alarms (GcalEvent GList* gcal_event_get_alarms (GcalEvent *self); -void gcal_event_add_alarm (GcalEvent *self, - guint type, - gboolean has_sound); +void gcal_event_remove_all_alarms (GcalEvent *self); -void gcal_event_remove_alarm (GcalEvent *self, - guint type); +void gcal_event_add_alarm (GcalEvent *self, + ECalComponentAlarm *alarm); const gchar* gcal_event_get_location (GcalEvent *self); diff --git a/src/gui/alarm-row.ui b/src/gui/alarm-row.ui deleted file mode 100644 index acf8bd9c..00000000 --- a/src/gui/alarm-row.ui +++ /dev/null @@ -1,73 +0,0 @@ - - - - - True - False - - - True - False - True - True - True - - - True - False - end - 0 - - - - - False - True - 0 - - - - - True - True - True - Toggles the sound of the alarm - - - True - False - audio-volume-high-symbolic - - - - - False - True - 1 - - - - - True - True - True - Remove the alarm - - - True - False - window-close-symbolic - - - - - False - True - 2 - - - - - diff --git a/src/gui/gcal-alarm-row.c b/src/gui/gcal-alarm-row.c new file mode 100644 index 00000000..1f95426c --- /dev/null +++ b/src/gui/gcal-alarm-row.c @@ -0,0 +1,367 @@ +/* gcal-alarm-row.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalAlarmRow" + +#include "config.h" + +#include + +#include "gcal-alarm-row.h" + +struct _GcalAlarmRow +{ + HdyActionRow parent; + + GtkToggleButton *volume_button; + GtkImage *volume_icon; + + ECalComponentAlarm *alarm; +}; + +G_DEFINE_TYPE (GcalAlarmRow, gcal_alarm_row, HDY_TYPE_ACTION_ROW) + +enum +{ + PROP_0, + PROP_ALARM, + N_PROPS +}; + +enum +{ + REMOVE_ALARM, + N_SIGNALS, +}; + +static guint signals [N_SIGNALS] = { 0, }; +static GParamSpec *properties [N_PROPS] = { NULL, }; + +/* + * Auxiliary methods + */ + +static gchar* +format_alarm_duration (ICalDuration *duration) +{ + guint minutes; + guint hours; + guint days; + + days = i_cal_duration_get_weeks (duration) * 7 + i_cal_duration_get_days (duration); + hours = i_cal_duration_get_hours (duration); + minutes = i_cal_duration_get_minutes (duration); + + if (days > 0) + { + if (hours > 0) + { + if (minutes > 0) + { + /* + * Translators: %1$u is days (in numbers), %2$u is hours (in numbers), and %3$u is minutes (in numbers). + * The full sentence would be "X days, X hours, and X minutes before the event starts". + */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + "%1$u day, %2$u hour, and %3$u minute before", + "%1$u day, %2$u hour, and %3$u minutes before", + minutes), + g_dngettext (GETTEXT_PACKAGE, + "%1$u day, %2$u hours, and %3$u minute before", + "%1$u day, %2$u hours, and %3$u minutes before", + minutes), + hours), + g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + "%1$u days, %2$u hour, and %3$u minute before", + "%1$u days, %2$u hour, and %3$u minutes before", + minutes), + g_dngettext (GETTEXT_PACKAGE, + "%1$u days, %2$u hours, and %3$u minute before", + "%1$u days, %2$u hours, and %3$u minutes before", + minutes), + hours), + days); + return g_strdup_printf (format, days, hours, minutes); + } + else + { + /* + * Translators: %1$u is days (in numbers) and %2$u is hours (in numbers). The full sentence would be "X + * days and X hours before the event starts". + */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + "%1$u day and %2$u hour before", + "%1$u day and %2$u hours before", + hours), + g_dngettext (GETTEXT_PACKAGE, + "%1$u days and %2$u hour before", + "%1$u days and %2$u hours before", + hours), + days); + return g_strdup_printf (format, days, hours); + } + } + else + { + if (minutes > 0) + { + /* Translators: %1$u is days (in numbers) and %2$u is minutes (in numbers). The full sentence would be "X + * days and X hours before the event starts". + */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + "%1$u day and %2$u minute before", + "%1$u day and %2$u minutes before", + minutes), + g_dngettext (GETTEXT_PACKAGE, + "%1$u days and %2$u minute before", + "%1$u days and %2$u minutes before", + minutes), + days); + return g_strdup_printf (format, days, minutes); + } + else + { + /* Translators: %1$u is days (in numbers). The full sentence would be "X days before the event starts". */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + "%1$u day before", + "%1$u days before", + days); + return g_strdup_printf (format, days); + } + } + } + else + { + if (hours > 0) + { + if (minutes > 0) + { + /* + * Translators: %1$u is hours (in numbers), and %2$u is minutes (in numbers). The full sentence would be + * "X hours and X minutes before the event starts". */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + g_dngettext (GETTEXT_PACKAGE, + "%1$u hour and %2$u minute before", + "%1$u hour and %2$u minutes before", + minutes), + g_dngettext (GETTEXT_PACKAGE, + "%1$u hours and %2$u minute before", + "%1$u hours and %2$u minutes before", + minutes), + hours); + return g_strdup_printf (format, hours, minutes); + } + else + { + /* Translators: %1$u is hours (in numbers). The full sentence would be "X hours before the event starts". */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + "%1$u hour before", + "%1$u hours before", + hours); + return g_strdup_printf (format, hours); + } + } + else + { + if (minutes > 0) + { + /* Translators: %1$u is minutes (in numbers). The full sentence would be "X minutes before the event starts". */ + const gchar *format = g_dngettext (GETTEXT_PACKAGE, + "%1$u minute before", + "%1$u minutes before", + minutes); + return g_strdup_printf (format, minutes); + } + else + { + return g_strdup (_("Event start time")); + } + } + } +} + +static void +setup_alarm (GcalAlarmRow *self) +{ + g_autofree gchar *formatted_duration = NULL; + ECalComponentAlarmTrigger *trigger; + ECalComponentAlarmAction action; + ICalDuration *duration; + + trigger = e_cal_component_alarm_get_trigger (self->alarm); + duration = e_cal_component_alarm_trigger_get_duration (trigger); + formatted_duration = format_alarm_duration (duration); + + hdy_action_row_set_title (HDY_ACTION_ROW (self), formatted_duration); + + action = e_cal_component_alarm_get_action (self->alarm); + gtk_toggle_button_set_active (self->volume_button, action == E_CAL_COMPONENT_ALARM_AUDIO); +} + + +/* + * Callbacks + */ + +static void +on_remove_button_clicked_cb (GtkButton *button, + GcalAlarmRow *self) +{ + g_signal_emit (self, signals[REMOVE_ALARM], 0); +} + +static void +on_sound_toggle_changed_cb (GtkToggleButton *button, + GParamSpec *pspec, + GcalAlarmRow *self) +{ + ECalComponentAlarmAction action; + gboolean has_sound; + + has_sound = gtk_toggle_button_get_active (button); + + /* Setup the alarm action */ + action = has_sound ? E_CAL_COMPONENT_ALARM_AUDIO : E_CAL_COMPONENT_ALARM_DISPLAY; + + e_cal_component_alarm_set_action (self->alarm, action); + + /* Update the volume icon */ + gtk_image_set_from_icon_name (self->volume_icon, + has_sound ? "audio-volume-high-symbolic" : "audio-volume-muted-symbolic", + GTK_ICON_SIZE_BUTTON); + +} + + +/* + * GObject overrides + */ + +static void +gcal_alarm_row_finalize (GObject *object) +{ + GcalAlarmRow *self = (GcalAlarmRow *)object; + + g_clear_pointer (&self->alarm, e_cal_component_alarm_free); + + G_OBJECT_CLASS (gcal_alarm_row_parent_class)->finalize (object); +} + +static void +gcal_alarm_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalAlarmRow *self = GCAL_ALARM_ROW (object); + + switch (prop_id) + { + case PROP_ALARM: + g_value_set_pointer (value, self->alarm); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_alarm_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalAlarmRow *self = GCAL_ALARM_ROW (object); + + switch (prop_id) + { + case PROP_ALARM: + g_assert (self->alarm == NULL); + self->alarm = e_cal_component_alarm_copy (g_value_get_pointer (value)); + setup_alarm (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_alarm_row_class_init (GcalAlarmRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_alarm_row_finalize; + object_class->get_property = gcal_alarm_row_get_property; + object_class->set_property = gcal_alarm_row_set_property; + + properties[PROP_ALARM] = g_param_spec_pointer ("alarm", + "Alarm", + "Alarm", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[REMOVE_ALARM] = g_signal_new ("remove-alarm", + GCAL_TYPE_ALARM_ROW, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/gcal-alarm-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalAlarmRow, volume_button); + gtk_widget_class_bind_template_child (widget_class, GcalAlarmRow, volume_icon); + + gtk_widget_class_bind_template_callback (widget_class, on_remove_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_sound_toggle_changed_cb); +} + +static void +gcal_alarm_row_init (GcalAlarmRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget* +gcal_alarm_row_new (ECalComponentAlarm *alarm) +{ + return g_object_new (GCAL_TYPE_ALARM_ROW, + "alarm", alarm, + NULL); +} + +ECalComponentAlarm* +gcal_alarm_row_get_alarm (GcalAlarmRow *self) +{ + g_return_val_if_fail (GCAL_IS_ALARM_ROW (self), NULL); + + return self->alarm; +} diff --git a/src/gui/gcal-alarm-row.h b/src/gui/gcal-alarm-row.h new file mode 100644 index 00000000..8c10f07a --- /dev/null +++ b/src/gui/gcal-alarm-row.h @@ -0,0 +1,36 @@ +/* gcal-alarm-row.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * 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 3 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_ALARM_ROW (gcal_alarm_row_get_type()) + +G_DECLARE_FINAL_TYPE (GcalAlarmRow, gcal_alarm_row, GCAL, ALARM_ROW, HdyActionRow) + +GtkWidget* gcal_alarm_row_new (ECalComponentAlarm *alarm); + +ECalComponentAlarm* gcal_alarm_row_get_alarm (GcalAlarmRow *self); + +G_END_DECLS diff --git a/src/gui/gcal-alarm-row.ui b/src/gui/gcal-alarm-row.ui new file mode 100644 index 00000000..7d416c4c --- /dev/null +++ b/src/gui/gcal-alarm-row.ui @@ -0,0 +1,49 @@ + + + + diff --git a/src/gui/gcal-edit-dialog.c b/src/gui/gcal-edit-dialog.c index 7dd242d1..3c5e032d 100644 --- a/src/gui/gcal-edit-dialog.c +++ b/src/gui/gcal-edit-dialog.c @@ -19,6 +19,7 @@ #define G_LOG_DOMAIN "GcalEditDialog" +#include "gcal-alarm-row.h" #include "gcal-context.h" #include "gcal-date-selector.h" #include "gcal-debug.h" @@ -82,6 +83,7 @@ struct _GcalEditDialog GtkWidget *notes_text; GtkWidget *alarms_listbox; + GtkListBoxRow *new_alarm_row; GtkWidget *repeat_combo; GtkWidget *repeat_duration_combo; @@ -107,6 +109,7 @@ struct _GcalEditDialog /* new data holders */ GcalEvent *event; GcalCalendar *selected_calendar; + GPtrArray *alarms; /* flags */ gboolean event_is_new; @@ -118,9 +121,6 @@ static void on_calendar_selected_action_cb (GSimpleAction GVariant *value, gpointer user_data); -static void on_sound_toggle_changed_cb (GtkToggleButton *button, - GtkWidget *row); - static void on_summary_entry_changed_cb (GtkEntry *entry, GParamSpec *pspec, GcalEditDialog *self); @@ -129,9 +129,6 @@ static void on_location_entry_changed_cb (GtkEntry GParamSpec *pspec, GcalEditDialog *self); -static void on_remove_alarm_button_clicked (GtkButton *button, - GtkWidget *row); - static void on_add_alarm_button_clicked_cb (GtkWidget *button, GcalEditDialog *self); @@ -617,110 +614,44 @@ get_row_for_alarm_trigger_minutes (GcalEditDialog *self, return NULL; } -static GtkWidget* -create_row_for_alarm (GcalEvent *event, - ECalComponentAlarm *alarm) +static ECalComponentAlarm* +create_alarm (guint minutes) { - ECalComponentAlarmAction action; - GtkBuilder *builder; - GtkWidget *label, *main_box, *row, *remove_button; - GtkWidget *volume_button, *volume_icon; - gboolean has_sound; - gchar *text; - gint trigger_minutes; - trigger_minutes = get_alarm_trigger_minutes (event, alarm); + ECalComponentAlarmTrigger *trigger; + ECalComponentAlarm *alarm; + ICalDuration *duration; - /* Something bad happened */ - if (trigger_minutes < 0) - return NULL; + duration = i_cal_duration_new_null_duration (); + i_cal_duration_set_is_neg (duration, TRUE); + i_cal_duration_set_minutes (duration, minutes); - if (trigger_minutes < 60) - { - text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%d minute before", - "%d minutes before", - trigger_minutes), - trigger_minutes); - } - else if (trigger_minutes < 1440) - { - text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%d hour before", - "%d hours before", - trigger_minutes / 60), - trigger_minutes / 60); - } - else if (trigger_minutes < 10080) - { - text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%d day before", - "%d days before", - trigger_minutes / 1440), - trigger_minutes / 1440); - } - else - { - text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, - "%d week before", - "%d weeks before", - trigger_minutes / 10080), - trigger_minutes / 10080); - } + trigger = e_cal_component_alarm_trigger_new_relative (E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START, duration); - /* The row */ - row = gtk_list_box_row_new (); - gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + g_clear_object (&duration); - g_object_set_data (G_OBJECT (row), "alarm", alarm); - g_object_set_data (G_OBJECT (row), "event", event); + alarm = e_cal_component_alarm_new (); + e_cal_component_alarm_take_trigger (alarm, trigger); + e_cal_component_alarm_set_action (alarm, E_CAL_COMPONENT_ALARM_DISPLAY); - /* Build the UI */ - builder = gtk_builder_new_from_resource ("/org/gnome/calendar/ui/gui/alarm-row.ui"); - -#define WID(x) (GTK_WIDGET (gtk_builder_get_object (builder, x))) - - label = WID ("label"); - gtk_label_set_label (GTK_LABEL (label), text); - - /* Retrieves the actions associated to the alarm */ - action = e_cal_component_alarm_get_action (alarm); - - /* Updates the volume button to match the action */ - has_sound = action == E_CAL_COMPONENT_ALARM_AUDIO; - - volume_button = WID ("volume_button"); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (volume_button), has_sound); - - volume_icon = WID ("volume_icon"); - gtk_image_set_from_icon_name (GTK_IMAGE (volume_icon), - has_sound ? "audio-volume-high-symbolic" : "audio-volume-muted-symbolic", - GTK_ICON_SIZE_BUTTON); - - g_signal_connect_object (volume_button, "toggled", G_CALLBACK (on_sound_toggle_changed_cb), row, 0); - - /* Remove button */ - remove_button = WID ("remove_button"); - - g_signal_connect_object (remove_button, - "clicked", - G_CALLBACK (on_remove_alarm_button_clicked), - row, - 0); - - main_box = WID ("main_box"); - gtk_container_add (GTK_CONTAINER (row), main_box); - - gtk_widget_show_all (row); - - g_clear_object (&builder); - g_free (text); - -#undef WID - - return row; + return alarm; } +static void +clear_alarms (GcalEditDialog *self) +{ + g_autoptr (GList) children = NULL; + GList *l; + + g_ptr_array_set_size (self->alarms, 0); + + children = gtk_container_get_children (GTK_CONTAINER (self->alarms_listbox)); + for (l = children; l != NULL; l = l->next) + { + if (l->data != self->new_alarm_row) + gtk_widget_destroy (l->data); + } +} /* * Callbacks @@ -809,17 +740,24 @@ sort_alarms_func (GtkListBoxRow *a, GtkListBoxRow *b, gpointer user_data) { - ECalComponentAlarm *alarm_a, *alarm_b; - GcalEvent *event_a, *event_b; - gint minutes_a, minutes_b; + ECalComponentAlarm *alarm_a; + ECalComponentAlarm *alarm_b; + GcalEditDialog *self; + gint minutes_a; + gint minutes_b; - alarm_a = g_object_get_data (G_OBJECT (a), "alarm"); - alarm_b = g_object_get_data (G_OBJECT (b), "alarm"); - event_a = g_object_get_data (G_OBJECT (a), "event"); - event_b = g_object_get_data (G_OBJECT (b), "event"); + self = GCAL_EDIT_DIALOG (user_data); - minutes_a = get_alarm_trigger_minutes (event_a, alarm_a); - minutes_b = get_alarm_trigger_minutes (event_b, alarm_b); + if (a == self->new_alarm_row) + return 1; + else if (b == self->new_alarm_row) + return -1; + + alarm_a = gcal_alarm_row_get_alarm (GCAL_ALARM_ROW (a)); + minutes_a = get_alarm_trigger_minutes (self->event, alarm_a); + + alarm_b = gcal_alarm_row_get_alarm (GCAL_ALARM_ROW (b)); + minutes_b = get_alarm_trigger_minutes (self->event, alarm_b); return minutes_a - minutes_b; } @@ -859,6 +797,7 @@ on_action_button_clicked_cb (GtkWidget *widget, gboolean was_all_day; gboolean all_day; gchar *note_text; + gsize i; /* Update summary */ gcal_event_set_summary (self->event, gtk_entry_get_text (GTK_ENTRY (self->summary_entry))); @@ -932,6 +871,14 @@ on_action_button_clicked_cb (GtkWidget *widget, g_clear_pointer (&start_date, g_date_time_unref); g_clear_pointer (&end_date, g_date_time_unref); + /* Update alarms */ + gcal_event_remove_all_alarms (self->event); + + for (i = 0; i < self->alarms->len; i++) + gcal_event_add_alarm (self->event, g_ptr_array_index (self->alarms, i)); + + clear_alarms (self); + /* Check Repeat popover and set recurrence-rules accordingly */ old_recur = gcal_event_get_recurrence (self->event); freq = gtk_combo_box_get_active (GTK_COMBO_BOX (self->repeat_combo)); @@ -1035,67 +982,42 @@ on_all_day_switch_active_changed_cb (GtkSwitch *all_day_switch, } static void -on_remove_alarm_button_clicked (GtkButton *button, - GtkWidget *row) +on_remove_alarm_cb (GcalAlarmRow *alarm_row, + GcalEditDialog *self) { ECalComponentAlarm *alarm; - GcalEditDialog *self; GtkWidget *alarm_button; - GcalEvent *event; gint trigger_minutes; + gsize i; - self = GCAL_EDIT_DIALOG (gtk_widget_get_toplevel (row)); - alarm = g_object_get_data (G_OBJECT (row), "alarm"); - event = g_object_get_data (G_OBJECT (row), "event"); - trigger_minutes = get_alarm_trigger_minutes (event, alarm); + GCAL_ENTRY; - /* - * Make the button sensitive again - */ + alarm = gcal_alarm_row_get_alarm (alarm_row); + trigger_minutes = get_alarm_trigger_minutes (self->event, alarm); + + /* Make the button sensitive again */ alarm_button = get_row_for_alarm_trigger_minutes (self, trigger_minutes); if (alarm_button) gtk_widget_set_sensitive (alarm_button, TRUE); - gcal_event_remove_alarm (event, trigger_minutes); + /* Remove from the array */ + for (i = 0; i < self->alarms->len; i++) + { + ECalComponentAlarm *a = g_ptr_array_index (self->alarms, i); - gcal_manager_update_event (gcal_context_get_manager (self->context), - event, - GCAL_RECURRENCE_MOD_THIS_ONLY); + if (trigger_minutes == get_alarm_trigger_minutes (self->event, a)) + { + GCAL_TRACE_MSG ("Removed alarm for %d minutes", trigger_minutes); - gtk_widget_destroy (row); + g_ptr_array_remove_index (self->alarms, i); + break; + } + } - /* - * In order to not allocate a spacing between the listbox and the - * add alarms button, we should always keep the listbox:visible property - * updated. - */ - gtk_widget_set_visible (self->alarms_listbox, gcal_event_has_alarms (self->event)); -} - -static void -on_sound_toggle_changed_cb (GtkToggleButton *button, - GtkWidget *row) -{ - ECalComponentAlarmAction action; - ECalComponentAlarm *alarm; - GtkWidget *image; - gboolean has_sound; - - alarm = g_object_get_data (G_OBJECT (row), "alarm"); - image = gtk_bin_get_child (GTK_BIN (button)); - has_sound = gtk_toggle_button_get_active (button); - - /* Setup the alarm action */ - action = has_sound ? E_CAL_COMPONENT_ALARM_AUDIO : E_CAL_COMPONENT_ALARM_DISPLAY; - - e_cal_component_alarm_set_action (alarm, action); - - /* Update the volume icon */ - gtk_image_set_from_icon_name (GTK_IMAGE (image), - has_sound ? "audio-volume-high-symbolic" : "audio-volume-muted-symbolic", - GTK_ICON_SIZE_BUTTON); + gtk_container_remove (GTK_CONTAINER (self->alarms_listbox), GTK_WIDGET (alarm_row)); + GCAL_EXIT; } static void @@ -1109,58 +1031,71 @@ on_time_format_changed_cb (GcalEditDialog *self) gcal_time_selector_set_time_format (GCAL_TIME_SELECTOR (self->end_time_selector), time_format); } +static GtkWidget * +create_alarm_row (GcalEditDialog *self, + ECalComponentAlarm *alarm) +{ + GtkWidget *row; + + row = gcal_alarm_row_new (alarm); + g_signal_connect_object (row, "remove-alarm", G_CALLBACK (on_remove_alarm_cb), self, 0); + + return row; +} + static void setup_alarms (GcalEditDialog *self) { - GList *alarms, *l; - guint i; + g_autoptr (GList) alarms = NULL; + GList *l; + gsize i; - gtk_widget_set_visible (self->alarms_listbox, gcal_event_has_alarms (self->event)); + GCAL_ENTRY; - alarms = gcal_event_get_alarms (self->event); + clear_alarms (self); - /* Remove previous alarms */ - gtk_container_foreach (GTK_CONTAINER (self->alarms_listbox), - (GtkCallback) gtk_widget_destroy, - NULL); - - /* - * We start by making all alarm buttons sensitive, - * and only make them insensitive when needed. - */ + /* We start by making all alarm buttons sensitive, and only make them insensitive when needed */ for (i = 0; i < G_N_ELEMENTS (minutes_button); i++) gtk_widget_set_sensitive (WIDGET_FROM_OFFSET (minutes_button[i].button_offset), TRUE); + alarms = gcal_event_get_alarms (self->event); for (l = alarms; l != NULL; l = l->next) + g_ptr_array_add (self->alarms, l->data); + + for (i = 0; i < self->alarms->len; i++) { + ECalComponentAlarm *alarm; GtkWidget *row; gint minutes; + guint j; - row = create_row_for_alarm (self->event, l->data); - - if (!row) - continue; + alarm = g_ptr_array_index (self->alarms, i); /* Make already-added alarm buttons insensitive */ - minutes = get_alarm_trigger_minutes (self->event, l->data); + minutes = get_alarm_trigger_minutes (self->event, alarm); - for (i = 0; i < G_N_ELEMENTS (minutes_button); i++) + for (j = 0; j < G_N_ELEMENTS (minutes_button); j++) { if (minutes_button[i].minutes == minutes) - gtk_widget_set_sensitive (WIDGET_FROM_OFFSET (minutes_button[i].button_offset), FALSE); + gtk_widget_set_sensitive (WIDGET_FROM_OFFSET (minutes_button[j].button_offset), FALSE); } + GCAL_TRACE_MSG ("Adding alarm for %u minutes", minutes); + /* Add the row */ + row = create_alarm_row (self, alarm); gtk_container_add (GTK_CONTAINER (self->alarms_listbox), row); } - g_list_free (alarms); + GCAL_EXIT; } static void on_add_alarm_button_clicked_cb (GtkWidget *button, GcalEditDialog *self) { + ECalComponentAlarm *alarm; + GtkWidget *row; guint i, minutes; /* Search for the button minute */ @@ -1178,19 +1113,13 @@ on_add_alarm_button_clicked_cb (GtkWidget *button, if (minutes == 0) return; - /* Add the alarm */ - gcal_event_add_alarm (self->event, minutes, FALSE); + alarm = create_alarm (minutes); - /* - * Instead of manually handling stuff, simply remove all alarms and - * add back again. - */ - setup_alarms (self); + row = create_alarm_row (self, alarm); + gtk_container_add (GTK_CONTAINER (self->alarms_listbox), row); + + g_ptr_array_add (self->alarms, alarm); - /* - * Since we don't allow more than 1 alarm per time, set the button - * to insensitive so it cannot be triggered anymore. - */ gtk_widget_set_sensitive (button, FALSE); } @@ -1208,6 +1137,7 @@ gcal_edit_dialog_finalize (GObject *object) self = GCAL_EDIT_DIALOG (object); + g_clear_pointer (&self->alarms, g_ptr_array_unref); g_clear_object (&self->action_group); g_clear_object (&self->context); g_clear_object (&self->event); @@ -1372,6 +1302,7 @@ gcal_edit_dialog_class_init (GcalEditDialogClass *klass) gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, two_days_button); gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, three_days_button); gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, one_week_button); + gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, new_alarm_row); /* Buttons */ gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, done_button); gtk_widget_class_bind_template_child (widget_class, GcalEditDialog, cancel_button); @@ -1418,6 +1349,7 @@ gcal_edit_dialog_class_init (GcalEditDialogClass *klass) static void gcal_edit_dialog_init (GcalEditDialog *self) { + self->alarms = g_ptr_array_new_with_free_func (e_cal_component_alarm_free); self->writable = TRUE; gtk_widget_init_template (GTK_WIDGET (self)); diff --git a/src/gui/gcal-edit-dialog.ui b/src/gui/gcal-edit-dialog.ui index 11172adf..ec75e431 100644 --- a/src/gui/gcal-edit-dialog.ui +++ b/src/gui/gcal-edit-dialog.ui @@ -442,38 +442,36 @@ + - + True - none - + vertical - - + True - Reminder - alarms_button + False + none + - - + + True - False - center - 6 - True - vertical - - + Reminder + alarms_button + + True True - True + center alarms_popover + True @@ -499,13 +497,7 @@ - - - False - False - none - - + diff --git a/src/meson.build b/src/meson.build index 8815b6b9..7311b53d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -107,6 +107,7 @@ sources = files( 'gui/calendar-management/gcal-calendars-page.c', 'gui/calendar-management/gcal-edit-calendar-page.c', 'gui/calendar-management/gcal-new-calendar-page.c', + 'gui/gcal-alarm-row.c', 'gui/gcal-application.c', 'gui/gcal-calendar-popover.c', 'gui/gcal-date-chooser.c',