diff --git a/po/POTFILES.in b/po/POTFILES.in index 6245cf142e61601db42394f86fefd39fdfbef178..dfdec3a9d0e3442235ddf008cca2fcf6523f5d83 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -27,6 +27,8 @@ src/gui/event-editor/gcal-schedule-section.ui src/gui/event-editor/gcal-summary-section.c src/gui/event-editor/gcal-summary-section.ui src/gui/event-editor/gcal-time-selector.ui +src/gui/event-editor/gcal-time-zone-dialog.c +src/gui/event-editor/gcal-time-zone-dialog.ui src/gui/gcal-application.c src/gui/gcal-calendar-button.ui src/gui/gcal-calendar-combo-row.ui diff --git a/src/core/gcal-event.c b/src/core/gcal-event.c index 09c90b4ea532d5afede79984cec366fa1af336ca..7f675123cb1b05cc5935f35a55e343fe1ecca58c 100644 --- a/src/core/gcal-event.c +++ b/src/core/gcal-event.c @@ -234,7 +234,7 @@ build_component_from_datetime (GcalEvent *self, g_autoptr (GTimeZone) zone = NULL; ICalTimezone *tz; - zone = gcal_util_get_app_timezone_or_local (); + zone = g_date_time_get_timezone (dt); tz = gcal_timezone_to_icaltimezone (zone); i_cal_time_set_timezone (itt, tz); tzid = g_strdup (i_cal_timezone_get_tzid (tz)); diff --git a/src/gui/event-editor/event-editor.gresource.xml b/src/gui/event-editor/event-editor.gresource.xml index 196b62db4a405ba095be229bfa82d79b998e1517..8ff039a73b3bb8a8603dac5e7ce847678d290bc8 100644 --- a/src/gui/event-editor/event-editor.gresource.xml +++ b/src/gui/event-editor/event-editor.gresource.xml @@ -5,6 +5,7 @@ gcal-date-chooser.ui gcal-date-chooser-day.ui gcal-date-chooser-row.ui + gcal-date-time-chooser.ui gcal-date-selector.ui gcal-event-editor-dialog.ui gcal-multi-choice.ui @@ -12,7 +13,7 @@ gcal-reminders-section.ui gcal-schedule-section.ui gcal-summary-section.ui - gcal-time-chooser-row.ui - gcal-time-selector.ui + gcal-time-zone-dialog.ui + gcal-time-zone-dialog-row.ui diff --git a/src/gui/event-editor/gcal-date-chooser-row.c b/src/gui/event-editor/gcal-date-chooser-row.c index 301a07fe20c66f1b94b193b0f6eb8d81f6963893..b64952eb4b8508e5af0599f74fda2c6f5419f89c 100644 --- a/src/gui/event-editor/gcal-date-chooser-row.c +++ b/src/gui/event-editor/gcal-date-chooser-row.c @@ -299,9 +299,16 @@ void gcal_date_chooser_row_set_date (GcalDateChooserRow *self, GDateTime *date) { + g_autoptr (GDateTime) date_utc = NULL; + g_return_if_fail (GCAL_IS_DATE_CHOOSER_ROW (self)); - gcal_view_set_date (GCAL_VIEW (self->date_chooser), date); + date_utc = g_date_time_new_utc (g_date_time_get_year (date), + g_date_time_get_month (date), + g_date_time_get_day_of_month (date), + 0, 0, 0); + + gcal_view_set_date (GCAL_VIEW (self->date_chooser), date_utc); update_entry (self); diff --git a/src/gui/event-editor/gcal-date-time-chooser.c b/src/gui/event-editor/gcal-date-time-chooser.c new file mode 100644 index 0000000000000000000000000000000000000000..344cb5447e5a57e716915425bdc431c984d02967 --- /dev/null +++ b/src/gui/event-editor/gcal-date-time-chooser.c @@ -0,0 +1,829 @@ +/* gcal-date-time-chooser.c + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ + +#define G_LOG_DOMAIN "GcalDateTimeChooser" + +#include "gcal-date-chooser-row.h" +#include "gcal-date-time-chooser.h" +#include "gcal-date-time-utils.h" +#include "gcal-debug.h" +#include "gcal-time-zone-dialog.h" + +#include + +struct _GcalDateTimeChooser +{ + AdwPreferencesGroup parent; + + GcalDateChooserRow *date_row; + AdwEntryRow *time_row; + GtkPopover *time_popover; + AdwButtonContent *time_zone_button_content; + AdwToggleGroup *period_toggle_group; + + GtkAdjustment *hour_adjustment; + GtkAdjustment *minute_adjustment; + + GDateTime *date_time; + GcalTimeFormat time_format; +}; + +G_DEFINE_TYPE (GcalDateTimeChooser, gcal_date_time_chooser, ADW_TYPE_PREFERENCES_GROUP); + +enum +{ + PROP_0, + PROP_DATE_TIME, + PROP_DATE_LABEL, + PROP_TIME_LABEL, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS] = { + NULL, +}; + +static void on_spin_buttons_value_changed_cb (GcalDateTimeChooser *self); + +static void on_period_toggle_group_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalDateTimeChooser *self); + +/* + * Auxiliary methods + */ + +/* + * Use the stored date_time of the GcalDateTimeChooser to format the content of the time entry. + */ +static void +normalize_time_entry (GcalDateTimeChooser *self) +{ + g_autoptr (GTimeZone) local_time_zone = g_time_zone_new_local (); + g_autoptr (GString) label = NULL; + const gchar *date_time_identifier; + const gchar *local_identifier; + gchar *date_time_str; + + GCAL_ENTRY; + + switch (self->time_format) + { + case GCAL_TIME_FORMAT_12H: + date_time_str = g_date_time_format (self->date_time, "%I:%M %p"); + break; + + case GCAL_TIME_FORMAT_24H: + date_time_str = g_date_time_format (self->date_time, "%R"); + break; + + default: + g_assert_not_reached (); + } + + label = g_string_new_take (date_time_str); + + date_time_identifier = g_time_zone_get_identifier (g_date_time_get_timezone (self->date_time)); + local_identifier = g_time_zone_get_identifier (local_time_zone); + + if (g_strcmp0 (date_time_identifier, local_identifier) != 0) + { + g_autofree gchar *time_zone_str = gcal_date_time_format_utc_offset (self->date_time); + + g_string_append (label, " ("); + g_string_append (label, time_zone_str); + g_string_append (label, ")"); + } + + gtk_editable_set_text (GTK_EDITABLE (self->time_row), label->str); + + GCAL_EXIT; +} + +/* + * Use the timezone of the stored date_time of the GcalDateTimeChooser to format the label of the timezone button. + */ +static void +normalize_timezone_button_label (GcalDateTimeChooser *self) +{ + g_autofree gchar *formatted = NULL; + g_autofree gchar *utc_str = NULL; + const gchar *identifier; + + identifier = g_time_zone_get_identifier (g_date_time_get_timezone (self->date_time)); + + if (g_strcmp0 (identifier, "UTC") == 0) + { + adw_button_content_set_label (self->time_zone_button_content, "UTC"); + return; + } + + utc_str = gcal_date_time_format_utc_offset (self->date_time); + + /* Translators: "%1$s" is the timezone identifier (e.g. UTC, America/Sao_Paulo) + * and "%2$s" is the timezone offset (e.g. UTC-3). + */ + formatted = g_strdup_printf (_("%1$s (%2$s)"), identifier, utc_str); + + adw_button_content_set_label (self->time_zone_button_content, formatted); +} + +/* + * Use the time of the stored date_time of the GcalDateTimeChooser to format the spinners and the am/pm toggle of the + * time popover. + */ +static void +normalize_time_popover (GcalDateTimeChooser *self) +{ + gint new_hour, new_minute; + + GCAL_ENTRY; + + new_hour = g_date_time_get_hour (self->date_time); + new_minute = g_date_time_get_minute (self->date_time); + + g_signal_handlers_block_by_func (self->hour_adjustment, on_spin_buttons_value_changed_cb, self); + g_signal_handlers_block_by_func (self->minute_adjustment, on_spin_buttons_value_changed_cb, self); + g_signal_handlers_block_by_func (self->period_toggle_group, on_period_toggle_group_active_changed_cb, self); + + adw_toggle_group_set_active_name (self->period_toggle_group, new_hour < 12 ? "am" : "pm"); + + switch (self->time_format) + { + case GCAL_TIME_FORMAT_12H: + if (new_hour == 0) + gtk_adjustment_set_value (self->hour_adjustment, 12); + else if (new_hour <= 12) + gtk_adjustment_set_value (self->hour_adjustment, new_hour); + else + gtk_adjustment_set_value (self->hour_adjustment, new_hour - 12); + break; + + case GCAL_TIME_FORMAT_24H: + gtk_adjustment_set_value (self->hour_adjustment, new_hour); + break; + + default: + g_assert_not_reached (); + } + + gtk_adjustment_set_value (self->minute_adjustment, new_minute); + + g_signal_handlers_unblock_by_func (self->hour_adjustment, on_spin_buttons_value_changed_cb, self); + g_signal_handlers_unblock_by_func (self->minute_adjustment, on_spin_buttons_value_changed_cb, self); + g_signal_handlers_unblock_by_func (self->period_toggle_group, on_period_toggle_group_active_changed_cb, self); + + GCAL_EXIT; +} + +static GRegex * +get_12h_regex (void) +{ + static GRegex *regex = NULL; + + if (g_once_init_enter_pointer (®ex)) + { + g_autoptr (GError) error = NULL; + g_autofree gchar *str = NULL; + + str = g_strdup_printf ("^(?[0-9]{1,2})([\\-:./_,'a-z](?[0-9]{1,2}))? ?(?%s|%s)", + _ ("AM"), _ ("PM")); + + regex = g_regex_new (str, G_REGEX_CASELESS, 0, &error); + + if (error) + g_error ("Failed to compile regex: %s. Aborting...", error->message); + } + + return regex; +} + +static GRegex * +get_24h_regex (void) +{ + static GRegex *regex = NULL; + + if (g_once_init_enter_pointer (®ex)) + { + g_autoptr (GError) error = NULL; + + regex = g_regex_new ("^(?[0-9]{1,2})([\\-:./_,'a-z](?[0-9]{1,2}))?", G_REGEX_CASELESS, 0, &error); + + if (error) + g_error ("Failed to compile regex: %s. Aborting...", error->message); + } + + return regex; +} + +/* + * Parse the string of the time entry. + * If the string is not recognised as a time, it resets the entry to the formatted form of the last time. + * If it is recognised, the stored date_time is updated and the entry is formatted accordingly. + */ +static void +validate_and_normalize_time_entry (GcalDateTimeChooser *self) +{ + const gchar *text; + + text = gtk_editable_get_text (GTK_EDITABLE (self->time_row)); + + switch (self->time_format) + { + case GCAL_TIME_FORMAT_12H: + { + g_autoptr (GMatchInfo) match_info = NULL; + g_autoptr (GDateTime) date_time = NULL; + g_autofree gchar *minute_str = NULL; + g_autofree gchar *hour_str = NULL; + g_autofree gchar *ampm_str = NULL; + gboolean am; + gint64 hour, minute; + + if (!g_regex_match (get_12h_regex (), text, 0, &match_info)) + { + normalize_time_entry (self); + break; + } + + hour_str = g_match_info_fetch_named (match_info, "hour"); + minute_str = g_match_info_fetch_named (match_info, "minute"); + ampm_str = g_match_info_fetch_named (match_info, "ampm"); + + hour = g_ascii_strtoll (hour_str, NULL, 10); + minute = g_ascii_strtoll (minute_str, NULL, 10); + am = (strcmp (g_ascii_strup (ampm_str, -1), _ ("AM")) == 0); + + if (hour == 0 || hour > 12 || minute > 59) + normalize_time_entry (self); + + if (hour == 12) + hour = 0; + + if (!am) + hour += 12; + + date_time = g_date_time_new (g_date_time_get_timezone (self->date_time), + g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + hour, minute, 0); + + gcal_date_time_chooser_set_date_time (self, date_time); + } + break; + + case GCAL_TIME_FORMAT_24H: + { + g_autoptr (GMatchInfo) match_info = NULL; + g_autoptr (GDateTime) date_time = NULL; + g_autofree gchar *minute_str = NULL; + g_autofree gchar *hour_str = NULL; + g_autofree gchar *ampm_str = NULL; + gint64 hour, minute; + + if (!g_regex_match (get_24h_regex (), text, 0, &match_info)) + { + normalize_time_entry (self); + break; + } + + hour_str = g_match_info_fetch_named (match_info, "hour"); + minute_str = g_match_info_fetch_named (match_info, "minute"); + + hour = g_ascii_strtoll (hour_str, NULL, 10); + minute = g_ascii_strtoll (minute_str, NULL, 10); + + if (hour > 23 || minute > 59) + { + normalize_time_entry (self); + break; + } + + date_time = g_date_time_new (g_date_time_get_timezone (self->date_time), + g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + hour, minute, 0); + + gcal_date_time_chooser_set_date_time (self, date_time); + } + break; + + default: + g_assert_not_reached (); + } +} + + +/* + * Callbacks + */ + +static void +on_date_changed_cb (GcalDateTimeChooser *self, + GParamSpec *pspec, + GtkWidget *widget) +{ + g_autoptr (GDateTime) new_date_time = NULL; + GDateTime *date_chooser_row_date; + + GCAL_ENTRY; + + date_chooser_row_date = gcal_date_chooser_row_get_date (self->date_row); + new_date_time = g_date_time_new (g_date_time_get_timezone (self->date_time), + g_date_time_get_year (date_chooser_row_date), + g_date_time_get_month (date_chooser_row_date), + g_date_time_get_day_of_month (date_chooser_row_date), + g_date_time_get_hour (self->date_time), + g_date_time_get_minute (self->date_time), + 0); + + gcal_date_time_chooser_set_date_time (self, new_date_time); + + GCAL_EXIT; +} + +static void +on_time_zone_selected_cb (GcalTimeZoneDialog *dialog, + GcalDateTimeChooser *self) +{ + GTimeZone *time_zone; + GDateTime *new; + + GCAL_ENTRY; + + time_zone = gcal_time_zone_dialog_get_time_zone (dialog); + + new = g_date_time_new (time_zone, + g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + g_date_time_get_hour (self->date_time), + g_date_time_get_minute (self->date_time), + 0); + + gcal_date_time_chooser_set_date_time (self, new); + + GCAL_EXIT; +} + +static void +on_time_zone_button_clicked_cb (GtkButton *button, + GcalDateTimeChooser *self) +{ + AdwDialog *dialog; + + validate_and_normalize_time_entry (self); + + dialog = ADW_DIALOG (gcal_time_zone_dialog_new (self->date_time)); + g_signal_connect (dialog, "timezone-selected", G_CALLBACK (on_time_zone_selected_cb), self); + gtk_popover_popdown (self->time_popover); + adw_dialog_present (dialog, GTK_WIDGET (self)); +} + +static void +on_time_row_contains_focus_changed_cb (GtkEventControllerFocus *focus_controller, + GParamSpec *pspec, + GcalDateTimeChooser *self) +{ + validate_and_normalize_time_entry (self); +} + +static void +on_time_popover_shown_cb (GtkPopover *popover, + GcalDateTimeChooser *self) +{ + validate_and_normalize_time_entry (self); +} + +static void +on_spin_button_output_cb (GtkWidget *widget, + GcalDateTimeChooser *self) +{ + g_autofree gchar *text = NULL; + GtkAdjustment *adjustment; + gint hour; + + adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget)); + hour = gtk_adjustment_get_value (adjustment); + + text = g_strdup_printf ("%02d", hour); + gtk_editable_set_text (GTK_EDITABLE (widget), text); +} + +static void +on_spin_buttons_value_changed_cb (GcalDateTimeChooser *self) +{ + g_autoptr (GDateTime) new_date_time = NULL; + gint hour, minute; + + GCAL_ENTRY; + + hour = gtk_adjustment_get_value (self->hour_adjustment); + minute = gtk_adjustment_get_value (self->minute_adjustment); + + switch (self->time_format) + { + case GCAL_TIME_FORMAT_12H: + if (hour == 12) + hour = 0; + if (g_strcmp0 (adw_toggle_group_get_active_name (self->period_toggle_group), "pm") == 0) + hour += 12; + break; + + case GCAL_TIME_FORMAT_24H: + break; + + default: + g_assert_not_reached (); + } + + new_date_time = g_date_time_new (g_date_time_get_timezone (self->date_time), + g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + hour, minute, 0); + + gcal_date_time_chooser_set_date_time (self, new_date_time); + + GCAL_EXIT; +} + +static void +on_hour_spin_button_wrapped_cb (GtkSpinButton *spin_button, + GcalDateTimeChooser *self) +{ + if (g_strcmp0 (adw_toggle_group_get_active_name (self->period_toggle_group), "am") == 0) + adw_toggle_group_set_active_name (self->period_toggle_group, "pm"); + else + adw_toggle_group_set_active_name (self->period_toggle_group, "am"); +} + +static void +on_period_toggle_group_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalDateTimeChooser *self) +{ + g_autoptr (GDateTime) new_date_time = NULL; + + if (g_strcmp0 (adw_toggle_group_get_active_name (self->period_toggle_group), "am") == 0) + new_date_time = g_date_time_add_hours (self->date_time, -12); + else + new_date_time = g_date_time_add_hours (self->date_time, 12); + + gcal_date_time_chooser_set_date_time (self, new_date_time); +} + +/* + * GObject overrides + */ + +static void +gcal_date_time_chooser_dispose (GObject *object) +{ + GcalDateTimeChooser *self = GCAL_DATE_TIME_CHOOSER (object); + + gcal_clear_date_time (&self->date_time); + + G_OBJECT_CLASS (gcal_date_time_chooser_parent_class)->dispose (object); +} + +static void +gcal_date_time_chooser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalDateTimeChooser *self = GCAL_DATE_TIME_CHOOSER (object); + + switch (prop_id) + { + case PROP_DATE_TIME: + g_value_set_boxed (value, gcal_date_time_chooser_get_date_time (self)); + break; + + case PROP_DATE_LABEL: + g_value_set_string (value, gcal_date_time_chooser_get_date_label (self)); + break; + + case PROP_TIME_LABEL: + g_value_set_string (value, gcal_date_time_chooser_get_time_label (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_date_time_chooser_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalDateTimeChooser *self = GCAL_DATE_TIME_CHOOSER (object); + + switch (prop_id) + { + case PROP_DATE_TIME: + gcal_date_time_chooser_set_date_time (self, g_value_get_boxed (value)); + break; + + case PROP_DATE_LABEL: + gcal_date_time_chooser_set_date_label (self, g_value_get_string (value)); + break; + + case PROP_TIME_LABEL: + gcal_date_time_chooser_set_time_label (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_date_time_chooser_class_init (GcalDateTimeChooserClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_ensure (GCAL_TYPE_DATE_CHOOSER_ROW); + + object_class->dispose = gcal_date_time_chooser_dispose; + object_class->get_property = gcal_date_time_chooser_get_property; + object_class->set_property = gcal_date_time_chooser_set_property; + + /** + * GcalDateTimeChooser::date-time: + * + * The current date-time of the chooser. + */ + properties[PROP_DATE_TIME] = g_param_spec_boxed ("date-time", + "DateTime of the date-time chooser", + "The current date-time of the chooser", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GcalDateTimeChooser::date-label: + * + * The current date label of the date row. + */ + properties[PROP_DATE_LABEL] = g_param_spec_string ("date-label", + NULL, + NULL, + "", + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GcalDateTimeChooser::time-label: + * + * The current time label of the time row. + */ + properties[PROP_TIME_LABEL] = g_param_spec_string ("time-label", + NULL, + NULL, + "", + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-date-time-chooser.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, date_row); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, time_row); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, time_popover); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, time_zone_button_content); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, period_toggle_group); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, hour_adjustment); + gtk_widget_class_bind_template_child (widget_class, GcalDateTimeChooser, minute_adjustment); + + gtk_widget_class_bind_template_callback (widget_class, on_date_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_time_zone_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_time_row_contains_focus_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_time_popover_shown_cb); + gtk_widget_class_bind_template_callback (widget_class, on_spin_button_output_cb); + gtk_widget_class_bind_template_callback (widget_class, on_spin_buttons_value_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_hour_spin_button_wrapped_cb); + gtk_widget_class_bind_template_callback (widget_class, on_period_toggle_group_active_changed_cb); +} + +static void +gcal_date_time_chooser_init (GcalDateTimeChooser *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->date_time = g_date_time_new_now_local (); +} + +/* Public API */ +GtkWidget * +gcal_date_time_chooser_new (void) +{ + return g_object_new (GCAL_TYPE_DATE_TIME_CHOOSER, NULL); +} + +void +gcal_date_time_chooser_set_time_format (GcalDateTimeChooser *self, + GcalTimeFormat time_format) +{ + g_return_if_fail (GCAL_IS_DATE_TIME_CHOOSER (self)); + + self->time_format = time_format; + + gtk_widget_set_visible (GTK_WIDGET (self->period_toggle_group), time_format == GCAL_TIME_FORMAT_12H); + + switch (self->time_format) + { + case GCAL_TIME_FORMAT_12H: + gtk_adjustment_set_lower (self->hour_adjustment, 1); + gtk_adjustment_set_upper (self->hour_adjustment, 12); + break; + + case GCAL_TIME_FORMAT_24H: + gtk_adjustment_set_lower (self->hour_adjustment, 0); + gtk_adjustment_set_upper (self->hour_adjustment, 23); + break; + + default: + g_assert_not_reached (); + } + + normalize_time_entry (self); +} + +/** + * gcal_date_time_chooser_get_date_time: + * @self: a #GcalDateTimeChooser + * + * Get the value of the date-time shown + * + * Returns: (transfer none): the date-time of the chooser. + */ +GDateTime * +gcal_date_time_chooser_get_date_time (GcalDateTimeChooser *self) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + return g_date_time_ref (self->date_time); +} + +/** + * gcal_date_time_chooser_set_date_time: + * @self: a #GcalDateTimeChooser + * @date_time: a valid #GDateTime + * + * Set the value of the shown date-time. + */ +void +gcal_date_time_chooser_set_date_time (GcalDateTimeChooser *self, + GDateTime *date_time) +{ + GCAL_ENTRY; + + gcal_set_date_time (&self->date_time, date_time); + + g_signal_handlers_block_by_func (self->date_row, on_date_changed_cb, self); + gcal_date_chooser_row_set_date (self->date_row, date_time); + g_signal_handlers_unblock_by_func (self->date_row, on_date_changed_cb, self); + + normalize_time_entry (self); + normalize_timezone_button_label (self); + normalize_time_popover (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DATE_TIME]); + + GCAL_EXIT; +} + +/** + * gcal_date_time_chooser_get_date: + * @self: a #GcalDateTimeChooser + * + * Get the value of the date shown. + * The timezone, hours, minutes and seconds are discarded and replaced with UTC, 0, 0 and 0 respectively. + * + * Returns: (transfer full): the date of the chooser. + */ +GDateTime * +gcal_date_time_chooser_get_date (GcalDateTimeChooser *self) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + return g_date_time_new_utc (g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + 0, 0, 0); +} + +/** + * gcal_date_time_chooser_set_date: + * @self: a #GcalDateTimeChooser + * @date_time: a valid #GDateTime + * + * Set the value of the shown date. + * The timezone, hours, minutes and seconds are ignored. + */ +void +gcal_date_time_chooser_set_date (GcalDateTimeChooser *self, + GDateTime *date_time) +{ + g_autoptr (GDateTime) new_date_time = NULL; + + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + GCAL_ENTRY; + + new_date_time = g_date_time_new (g_date_time_get_timezone (self->date_time), + g_date_time_get_year (date_time), + g_date_time_get_month (date_time), + g_date_time_get_day_of_month (date_time), + g_date_time_get_hour (self->date_time), + g_date_time_get_minute (self->date_time), + 0); + + gcal_date_time_chooser_set_date_time (self, new_date_time); + + GCAL_EXIT; +} + +/** + * gcal_date_time_chooser_get_date_label: + * @self: a #GcalDateTimeChooser + * + * Get the value of the date row label + * + * Returns: (transfer none): the label of the date row. + */ +const gchar* +gcal_date_time_chooser_get_date_label (GcalDateTimeChooser *self) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + return adw_preferences_row_get_title (ADW_PREFERENCES_ROW (self->date_row)); +} + +/** + * gcal_date_time_chooser_set_date_label: + * @self: a #GcalDateTimeChooser + * @date_label: The label + * + * Set the value of the date row label + */ +void +gcal_date_time_chooser_set_date_label (GcalDateTimeChooser *self, + const gchar *date_label) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->date_row), date_label); +} + +/** + * gcal_date_time_chooser_get_time_label: + * @self: a #GcalDateTimeChooser + * + * Get the value of the time row label + * + * Returns: (transfer none): the label of the time row. + */ +const gchar* +gcal_date_time_chooser_get_time_label (GcalDateTimeChooser *self) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + return adw_preferences_row_get_title (ADW_PREFERENCES_ROW (self->time_row)); +} + +/** + * gcal_date_time_chooser_set_time_label: + * @self: a #GcalDateTimeChooser + * @time_label: The label + * + * Set the value of the time row label + */ +void +gcal_date_time_chooser_set_time_label (GcalDateTimeChooser *self, + const gchar *time_label) +{ + g_assert (GCAL_IS_DATE_TIME_CHOOSER (self)); + + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->time_row), time_label); +} diff --git a/src/gui/event-editor/gcal-date-time-chooser.h b/src/gui/event-editor/gcal-date-time-chooser.h new file mode 100644 index 0000000000000000000000000000000000000000..cb78bd9ff9004d116fce8584a6f1778ca1829ad1 --- /dev/null +++ b/src/gui/event-editor/gcal-date-time-chooser.h @@ -0,0 +1,56 @@ +/* gcal-date-time-chooser.h + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ + +#pragma once + +#include + +#include "gcal-enums.h" + +G_BEGIN_DECLS + +#define GCAL_TYPE_DATE_TIME_CHOOSER (gcal_date_time_chooser_get_type ()) + +G_DECLARE_FINAL_TYPE (GcalDateTimeChooser, gcal_date_time_chooser, GCAL, DATE_TIME_CHOOSER, AdwPreferencesGroup) + +GtkWidget* gcal_date_time_chooser_new (void); + +void gcal_date_time_chooser_set_time_format (GcalDateTimeChooser *chooser, + GcalTimeFormat time_format); + +GDateTime* gcal_date_time_chooser_get_date_time (GcalDateTimeChooser *chooser); + +void gcal_date_time_chooser_set_date_time (GcalDateTimeChooser *chooser, + GDateTime *date_time); + +GDateTime* gcal_date_time_chooser_get_date (GcalDateTimeChooser *chooser); + +void gcal_date_time_chooser_set_date (GcalDateTimeChooser *chooser, + GDateTime *date_time); + +const gchar* gcal_date_time_chooser_get_date_label (GcalDateTimeChooser *chooser); + +void gcal_date_time_chooser_set_date_label (GcalDateTimeChooser *chooser, + const gchar *date_label); + +const gchar* gcal_date_time_chooser_get_time_label (GcalDateTimeChooser *chooser); + +void gcal_date_time_chooser_set_time_label (GcalDateTimeChooser *chooser, + const gchar *time_label); + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-date-time-chooser.ui b/src/gui/event-editor/gcal-date-time-chooser.ui new file mode 100644 index 0000000000000000000000000000000000000000..2a69948c820ada6623207c4709d30755bcca34d4 --- /dev/null +++ b/src/gui/event-editor/gcal-date-time-chooser.ui @@ -0,0 +1,158 @@ + + + + + + 23 + 1 + 5 + + + + 59 + 1 + 10 + + + + diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index afabfc550ac0b291ca8368341c01fd2c2b660f37..d5f33498a0e70dfab3d8a5528b33e7f0f2f24255 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -23,29 +23,31 @@ #include "gcal-context.h" #include "gcal-date-selector.h" #include "gcal-date-chooser-row.h" +#include "gcal-date-time-chooser.h" #include "gcal-debug.h" #include "gcal-event.h" #include "gcal-event-editor-section.h" #include "gcal-recurrence.h" #include "gcal-schedule-section.h" -#include "gcal-time-chooser-row.h" #include "gcal-utils.h" #include struct _GcalScheduleSection { - GtkBox parent; - - AdwToggleGroup *schedule_type_toggle_group; - GcalDateChooserRow *start_date_row; - GcalTimeChooserRow *start_time_row; - GcalDateChooserRow *end_date_row; - GcalTimeChooserRow *end_time_row; - GtkWidget *number_of_occurrences_spin; - GtkWidget *repeat_combo; - GtkWidget *repeat_duration_combo; - GtkWidget *until_date_selector; + GtkBox parent; + + AdwToggleGroup *schedule_type_toggle_group; + AdwPreferencesGroup *start_date_group; + GcalDateChooserRow *start_date_row; + AdwPreferencesGroup *end_date_group; + GcalDateChooserRow *end_date_row; + GcalDateTimeChooser *start_date_time_chooser; + GcalDateTimeChooser *end_date_time_chooser; + GtkWidget *number_of_occurrences_spin; + GtkWidget *repeat_combo; + GtkWidget *repeat_duration_combo; + GtkWidget *until_date_selector; GcalContext *context; GcalEvent *event; @@ -65,6 +67,22 @@ enum N_PROPS }; +static void on_start_date_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self); + +static void on_end_date_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self); + +static void on_start_date_time_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self); + +static void on_end_date_time_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self); + /* * Auxiliary methods @@ -77,225 +95,188 @@ all_day_selected (GcalScheduleSection *self) return g_strcmp0 (active, "all-day") == 0; } + static void -find_best_timezones_for_event (GcalScheduleSection *self, - GTimeZone **out_tz_start, - GTimeZone **out_tz_end) +block_date_signals (GcalScheduleSection *self) { - gboolean was_all_day; - gboolean all_day; - gboolean new_event; + g_signal_handlers_block_by_func (self->start_date_row, on_start_date_changed_cb, self); + g_signal_handlers_block_by_func (self->end_date_row, on_end_date_changed_cb, self); - /* Update all day */ - all_day = all_day_selected (self); - was_all_day = gcal_event_get_all_day (self->event); - new_event = self->flags & GCAL_EVENT_EDITOR_FLAG_NEW_EVENT; + g_signal_handlers_block_by_func (self->start_date_time_chooser, on_start_date_time_changed_cb, self); + g_signal_handlers_block_by_func (self->end_date_time_chooser, on_end_date_time_changed_cb, self); +} - GCAL_TRACE_MSG ("Finding best timezone with all_day=%d, was_all_day=%d, event_is_new=%d", - all_day, - was_all_day, - new_event); +static void +unblock_date_signals (GcalScheduleSection *self) +{ + g_signal_handlers_unblock_by_func (self->end_date_time_chooser, on_end_date_time_changed_cb, self); + g_signal_handlers_unblock_by_func (self->start_date_time_chooser, on_start_date_time_changed_cb, self); - if (!new_event && was_all_day && !all_day) - { - GCAL_TRACE_MSG ("Using original event timezones"); + g_signal_handlers_unblock_by_func (self->end_date_row, on_end_date_changed_cb, self); + g_signal_handlers_unblock_by_func (self->start_date_row, on_start_date_changed_cb, self); +} - gcal_event_get_original_timezones (self->event, out_tz_start, out_tz_end); - return; - } +static void +remove_recurrence_properties (GcalEvent *event) +{ + ECalComponent *comp; - if (all_day) - { - GCAL_TRACE_MSG ("Using UTC timezones"); + comp = gcal_event_get_component (event); - if (out_tz_start) - *out_tz_start = g_time_zone_new_utc (); + e_cal_component_set_recurid (comp, NULL); + e_cal_component_set_rrules (comp, NULL); + e_cal_component_commit_sequence (comp); +} - if (out_tz_end) - *out_tz_end = g_time_zone_new_utc (); - } - else - { - g_autoptr (GTimeZone) tz_start = NULL; - g_autoptr (GTimeZone) tz_end = NULL; - if (new_event) - { - GCAL_TRACE_MSG ("Using the local timezone"); +/* + * Callbacks + */ - tz_start = g_time_zone_new_local (); - tz_end = g_time_zone_new_local (); - } - else - { - GCAL_TRACE_MSG ("Using the current timezones"); +static void +on_schedule_type_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self) +{ + gboolean all_day = all_day_selected (self); - tz_start = g_time_zone_ref (g_date_time_get_timezone (gcal_event_get_date_start (self->event))); - tz_end = g_time_zone_ref (g_date_time_get_timezone (gcal_event_get_date_end (self->event))); - } + gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), all_day); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), all_day); + gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), !all_day); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), !all_day); + + block_date_signals (self); - if (out_tz_start) - *out_tz_start = g_steal_pointer (&tz_start); + if (all_day) + { + g_autoptr (GDateTime) start_local = NULL; + g_autoptr (GDateTime) end_local = NULL; + + GDateTime *start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + GDateTime *end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - if (out_tz_end) - *out_tz_end = g_steal_pointer (&tz_end); + start_local = g_date_time_to_local (start); + end_local = g_date_time_to_local (end); + + gcal_date_chooser_row_set_date (self->start_date_row, start_local); + gcal_date_chooser_row_set_date (self->end_date_row, end_local); + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, start_local); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, end_local); } + + unblock_date_signals (self); } -static GDateTime* -return_datetime_for_widgets (GcalScheduleSection *self, - GTimeZone *timezone, - GcalDateChooserRow *date_chooser_row, - GcalTimeChooserRow *time_chooser_row) +static void +on_start_date_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self) { - g_autoptr (GDateTime) date_in_local_tz = NULL; - g_autoptr (GDateTime) date_in_best_tz = NULL; - GDateTime *date; - GDateTime *time; - GDateTime *retval; - gboolean all_day; + GDateTime *start, *end; - all_day = all_day_selected (self); - date = gcal_date_chooser_row_get_date (date_chooser_row); - time = gcal_time_chooser_row_get_time (time_chooser_row); + GCAL_ENTRY; - date_in_local_tz = g_date_time_new_local (g_date_time_get_year (date), - g_date_time_get_month (date), - g_date_time_get_day_of_month (date), - all_day ? 0 : g_date_time_get_hour (time), - all_day ? 0 : g_date_time_get_minute (time), - 0); + block_date_signals (self); - if (all_day) - date_in_best_tz = g_date_time_ref (date_in_local_tz); - else - date_in_best_tz = g_date_time_to_timezone (date_in_local_tz, timezone); - - retval = g_date_time_new (timezone, - g_date_time_get_year (date_in_best_tz), - g_date_time_get_month (date_in_best_tz), - g_date_time_get_day_of_month (date_in_best_tz), - all_day ? 0 : g_date_time_get_hour (date_in_best_tz), - all_day ? 0 : g_date_time_get_minute (date_in_best_tz), - 0); + start = gcal_date_chooser_row_get_date (self->start_date_row); + end = gcal_date_chooser_row_get_date (self->end_date_row); - return retval; -} + if (g_date_time_compare (start, end) == 1) + { + gcal_date_chooser_row_set_date (self->end_date_row, start); + gcal_date_time_chooser_set_date (self->end_date_time_chooser, start); + } -static GDateTime* -get_date_start (GcalScheduleSection *self) -{ - g_autoptr (GTimeZone) start_tz = NULL; + // Keep the date row and the date-time chooser in sync + gcal_date_time_chooser_set_date (self->start_date_time_chooser, start); - find_best_timezones_for_event (self, &start_tz, NULL); + unblock_date_signals (self); - return return_datetime_for_widgets (self, - start_tz, - self->start_date_row, - self->start_time_row); + GCAL_EXIT; } -static GDateTime* -get_date_end (GcalScheduleSection *self) +static void +on_end_date_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self) { - g_autoptr (GTimeZone) end_tz = NULL; + GDateTime *start, *end; - find_best_timezones_for_event (self, NULL, &end_tz); + GCAL_ENTRY; - return return_datetime_for_widgets (self, - end_tz, - self->end_date_row, - self->end_time_row); -} + block_date_signals (self); -static void -remove_recurrence_properties (GcalEvent *event) -{ - ECalComponent *comp; + start = gcal_date_chooser_row_get_date (self->start_date_row); + end = gcal_date_chooser_row_get_date (self->end_date_row); - comp = gcal_event_get_component (event); + if (g_date_time_compare (start, end) == 1) + { + gcal_date_chooser_row_set_date (self->start_date_row, end); + gcal_date_time_chooser_set_date (self->start_date_time_chooser, end); + } - e_cal_component_set_recurid (comp, NULL); - e_cal_component_set_rrules (comp, NULL); - e_cal_component_commit_sequence (comp); + // Keep the date row and the date-time chooser in sync + gcal_date_time_chooser_set_date (self->end_date_time_chooser, end); + + unblock_date_signals (self); + + GCAL_EXIT; } static void -sync_datetimes (GcalScheduleSection *self, - GParamSpec *pspec, - GtkWidget *widget) +on_start_date_time_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self) { - GDateTime *start, *end, *start_local, *end_local, *new_date; - GtkWidget *date_widget, *time_widget; - gboolean is_start, is_all_day; + GDateTime *start, *end; GCAL_ENTRY; - is_start = (widget == GTK_WIDGET (self->start_time_row) || widget == GTK_WIDGET (self->start_date_row)); - is_all_day = all_day_selected (self); - start = get_date_start (self); - end = get_date_end (self); - - /* The date is valid, no need to update the fields */ - if (g_date_time_compare (end, start) >= 0) - GCAL_GOTO (out); + block_date_signals (self); - start_local = g_date_time_to_local (start); - end_local = g_date_time_to_local (end); - - /* - * If the user is changing the start date or time, we change the end - * date or time (and vice versa). - */ - if (is_start) - { - new_date = is_all_day ? g_date_time_add_hours (start, 0) : g_date_time_add_hours (start_local, 1); + start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - date_widget = GTK_WIDGET (self->end_date_row); - time_widget = GTK_WIDGET (self->end_time_row); - } - else + if (g_date_time_compare (start, end) == 1) { - new_date = is_all_day ? g_date_time_add_hours (end, 0) : g_date_time_add_hours (end_local, -1); + GTimeZone *end_tz = g_date_time_get_timezone (end); + g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); + g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); - date_widget = GTK_WIDGET (self->start_date_row); - time_widget = GTK_WIDGET (self->start_time_row); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, new_end_in_end_tz); } - g_signal_handlers_block_by_func (date_widget, sync_datetimes, self); - g_signal_handlers_block_by_func (time_widget, sync_datetimes, self); - - gcal_date_chooser_row_set_date (GCAL_DATE_CHOOSER_ROW (date_widget), new_date); - gcal_time_chooser_row_set_time (GCAL_TIME_CHOOSER_ROW (time_widget), new_date); - - g_signal_handlers_unblock_by_func (date_widget, sync_datetimes, self); - g_signal_handlers_unblock_by_func (time_widget, sync_datetimes, self); - - g_clear_pointer (&start_local, g_date_time_unref); - g_clear_pointer (&end_local, g_date_time_unref); - g_clear_pointer (&new_date, g_date_time_unref); - -out: - g_clear_pointer (&start, g_date_time_unref); - g_clear_pointer (&end, g_date_time_unref); + unblock_date_signals (self); GCAL_EXIT; } - -/* - * Callbacks - */ - static void -on_schedule_type_changed_cb (GtkWidget *widget, +on_end_date_time_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - gboolean all_day = all_day_selected (self); + GDateTime *start, *end; + + GCAL_ENTRY; + + block_date_signals (self); + + start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); + + if (g_date_time_compare (start, end) == 1) + { + GTimeZone *start_tz = g_date_time_get_timezone (start); + g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); + g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); + + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, new_start_in_start_tz); + } + + unblock_date_signals (self); - gtk_widget_set_visible (GTK_WIDGET (self->start_time_row), !all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_time_row), !all_day); + GCAL_EXIT; } static void @@ -329,8 +310,8 @@ on_time_format_changed_cb (GcalScheduleSection *self) { GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - gcal_time_chooser_row_set_time_format (self->start_time_row, time_format); - gcal_time_chooser_row_set_time_format (self->end_time_row, time_format); + gcal_date_time_chooser_set_time_format (self->start_date_time_chooser, time_format); + gcal_date_time_chooser_set_time_format (self->end_date_time_chooser, time_format); } @@ -338,18 +319,33 @@ on_time_format_changed_cb (GcalScheduleSection *self) * GcalEventEditorSection interface */ +/* + * Sets the event of the schedule section. + * + * If the event is not a new event, the event date start and date end timezones are kept and displayed to the user. + * + * If the event is a new event, the event date start and date end timezones are forgotten. The results of + * g_date_time_get_year, g_date_time_get_month, g_date_time_get_day_of_month, g_date_time_get_hour and + * g_date_time_get_minute are used for the date row and the date time chooser. + * The date row timezone is set to UTC (as all day events use the UTC timezone). + * The date time chooser timezone is set to local. + */ static void gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalEvent *event, GcalEventEditorFlags flags) { - g_autoptr (GDateTime) date_start = NULL; - g_autoptr (GDateTime) date_end = NULL; + g_autoptr (GDateTime) date_time_start_utc = NULL; + g_autoptr (GDateTime) date_time_end_utc = NULL; + g_autoptr (GDateTime) date_time_start_local = NULL; + g_autoptr (GDateTime) date_time_end_local = NULL; + GcalScheduleSection *self; + GDateTime* date_time_start = NULL; + GDateTime* date_time_end = NULL; GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; - GcalScheduleSection *self; GcalRecurrence *recur; - gboolean all_day; + gboolean all_day, new_event; GCAL_ENTRY; @@ -362,44 +358,69 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GCAL_RETURN (); all_day = gcal_event_get_all_day (event); + new_event = flags & GCAL_EVENT_EDITOR_FLAG_NEW_EVENT; /* schedule type */ adw_toggle_group_set_active_name (self->schedule_type_toggle_group, all_day ? "all-day" : "time-slot"); - gtk_widget_set_visible (GTK_WIDGET (self->start_time_row), !all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_time_row), !all_day); - - /* retrieve start and end dates */ - date_start = gcal_event_get_date_start (event); - date_start = all_day ? g_date_time_ref (date_start) : g_date_time_to_local (date_start); + gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), all_day); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), all_day); + gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), !all_day); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), !all_day); - date_end = gcal_event_get_date_end (event); + /* retrieve start and end date-times */ + date_time_start = gcal_event_get_date_start (event); + date_time_end = gcal_event_get_date_end (event); /* * This is subtracting what has been added in action_button_clicked (). * See bug 769300. */ - date_end = all_day ? g_date_time_add_days (date_end, -1) : g_date_time_to_local (date_end); + date_time_end = all_day ? g_date_time_add_days (date_time_end, -1) : date_time_end; + + date_time_start_utc = g_date_time_new_utc (g_date_time_get_year (date_time_start), + g_date_time_get_month (date_time_start), + g_date_time_get_day_of_month (date_time_start), + 0, 0, 0); + + date_time_end_utc = g_date_time_new_utc (g_date_time_get_year (date_time_end), + g_date_time_get_month (date_time_end), + g_date_time_get_day_of_month (date_time_end), + 0, 0, 0); + + date_time_start_local = g_date_time_new_local (g_date_time_get_year (date_time_start), + g_date_time_get_month (date_time_start), + g_date_time_get_day_of_month (date_time_start), + g_date_time_get_hour (date_time_start), + g_date_time_get_minute (date_time_start), + 0); + + date_time_end_local = g_date_time_new_local (g_date_time_get_year (date_time_end), + g_date_time_get_month (date_time_end), + g_date_time_get_day_of_month (date_time_end), + g_date_time_get_hour (date_time_end), + g_date_time_get_minute (date_time_end), + 0); + + block_date_signals (self); /* date */ - g_signal_handlers_block_by_func (self->end_date_row, sync_datetimes, self); - g_signal_handlers_block_by_func (self->start_date_row, sync_datetimes, self); + gcal_date_chooser_row_set_date (self->start_date_row, date_time_start_utc); + gcal_date_chooser_row_set_date (self->end_date_row, date_time_end_utc); - gcal_date_chooser_row_set_date (self->start_date_row, date_start); - gcal_date_chooser_row_set_date (self->end_date_row, date_end); - - g_signal_handlers_unblock_by_func (self->start_date_row, sync_datetimes, self); - g_signal_handlers_unblock_by_func (self->end_date_row, sync_datetimes, self); - - /* time */ - g_signal_handlers_block_by_func (self->end_time_row, sync_datetimes, self); - g_signal_handlers_block_by_func (self->start_time_row, sync_datetimes, self); - - gcal_time_chooser_row_set_time (self->start_time_row, date_start); - gcal_time_chooser_row_set_time (self->end_time_row, date_end); + /* date-time */ + if (new_event || all_day) + { + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start_local); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end_local); + } + else + { + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); + } - g_signal_handlers_unblock_by_func (self->start_time_row, sync_datetimes, self); - g_signal_handlers_unblock_by_func (self->end_time_row, sync_datetimes, self); + unblock_date_signals (self); /* Recurrences */ recur = gcal_event_get_recurrence (event); @@ -440,29 +461,28 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, static void gcal_schedule_section_apply (GcalEventEditorSection *section) { - g_autoptr (GDateTime) start_date = NULL; - g_autoptr (GDateTime) end_date = NULL; + GDateTime *start_date, *end_date; GcalRecurrenceFrequency freq; GcalScheduleSection *self; GcalRecurrence *old_recur; - gboolean was_all_day; gboolean all_day; GCAL_ENTRY; self = GCAL_SCHEDULE_SECTION (section); - all_day = all_day_selected (self); - was_all_day = gcal_event_get_all_day (self->event); - if (!was_all_day && all_day) - gcal_event_save_original_timezones (self->event); + all_day = all_day_selected (self); - /* - * Update start & end dates. The dates are already translated to the current - * timezone (unless the event used to be all day, but no longer is). - */ - start_date = get_date_start (self); - end_date = get_date_end (self); + if (!all_day) + { + start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); + } + else + { + start_date = gcal_date_chooser_row_get_date (self->start_date_row); + end_date = gcal_date_chooser_row_get_date (self->end_date_row); + } #ifdef GCAL_ENABLE_TRACE { @@ -485,26 +505,8 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) { GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); - g_clear_pointer (&end_date, g_date_time_unref); end_date = fake_end_date; } - else if (!all_day && was_all_day) - { - /* When an all day event is changed to be not an all day event, we - * need to correct for the fact that the event's timezone was until - * now set to UTC. That means we need to change the timezone to - * localtime now, or else it will be saved incorrectly. - */ - GDateTime *localtime_date; - - localtime_date = g_date_time_to_local (start_date); - g_clear_pointer (&start_date, g_date_time_unref); - start_date = localtime_date; - - localtime_date = g_date_time_to_local (end_date); - g_clear_pointer (&end_date, g_date_time_unref); - end_date = localtime_date; - } gcal_event_set_date_start (self->event, start_date); gcal_event_set_date_end (self->event, end_date); @@ -548,9 +550,9 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) static gboolean gcal_schedule_section_changed (GcalEventEditorSection *section) { - g_autoptr (GDateTime) start_date = NULL; - g_autoptr (GDateTime) end_date = NULL; GcalScheduleSection *self; + GDateTime *start_date, *end_date, *prev_start_date, *prev_end_date; + GTimeZone *start_tz, *end_tz, *prev_start_tz, *prev_end_tz; gboolean was_all_day; gboolean all_day; @@ -564,38 +566,41 @@ gcal_schedule_section_changed (GcalEventEditorSection *section) if (all_day != was_all_day) GCAL_RETURN (TRUE); + if (!all_day) + { + start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); + } + else + { + start_date = gcal_date_chooser_row_get_date (self->start_date_row); + end_date = gcal_date_chooser_row_get_date (self->end_date_row); + } + prev_start_date = gcal_event_get_date_start (self->event); + prev_end_date = gcal_event_get_date_end (self->event); + + start_tz = g_date_time_get_timezone (start_date); + end_tz = g_date_time_get_timezone (end_date); + prev_start_tz = g_date_time_get_timezone (prev_start_date); + prev_end_tz = g_date_time_get_timezone (prev_end_date); + /* Start date */ - start_date = get_date_start (self); - if (!g_date_time_equal (start_date, gcal_event_get_date_start (self->event))) + if (!g_date_time_equal (start_date, prev_start_date)) + GCAL_RETURN (TRUE); + if (g_strcmp0 (g_time_zone_get_identifier (start_tz), g_time_zone_get_identifier (prev_start_tz)) != 0) GCAL_RETURN (TRUE); /* End date */ - end_date = get_date_end (self); if (all_day) { - g_autoptr (GDateTime) fake_end_date = g_date_time_add_days (end_date, 1); - gcal_set_date_time (&end_date, fake_end_date); - } - else if (!all_day && was_all_day) - { - /* When an all day event is changed to be not an all day event, we - * need to correct for the fact that the event's timezone was until - * now set to UTC. That means we need to change the timezone to - * localtime now, or else it will be saved incorrectly. - */ - GDateTime *localtime_date; - - localtime_date = g_date_time_to_local (start_date); - g_clear_pointer (&start_date, g_date_time_unref); - start_date = localtime_date; - - localtime_date = g_date_time_to_local (end_date); - g_clear_pointer (&end_date, g_date_time_unref); - end_date = localtime_date; + GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); + end_date = fake_end_date; } if (!g_date_time_equal (end_date, gcal_event_get_date_end (self->event))) GCAL_RETURN (TRUE); + if (g_strcmp0 (g_time_zone_get_identifier (end_tz), g_time_zone_get_identifier (prev_end_tz)) != 0) + GCAL_RETURN (TRUE); /* Recurrency */ GCAL_RETURN (gcal_schedule_section_recurrence_changed (self)); @@ -676,6 +681,8 @@ gcal_schedule_section_class_init (GcalScheduleSectionClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + g_type_ensure (GCAL_TYPE_DATE_TIME_CHOOSER); + object_class->finalize = gcal_schedule_section_finalize; object_class->get_property = gcal_schedule_section_get_property; object_class->set_property = gcal_schedule_section_set_property; @@ -685,19 +692,23 @@ gcal_schedule_section_class_init (GcalScheduleSectionClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-schedule-section.ui"); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, schedule_type_toggle_group); + gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, start_date_group); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, start_date_row); - gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, start_time_row); + gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, end_date_group); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, end_date_row); - gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, end_time_row); + gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, start_date_time_chooser); + gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, end_date_time_chooser); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, number_of_occurrences_spin); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, repeat_combo); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, repeat_duration_combo); gtk_widget_class_bind_template_child (widget_class, GcalScheduleSection, until_date_selector); - gtk_widget_class_bind_template_callback (widget_class, on_schedule_type_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_start_date_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_end_date_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_start_date_time_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_end_date_time_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_repeat_duration_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_repeat_type_changed_cb); - gtk_widget_class_bind_template_callback (widget_class, sync_datetimes); } static void @@ -705,7 +716,10 @@ gcal_schedule_section_init (GcalScheduleSection *self) { gtk_widget_init_template (GTK_WIDGET (self)); - on_time_format_changed_cb (self); + g_signal_connect (self->schedule_type_toggle_group, + "notify::active", + G_CALLBACK (on_schedule_type_changed_cb), + self); } gboolean @@ -734,15 +748,25 @@ gcal_schedule_section_recurrence_changed (GcalScheduleSection *self) gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self) { - g_autoptr (GDateTime) start_date = NULL; - g_autoptr (GDateTime) end_date = NULL; + GDateTime *start_date, *end_date; + gboolean all_day; g_return_val_if_fail (GCAL_IS_SCHEDULE_SECTION (self), FALSE); GCAL_ENTRY; - start_date = get_date_start (self); - end_date = get_date_end (self); + all_day = all_day_selected (self); + + if (all_day) + { + start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); + } + else + { + start_date = gcal_date_chooser_row_get_date (self->start_date_row); + end_date = gcal_date_chooser_row_get_date (self->end_date_row); + } GCAL_RETURN (gcal_date_time_compare_date (start_date, gcal_event_get_date_start (self->event)) < 0 || gcal_date_time_compare_date (end_date, gcal_event_get_date_end (self->event)) > 0); diff --git a/src/gui/event-editor/gcal-schedule-section.ui b/src/gui/event-editor/gcal-schedule-section.ui index dbc2fe1231e440ee89fc6b7715f8d7bcfc46b479..23e47e1eed4f12fe031eb36ef4a413732d7d6630 100644 --- a/src/gui/event-editor/gcal-schedule-section.ui +++ b/src/gui/event-editor/gcal-schedule-section.ui @@ -21,61 +21,46 @@ time-slot - + - - True - 12 - - + - - - - Start Date - - - - - - - Start Time - - - + + Start Date + - + - - True - 12 - - + - - - - End Date - - - - - - - End Time - - - + + End Date + + + + + + + Start Date + Start Time + + + + + + End Date + End Time + diff --git a/src/gui/event-editor/gcal-time-chooser-row.c b/src/gui/event-editor/gcal-time-chooser-row.c deleted file mode 100644 index 0612d058ec108745923fc0891bdcff951adddb01..0000000000000000000000000000000000000000 --- a/src/gui/event-editor/gcal-time-chooser-row.c +++ /dev/null @@ -1,399 +0,0 @@ -/* gcal-time-chooser-row.c - * - * Copyright (C) 2024 Titouan Real - * - * gnome-calendar 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. - * - * gnome-calendar 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 . - */ - -#define G_LOG_DOMAIN "GcalTimeChooserRow" - -#include "gcal-time-chooser-row.h" -#include "gcal-time-selector.h" - -#include -#include -#include -#include - -struct _GcalTimeChooserRow -{ - AdwEntryRow parent; - - /* widgets */ - GcalTimeSelector *time_selector; - - GcalTimeFormat time_format; -}; - -G_DEFINE_TYPE (GcalTimeChooserRow, gcal_time_chooser_row, ADW_TYPE_ENTRY_ROW); - -enum -{ - PROP_0, - PROP_TIME_FORMAT, - PROP_TIME, - N_PROPS -}; - -static GParamSpec* properties[N_PROPS] = { NULL, }; - - -/* - * Auxiliary methods - */ - -static void -update_entry (GcalTimeChooserRow *self) -{ - g_autofree gchar *label = NULL; - GDateTime *time; - - time = gcal_time_selector_get_time (self->time_selector); - - switch (self->time_format) - { - case GCAL_TIME_FORMAT_12H: - label = g_date_time_format (time, "%I:%M %p"); - break; - - case GCAL_TIME_FORMAT_24H: - label = g_date_time_format (time, "%R"); - break; - - default: - g_assert_not_reached (); - } - - gtk_editable_set_text (GTK_EDITABLE (self), label); -} - -static GRegex* -get_12h_regex (void) -{ - static GRegex *regex = NULL; - - if (g_once_init_enter_pointer (®ex)) - { - g_autoptr (GError) error = NULL; - g_autofree gchar *str = NULL; - - str = g_strdup_printf ("^(?[0-9]{1,2})([\\-:./_,'a-z](?[0-9]{1,2}))? ?(?%s|%s)", - _("AM"), _("PM")); - - regex = g_regex_new (str, G_REGEX_CASELESS, 0, &error); - - if (error) - g_error ("Failed to compile regex: %s. Aborting...", error->message); - } - - return regex; -} - -static GRegex* -get_24h_regex (void) -{ - static GRegex *regex = NULL; - - if (g_once_init_enter_pointer (®ex)) - { - g_autoptr (GError) error = NULL; - - regex = g_regex_new ("^(?[0-9]{1,2})([\\-:./_,'a-z](?[0-9]{1,2}))?", G_REGEX_CASELESS, 0, &error); - - if (error) - g_error ("Failed to compile regex: %s. Aborting...", error->message); - } - - return regex; -} - -static void -parse_time (GcalTimeChooserRow *self) -{ - g_autoptr (GDateTime) now = NULL; - const gchar *text; - - text = gtk_editable_get_text (GTK_EDITABLE (self)); - now = g_date_time_new_now_local (); - - switch (self->time_format) - { - case GCAL_TIME_FORMAT_12H: - { - g_autoptr (GMatchInfo) match_info = NULL; - g_autoptr (GDateTime) time = NULL; - g_autofree gchar *minute_str = NULL; - g_autofree gchar *hour_str = NULL; - g_autofree gchar *ampm_str = NULL; - gboolean am; - gint64 hour, minute; - - if (!g_regex_match (get_12h_regex (), text, 0, &match_info)) - { - update_entry (self); - break; - } - - hour_str = g_match_info_fetch_named (match_info, "hour"); - minute_str = g_match_info_fetch_named (match_info, "minute"); - ampm_str = g_match_info_fetch_named (match_info, "ampm"); - - hour = g_ascii_strtoll (hour_str, NULL, 10); - minute = g_ascii_strtoll (minute_str, NULL, 10); - am = (strcmp (g_ascii_strup (ampm_str, -1), _("AM")) == 0); - - /* Allow 12AM and 12PM */ - if ((hour > 11 || minute > 59) && !(hour == 12 && minute == 0)) - { - update_entry (self); - break; - } - - if (hour == 12) - { - g_assert (minute == 0); - hour = 0; - } - - if (!am) - hour += 12; - - now = g_date_time_new_now_local (); - time = g_date_time_new_local (g_date_time_get_year (now), - g_date_time_get_month (now), - g_date_time_get_day_of_month (now), - hour, minute, 0); - - gcal_time_chooser_row_set_time (self, time); - - } - break; - - case GCAL_TIME_FORMAT_24H: - { - g_autoptr (GMatchInfo) match_info = NULL; - g_autoptr (GDateTime) time = NULL; - g_autofree gchar *minute_str = NULL; - g_autofree gchar *hour_str = NULL; - g_autofree gchar *ampm_str = NULL; - gint64 hour, minute; - - if (!g_regex_match (get_24h_regex (), text, 0, &match_info)) - { - update_entry (self); - break; - } - - hour_str = g_match_info_fetch_named (match_info, "hour"); - minute_str = g_match_info_fetch_named (match_info, "minute"); - - hour = g_ascii_strtoll (hour_str, NULL, 10); - minute = g_ascii_strtoll (minute_str, NULL, 10); - - if (hour > 23 || minute > 59) - { - update_entry (self); - break; - } - - time = g_date_time_new_local (g_date_time_get_year (now), - g_date_time_get_month (now), - g_date_time_get_day_of_month (now), - hour, minute, 0); - - gcal_time_chooser_row_set_time (self, time); - - } - break; - - default: - g_assert_not_reached (); - } -} - - -/* - * Callbacks - */ - -static void -on_contains_focus_changed_cb (GtkEventControllerFocus *focus_controller, - GParamSpec *pspec, - GcalTimeChooserRow *self) -{ - parse_time (self); -} - -static void -on_time_selected_changed_cb (GcalTimeSelector *selector, - GParamSpec *pspec, - GcalTimeChooserRow *self) -{ - update_entry (self); - - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIME]); -} - -static void -on_time_popover_shown_cb (GtkPopover *popover, - GcalTimeChooserRow *self) -{ - parse_time (self); -} - - -/* - * GObject overrides - */ - -static void -gcal_time_chooser_row_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GcalTimeChooserRow *self = GCAL_TIME_CHOOSER_ROW (object); - - switch (prop_id) - { - case PROP_TIME_FORMAT: - g_value_set_enum (value, self->time_format); - break; - - case PROP_TIME: - g_value_set_boxed (value, gcal_time_chooser_row_get_time (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gcal_time_chooser_row_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GcalTimeChooserRow *self = GCAL_TIME_CHOOSER_ROW (object); - - switch (prop_id) - { - case PROP_TIME_FORMAT: - gcal_time_chooser_row_set_time_format (self, g_value_get_enum (value)); - break; - - case PROP_TIME: - gcal_time_chooser_row_set_time (self, g_value_get_boxed (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gcal_time_chooser_row_class_init (GcalTimeChooserRowClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->get_property = gcal_time_chooser_row_get_property; - object_class->set_property = gcal_time_chooser_row_set_property; - - properties[PROP_TIME_FORMAT] = g_param_spec_enum ("time-format", NULL, NULL, - GCAL_TYPE_TIME_FORMAT, - GCAL_TIME_FORMAT_24H, - G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); - - properties[PROP_TIME] = g_param_spec_boxed ("time", NULL, NULL, - G_TYPE_DATE_TIME, - G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-time-chooser-row.ui"); - - gtk_widget_class_bind_template_child (widget_class, GcalTimeChooserRow, time_selector); - - gtk_widget_class_bind_template_callback (widget_class, on_contains_focus_changed_cb); - gtk_widget_class_bind_template_callback (widget_class, on_time_selected_changed_cb); - gtk_widget_class_bind_template_callback (widget_class, on_time_popover_shown_cb); -} - -static void -gcal_time_chooser_row_init (GcalTimeChooserRow *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - self->time_format = GCAL_TIME_FORMAT_24H; -} - -GcalTimeFormat -gcal_time_chooser_row_get_time_format (GcalTimeChooserRow *self) -{ - g_return_val_if_fail (GCAL_IS_TIME_CHOOSER_ROW (self), 0); - - return self->time_format; -} - -void -gcal_time_chooser_row_set_time_format (GcalTimeChooserRow *self, - GcalTimeFormat time_format) -{ - g_return_if_fail (GCAL_IS_TIME_CHOOSER_ROW (self)); - - if (self->time_format == time_format) - return; - - self->time_format = time_format; - - gcal_time_selector_set_time_format (self->time_selector, time_format); - update_entry (self); -} - -/** - * gcal_time_chooser_row_set_time: - * @selector: a #GcalTimeChooserRow - * @date: a valid #GDateTime - * - * Set the value of the shown time. - */ -void -gcal_time_chooser_row_set_time (GcalTimeChooserRow *self, - GDateTime *time) -{ - g_return_if_fail (GCAL_IS_TIME_CHOOSER_ROW (self)); - - gcal_time_selector_set_time (self->time_selector, time); - update_entry (self); - - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIME]); -} - -/** - * gcal_time_chooser_row_get_time: - * @selector: a #GcalTimeChooserRow - * - * Get the value of the time shown - * - * Returns: (transfer none): the time of the selector. - */ -GDateTime* -gcal_time_chooser_row_get_time (GcalTimeChooserRow *self) -{ - g_return_val_if_fail (GCAL_IS_TIME_CHOOSER_ROW (self), NULL); - - return gcal_time_selector_get_time (self->time_selector); -} diff --git a/src/gui/event-editor/gcal-time-selector.c b/src/gui/event-editor/gcal-time-selector.c deleted file mode 100644 index 2b466db34a540abebbd9108b93d561db70513b2e..0000000000000000000000000000000000000000 --- a/src/gui/event-editor/gcal-time-selector.c +++ /dev/null @@ -1,295 +0,0 @@ -/* gcal-time-selector.c - * - * Copyright (C) 2015 Erick Pérez Castellanos - * 2014 Georges Basile Stavracas Neto - * - * gnome-calendar 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. - * - * gnome-calendar 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 . - */ - -#define G_LOG_DOMAIN "GcalTimeSelector" - -#include "gcal-date-time-utils.h" -#include "gcal-time-selector.h" - -#include - -struct _GcalTimeSelector -{ - GtkBox parent; - - GtkAdjustment *hour_adjustment; - GtkAdjustment *minute_adjustment; - - GtkWidget *time_box; - GtkWidget *hour_spin; - GtkWidget *minute_spin; - GtkWidget *period_toggle_group; - - GDateTime *time; - - GcalTimeFormat time_format; -}; - -enum -{ - PROP_0, - PROP_TIME, - LAST_PROP -}; - -G_DEFINE_TYPE (GcalTimeSelector, gcal_time_selector, GTK_TYPE_BOX); - -static void -update_time (GcalTimeSelector *selector) -{ - GDateTime *now, *new_time; - gint hour, minute; - - /* Retrieve current time */ - hour = (gint) gtk_adjustment_get_value (selector->hour_adjustment); - minute = (gint) gtk_adjustment_get_value (selector->minute_adjustment); - - if (selector->time_format == GCAL_TIME_FORMAT_12H) - { - hour = hour % 12; - - if (g_strcmp0 (adw_toggle_group_get_active_name (ADW_TOGGLE_GROUP (selector->period_toggle_group)), "pm") == 0) - hour += 12; - } - - now = g_date_time_new_now_local (); - new_time = g_date_time_new_local (g_date_time_get_year (now), - g_date_time_get_month (now), - g_date_time_get_day_of_month (now), - hour, minute, 0); - - /* Set the new time */ - gcal_time_selector_set_time (selector, new_time); - - g_clear_pointer (&new_time, g_date_time_unref); - g_clear_pointer (&now, g_date_time_unref); -} - -static void -on_period_toggle_changed_cb (GtkWidget *widget, - GParamSpec *pspec, - GcalTimeSelector *selector) -{ - update_time (selector); -} - -static gboolean -on_output (GtkWidget *widget, - GcalTimeSelector *selector) -{ - GtkAdjustment *adjustment; - gchar *text; - gint value; - - adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget)); - value = (gint) gtk_adjustment_get_value (adjustment); - text = g_strdup_printf ("%02d", value); - gtk_editable_set_text (GTK_EDITABLE (widget), text); - - g_free (text); - - return TRUE; -} - -static void -gcal_time_selector_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - GcalTimeSelector *self = (GcalTimeSelector*) object; - - switch (prop_id) - { - case PROP_TIME: - g_value_set_boxed (value, self->time); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -gcal_time_selector_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - GcalTimeSelector *self = (GcalTimeSelector*) object; - - switch (prop_id) - { - case PROP_TIME: - gcal_time_selector_set_time (self, g_value_get_boxed (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -void -gcal_time_selector_set_time_format (GcalTimeSelector *selector, - GcalTimeFormat time_format) -{ - g_return_if_fail (GCAL_IS_TIME_SELECTOR (selector)); - - selector->time_format = time_format; - gtk_widget_set_visible (selector->period_toggle_group, time_format == GCAL_TIME_FORMAT_12H); - - if (time_format == GCAL_TIME_FORMAT_24H) - { - gtk_adjustment_set_lower (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (selector->hour_spin)), 0.0); - gtk_adjustment_set_upper (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (selector->hour_spin)), 23.0); - } - else - { - gtk_adjustment_set_lower (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (selector->hour_spin)), 1.0); - gtk_adjustment_set_upper (gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (selector->hour_spin)), 12.0); - } -} - -static void -gcal_time_selector_finalize (GObject *object) -{ - GcalTimeSelector *self = GCAL_TIME_SELECTOR (object); - - g_clear_pointer (&self->time, g_date_time_unref); - G_OBJECT_CLASS (gcal_time_selector_parent_class)->finalize (object); -} - -static void -gcal_time_selector_dispose (GObject *object) -{ - G_OBJECT_CLASS (gcal_time_selector_parent_class)->dispose (object); -} - -static void -gcal_time_selector_class_init (GcalTimeSelectorClass *klass) -{ - GObjectClass *object_class; - - object_class = G_OBJECT_CLASS (klass); - object_class->dispose = gcal_time_selector_dispose; - object_class->finalize = gcal_time_selector_finalize; - object_class->get_property = gcal_time_selector_get_property; - object_class->set_property = gcal_time_selector_set_property; - - /** - * GcalTimeSelector::time: - * - * The current time of the selector. - */ - g_object_class_install_property (object_class, - PROP_TIME, - g_param_spec_boxed ("time", - "Time of the selector", - "The current time of the selector", - G_TYPE_DATE_TIME, - G_PARAM_READWRITE)); - - gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/calendar/ui/event-editor/gcal-time-selector.ui"); - - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, time_box); - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, hour_adjustment); - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, hour_spin); - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, minute_adjustment); - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, minute_spin); - gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (klass), GcalTimeSelector, period_toggle_group); - - gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), on_output); - gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), update_time); - gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), on_period_toggle_changed_cb); -} - -static void -gcal_time_selector_init (GcalTimeSelector *self) -{ - self->time = g_date_time_new_now_local (); - - gtk_widget_init_template (GTK_WIDGET (self)); - - if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) - gtk_widget_set_direction (self->time_box, GTK_TEXT_DIR_LTR); -} - -/* Public API */ -GtkWidget* -gcal_time_selector_new (void) -{ - return g_object_new (GCAL_TYPE_TIME_SELECTOR, NULL); -} - -void -gcal_time_selector_set_time (GcalTimeSelector *selector, - GDateTime *time) -{ - g_return_if_fail (GCAL_IS_TIME_SELECTOR (selector)); - - if (selector->time != time) - { - gint hour, minute; - - if (selector->time && - time && - g_date_time_get_hour (selector->time) == g_date_time_get_hour (time) && - g_date_time_get_minute (selector->time) == g_date_time_get_minute (time) && - g_date_time_get_second (selector->time) == g_date_time_get_second (time)) - { - return; - } - - gcal_set_date_time (&selector->time, time); - - /* Update the spinners */ - g_signal_handlers_block_by_func (selector->hour_adjustment, update_time, selector); - g_signal_handlers_block_by_func (selector->minute_adjustment, update_time, selector); - - hour = g_date_time_get_hour (time); - minute = g_date_time_get_minute (time); - - if (selector->time_format == GCAL_TIME_FORMAT_12H) - { - g_signal_handlers_block_by_func (selector->period_toggle_group, on_period_toggle_changed_cb, selector); - - adw_toggle_group_set_active_name (ADW_TOGGLE_GROUP (selector->period_toggle_group), hour >= 12 ? "pm" : "am"); - hour = hour % 12; - hour = (hour == 0)? 12 : hour; - - g_signal_handlers_unblock_by_func (selector->period_toggle_group, on_period_toggle_changed_cb, selector); - } - - gtk_adjustment_set_value (selector->hour_adjustment, hour); - gtk_adjustment_set_value (selector->minute_adjustment, minute); - - g_signal_handlers_unblock_by_func (selector->hour_adjustment, update_time, selector); - g_signal_handlers_unblock_by_func (selector->minute_adjustment, update_time, selector); - - g_object_notify (G_OBJECT (selector), "time"); - } -} - -GDateTime* -gcal_time_selector_get_time (GcalTimeSelector *selector) -{ - g_return_val_if_fail (GCAL_IS_TIME_SELECTOR (selector), NULL); - - return selector->time; -} diff --git a/src/gui/event-editor/gcal-time-selector.h b/src/gui/event-editor/gcal-time-selector.h deleted file mode 100644 index 84c2147c9f34860e1b42965045e9a6b02d4ad29f..0000000000000000000000000000000000000000 --- a/src/gui/event-editor/gcal-time-selector.h +++ /dev/null @@ -1,43 +0,0 @@ -/* gcal-time-selector.h - * - * Copyright (C) 2015 Erick Pérez Castellanos - * - * gnome-calendar 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. - * - * gnome-calendar 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 . - */ - -#ifndef __GCAL_TIME_SELECTOR_H__ -#define __GCAL_TIME_SELECTOR_H__ - -#include "gcal-enums.h" - -#include - -G_BEGIN_DECLS - -#define GCAL_TYPE_TIME_SELECTOR (gcal_time_selector_get_type ()) -G_DECLARE_FINAL_TYPE (GcalTimeSelector, gcal_time_selector, GCAL, TIME_SELECTOR, GtkBox) - -GtkWidget* gcal_time_selector_new (void); - -void gcal_time_selector_set_time_format (GcalTimeSelector *selector, - GcalTimeFormat time_format); - -GDateTime* gcal_time_selector_get_time (GcalTimeSelector *selector); - -void gcal_time_selector_set_time (GcalTimeSelector *selector, - GDateTime *time); - -G_END_DECLS - -#endif /* __GCAL_TIME_SELECTOR_H__ */ diff --git a/src/gui/event-editor/gcal-time-selector.ui b/src/gui/event-editor/gcal-time-selector.ui deleted file mode 100644 index 6aa5540a6b8ba1238fcbe619150864c047540ccb..0000000000000000000000000000000000000000 --- a/src/gui/event-editor/gcal-time-selector.ui +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 23 - 1 - 5 - - - - 59 - 1 - 10 - - - diff --git a/src/gui/event-editor/gcal-time-zone-dialog-row.c b/src/gui/event-editor/gcal-time-zone-dialog-row.c new file mode 100644 index 0000000000000000000000000000000000000000..2f0b00830bb1858849b85605e2d21012fa550503 --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog-row.c @@ -0,0 +1,89 @@ +/* gcal-time-zone-dialog-row.c + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ +#include "config.h" + +#define G_LOG_DOMAIN "GcalTimeZoneDialogRow" + +#include "gcal-time-zone-dialog-row.h" + +struct _GcalTimeZoneDialogRow +{ + AdwActionRow parent; + + GWeatherLocation *location; +}; + +G_DEFINE_TYPE (GcalTimeZoneDialogRow, gcal_time_zone_dialog_row, ADW_TYPE_ACTION_ROW) + + +/* + * Auxiliary methods + */ + + +/* + * Callbacks + */ + + +/* + * Gobject overrides + */ + +static void +gcal_time_zone_dialog_row_class_init (GcalTimeZoneDialogRowClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-time-zone-dialog-row.ui"); +} + +static void +gcal_time_zone_dialog_row_init (GcalTimeZoneDialogRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +/** + * gcal_time_zone_dialog_row_new: + * + * Creates a new #GcalTimeZoneDialogRow + * + * Returns: (transfer full): a #GcalTimeZoneDialogRow + */ +GtkWidget* +gcal_time_zone_dialog_row_new (GWeatherLocation *location) +{ + GtkWidget *widget = g_object_new (GCAL_TYPE_TIME_ZONE_DIALOG_ROW, NULL); + + (GCAL_TIME_ZONE_DIALOG_ROW (widget))->location = g_object_ref (location); + + return widget; +} + +/** + * gcal_time_zone_dialog_row_get_location: + * @self: a #GcalTimeZoneDialogRow + * + * Returns: (transfer none): a #GWeatherLocation + */ +GWeatherLocation* +gcal_time_zone_dialog_row_get_location (GcalTimeZoneDialogRow *self) +{ + return self->location; +} diff --git a/src/gui/event-editor/gcal-time-zone-dialog-row.h b/src/gui/event-editor/gcal-time-zone-dialog-row.h new file mode 100644 index 0000000000000000000000000000000000000000..262596b42d4a0870c3578bd9d3bbadf713f4881a --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog-row.h @@ -0,0 +1,34 @@ +/* gcal-time-zone-dialog-row.h + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ + +#pragma once + +#include + +#include "libgweather/gweather.h" + +G_BEGIN_DECLS + +#define GCAL_TYPE_TIME_ZONE_DIALOG_ROW (gcal_time_zone_dialog_row_get_type ()) +G_DECLARE_FINAL_TYPE (GcalTimeZoneDialogRow, gcal_time_zone_dialog_row, GCAL, TIME_ZONE_DIALOG_ROW, AdwActionRow); + +GtkWidget* gcal_time_zone_dialog_row_new (GWeatherLocation *location); + +GWeatherLocation* gcal_time_zone_dialog_row_get_location (GcalTimeZoneDialogRow *self); + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-time-zone-dialog-row.ui b/src/gui/event-editor/gcal-time-zone-dialog-row.ui new file mode 100644 index 0000000000000000000000000000000000000000..70fc7ccae39a5060572e24f6448906df9e558228 --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog-row.ui @@ -0,0 +1,9 @@ + + + + diff --git a/src/gui/event-editor/gcal-time-zone-dialog.c b/src/gui/event-editor/gcal-time-zone-dialog.c new file mode 100644 index 0000000000000000000000000000000000000000..0eb97069fc956f3de21c0a2b5eb09e7b10bf03f3 --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog.c @@ -0,0 +1,313 @@ +/* gcal-time-zone-dialog.c + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ +#include "config.h" + +#define G_LOG_DOMAIN "GcalTimeZoneDialog" + +#include "gcal-date-time-utils.h" +#include "gcal-debug.h" +#include "gcal-time-zone-dialog.h" +#include "gcal-time-zone-dialog-row.h" + +#include + +#define RESULT_COUNT_LIMIT 12 + +struct _GcalTimeZoneDialog +{ + AdwDialog parent; + + GtkStack *stack; + AdwStatusPage *empty_search; + GtkScrolledWindow *search_results; + GtkSearchEntry *location_entry; + GtkListBox *listbox; + + GListStore *locations; + GTimeZone *time_zone; + GDateTime *date_time; +}; + +G_DEFINE_TYPE (GcalTimeZoneDialog, gcal_time_zone_dialog, ADW_TYPE_DIALOG) + +enum +{ + TIME_ZONE_SELECTED, + N_SIGNALS, +}; + +static guint signals [N_SIGNALS] = { 0, }; + + +/* + * Auxiliary methods + */ + +static GtkWidget* +create_row (gpointer item, + gpointer user_data) +{ + GcalTimeZoneDialog *self = (GcalTimeZoneDialog *) user_data; + g_autoptr (GDateTime) date_time = NULL; + g_autofree gchar *country_name = NULL; + g_autofree gchar *utc_str = NULL; + g_autofree gchar *label = NULL; + GWeatherLocation *location; + AdwActionRow *row; + + g_assert (GCAL_IS_TIME_ZONE_DIALOG (self)); + g_assert (GWEATHER_IS_LOCATION (item)); + + location = GWEATHER_LOCATION (item); + + country_name = gweather_location_get_country_name (location); + if (country_name) + { + /* Translators: "%1$s" is the city / region name, and + * "%2$s" is the country name (already localized). + */ + label = g_strdup_printf (_("%1$s, %2$s"), + gweather_location_get_name (location), + country_name); + } + else + { + label = g_strdup (gweather_location_get_name (location)); + } + + + date_time = g_date_time_new (gweather_location_get_timezone (location), + g_date_time_get_year (self->date_time), + g_date_time_get_month (self->date_time), + g_date_time_get_day_of_month (self->date_time), + g_date_time_get_hour (self->date_time), + g_date_time_get_minute (self->date_time), + 0); + utc_str = gcal_date_time_format_utc_offset (date_time); + + row = ADW_ACTION_ROW (gcal_time_zone_dialog_row_new (location)); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), utc_str); + adw_action_row_set_subtitle (row, label); + + return GTK_WIDGET (row); +} + +static void +query_locations (GcalTimeZoneDialog *self, + GWeatherLocation *location, + gchar *search) +{ + gboolean contains_name; + GWeatherLocation *loc; + GTimeZone *timezone; + const gchar *name, *country_name; + + timezone = gweather_location_get_timezone (location); + + if (timezone) + { + name = gweather_location_get_name (location); + country_name = gweather_location_get_country_name (location); + + contains_name = name != NULL && g_strrstr (g_utf8_casefold (g_utf8_normalize (name, -1, G_NORMALIZE_ALL), -1), search) != NULL; + contains_name = contains_name | (country_name != NULL && g_strrstr (g_utf8_casefold (g_utf8_normalize (country_name, -1, G_NORMALIZE_ALL), -1), search) != NULL); + + if (g_list_model_get_n_items (G_LIST_MODEL (self->locations)) >= RESULT_COUNT_LIMIT) + return; + + switch (gweather_location_get_level (location)) + { + case GWEATHER_LOCATION_CITY: + if (contains_name) + g_list_store_append (self->locations, location); + return; + + case GWEATHER_LOCATION_NAMED_TIMEZONE: + if (contains_name) + g_list_store_append (self->locations, location); + return; + + default: + break; + } + } + + loc = gweather_location_next_child (location, NULL); + while (loc) + { + query_locations (self, loc, search); + if (g_list_model_get_n_items (G_LIST_MODEL (self->locations)) >= RESULT_COUNT_LIMIT) + return; + + loc = gweather_location_next_child (location, loc); + } +} + + +/* + * Callbacks + */ + +static void +on_search_changed (GtkSearchEntry *entry, + GcalTimeZoneDialog *self) +{ + gchar *temp, *search; + GWeatherLocation *world_location; + + g_list_store_remove_all (self->locations); + + if (g_strcmp0 (gtk_editable_get_text (GTK_EDITABLE (self->location_entry)), "") == 0) + { + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_search)); + return; + } + + temp = g_utf8_normalize (gtk_editable_get_text (GTK_EDITABLE (self->location_entry)), -1, G_NORMALIZE_ALL); + search = g_utf8_casefold (temp, -1); + g_free(temp); + + world_location = gweather_location_get_world (); + if (!world_location) + return; + + query_locations (self, world_location, search); + + if (g_list_model_get_n_items (G_LIST_MODEL (self->locations)) == 0) + { + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_search)); + return; + } + + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->search_results)); +} + +static void +on_stop_search_cb (GtkSearchEntry *entry, + GcalTimeZoneDialog *self) +{ + adw_dialog_close (ADW_DIALOG (self)); +} + +static void +on_row_activated_cb (GtkListBox *list_box, + GtkListBoxRow *list_box_row, + GcalTimeZoneDialog *self) +{ + GcalTimeZoneDialogRow *row; + GWeatherLocation *location; + GTimeZone *time_zone; + + GCAL_ENTRY; + + row = GCAL_TIME_ZONE_DIALOG_ROW (list_box_row); + + location = gcal_time_zone_dialog_row_get_location (row); + time_zone = gweather_location_get_timezone (location); + + // The dialog only shows locations that have a timezone. + g_assert (time_zone != NULL); + + self->time_zone = g_time_zone_new_identifier (g_time_zone_get_identifier (time_zone)); + + g_signal_emit (self, signals[TIME_ZONE_SELECTED], 0); + + adw_dialog_close (ADW_DIALOG (self)); + + GCAL_EXIT; +} + + +/* + * Gobject overrides + */ + +static void +gcal_time_zone_dialog_class_init (GcalTimeZoneDialogClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + signals[TIME_ZONE_SELECTED] = g_signal_new ("timezone-selected", + GCAL_TYPE_TIME_ZONE_DIALOG, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, + 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/event-editor/gcal-time-zone-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalTimeZoneDialog, stack); + gtk_widget_class_bind_template_child (widget_class, GcalTimeZoneDialog, empty_search); + gtk_widget_class_bind_template_child (widget_class, GcalTimeZoneDialog, search_results); + gtk_widget_class_bind_template_child (widget_class, GcalTimeZoneDialog, location_entry); + gtk_widget_class_bind_template_child (widget_class, GcalTimeZoneDialog, listbox); + + gtk_widget_class_bind_template_callback (widget_class, on_search_changed); + gtk_widget_class_bind_template_callback (widget_class, on_stop_search_cb); + gtk_widget_class_bind_template_callback (widget_class, on_row_activated_cb); +} + +static void +gcal_time_zone_dialog_init (GcalTimeZoneDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_search_entry_set_key_capture_widget (self->location_entry, GTK_WIDGET (self)); + + self->locations = g_list_store_new (GWEATHER_TYPE_LOCATION); + + gtk_list_box_bind_model (self->listbox, + G_LIST_MODEL (self->locations), + create_row, + self, + NULL); +} + +/** + * gcal_time_zone_dialog_new: + * @date_time: a #GDateTime + * + * Creates a new #GcalTimeZoneDialog + * + * Returns: (transfer full): a #GcalTimeZoneDialog + */ +GtkWidget* +gcal_time_zone_dialog_new (GDateTime *date_time) +{ + GcalTimeZoneDialog *dialog = g_object_new (GCAL_TYPE_TIME_ZONE_DIALOG, NULL); + + dialog->date_time = g_date_time_ref (date_time); + + return GTK_WIDGET (dialog); +} + +/** + * gcal_time_zone_dialog_get_time_zone: + * @self: a #GcalTimeZoneDialog + * + * Get the value of the selected timezone + * + * Returns: (transfer none): the timezone selected in the dialog. + */ +GTimeZone* +gcal_time_zone_dialog_get_time_zone (GcalTimeZoneDialog *self) +{ + return self->time_zone; +} diff --git a/src/gui/event-editor/gcal-time-zone-dialog.h b/src/gui/event-editor/gcal-time-zone-dialog.h new file mode 100644 index 0000000000000000000000000000000000000000..276a0468029a5a23343d67e4a6c0212825209858 --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog.h @@ -0,0 +1,34 @@ +/* gcal-time-zone-dialog.h + * + * Copyright (C) 2024 Titouan Real + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ + +#pragma once + +#include + +#include "libgweather/gweather.h" + +G_BEGIN_DECLS + +#define GCAL_TYPE_TIME_ZONE_DIALOG (gcal_time_zone_dialog_get_type ()) +G_DECLARE_FINAL_TYPE (GcalTimeZoneDialog, gcal_time_zone_dialog, GCAL, TIME_ZONE_DIALOG, AdwDialog); + +GtkWidget* gcal_time_zone_dialog_new (GDateTime *date_time); + +GTimeZone* gcal_time_zone_dialog_get_time_zone (GcalTimeZoneDialog *self); + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-time-zone-dialog.ui b/src/gui/event-editor/gcal-time-zone-dialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..677538b1b87c6d0707162a442c3dc8fab3a11b9f --- /dev/null +++ b/src/gui/event-editor/gcal-time-zone-dialog.ui @@ -0,0 +1,60 @@ + + + + diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index dcc40a13dc72b96487ee8d4ed6df1bd5c618139c..7e7abb2d9fcb797fe637e834ad8cc8f9870778f2 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -11,6 +11,7 @@ sources += files( 'gcal-date-chooser.c', 'gcal-date-chooser-day.c', 'gcal-date-chooser-row.c', + 'gcal-date-time-chooser.c', 'gcal-date-selector.c', 'gcal-event-editor-dialog.c', 'gcal-event-editor-section.c', @@ -19,6 +20,6 @@ sources += files( 'gcal-reminders-section.c', 'gcal-schedule-section.c', 'gcal-summary-section.c', - 'gcal-time-chooser-row.c', - 'gcal-time-selector.c', + 'gcal-time-zone-dialog.c', + 'gcal-time-zone-dialog-row.c', ) diff --git a/src/gui/gcal-window.c b/src/gui/gcal-window.c index 2f88560b136c008272e5da3f396a79d08ab19dc5..f6446470c10125b2aaaf775910fae44a04cb6500 100644 --- a/src/gui/gcal-window.c +++ b/src/gui/gcal-window.c @@ -490,11 +490,10 @@ on_window_new_event_cb (GSimpleAction *action, GcalEvent *event; self = GCAL_WINDOW (user_data); - start = g_date_time_new (gcal_context_get_timezone (self->context), - g_date_time_get_year (self->active_date), - g_date_time_get_month (self->active_date), - g_date_time_get_day_of_month (self->active_date), - 0, 0, 0); + start = g_date_time_new_utc (g_date_time_get_year (self->active_date), + g_date_time_get_month (self->active_date), + g_date_time_get_day_of_month (self->active_date), + 0, 0, 0); end = g_date_time_add_days (start, 1); manager = gcal_context_get_manager (self->context); diff --git a/src/gui/icons/check-plain-symbolic.svg b/src/gui/icons/check-plain-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..096db07804da8ceefc25883013801347f6c18072 --- /dev/null +++ b/src/gui/icons/check-plain-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/gui/icons/globe-symbolic.svg b/src/gui/icons/globe-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..40184d655c7cf0cdb1a2f7b686bfa72c630a9299 --- /dev/null +++ b/src/gui/icons/globe-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/gui/icons/icons.gresource.xml b/src/gui/icons/icons.gresource.xml index 2fe45bb1549ffd95fe7d77fc6ac0bb0ad5e3ae55..4ec18af5719e4ca0a85f513d102a55549b1a7907 100644 --- a/src/gui/icons/icons.gresource.xml +++ b/src/gui/icons/icons.gresource.xml @@ -5,11 +5,14 @@ calendar-month-symbolic.svg calendar-week-symbolic.svg calendar-today-symbolic.svg + check-plain-symbolic.svg clock-alt-symbolic.svg checkmark-small-symbolic.svg external-link-symbolic.svg edit-symbolic.svg eye-not-looking-symbolic.svg + globe-symbolic.svg info-outline-symbolic.svg + loupe-large-symbolic.svg diff --git a/src/gui/icons/loupe-large-symbolic.svg b/src/gui/icons/loupe-large-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..8472dea75b0cd4a796698ffa89568f850110d6af --- /dev/null +++ b/src/gui/icons/loupe-large-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/gui/views/gcal-week-view.c b/src/gui/views/gcal-week-view.c index 4eb14a8a22584971af1e7b7e9d683a659fc29d76..4752cf8b2f56073b5a1b2d131c2cfe5928d7a156 100644 --- a/src/gui/views/gcal-week-view.c +++ b/src/gui/views/gcal-week-view.c @@ -178,7 +178,7 @@ begin_zoom (GcalWeekView *self, center = gtk_adjustment_get_value (vadjustment) + view_center_y - gtk_adjustment_get_lower (vadjustment); height = gtk_adjustment_get_upper (vadjustment) - gtk_adjustment_get_lower (vadjustment); - self->gesture_zoom_center = center / height; + self->gesture_zoom_center = height != 0.f ? center / height : 1.0; self->initial_zoom_level = self->zoom_level; } diff --git a/src/utils/gcal-date-time-utils.c b/src/utils/gcal-date-time-utils.c index df3ebad4e4ee09e548af67bc334fbcb31f7d91fb..e9fec042388f0b60787cba817c20c7408e75937b 100644 --- a/src/utils/gcal-date-time-utils.c +++ b/src/utils/gcal-date-time-utils.c @@ -299,3 +299,48 @@ gcal_date_time_add_floating_minutes (GDateTime *initial, offset = offset / 1000000; return g_date_time_add_seconds (result, offset); } + +/** + * gcal_date_time_add_floating_minutes: + * @date_time: #GDateTime for which to calculate the UTC difference + * + * Creates a string "UTC+-n" where n is the offset to UTC of date_time. + * + * Returns: (transfer full): A new string. + */ +gchar* +gcal_date_time_format_utc_offset (GDateTime *date_time) +{ + g_autoptr (GTimeZone) local_time_zone = NULL; + g_autoptr (GString) utc_str = NULL; + gint64 utc_offset_seconds; + + utc_str = g_string_new ("UTC"); + utc_offset_seconds = g_date_time_get_utc_offset (date_time) / (1000 * 1000); + + if (utc_offset_seconds != 0) + { + gboolean negative; + gint64 minutes; + gint64 hours; + + negative = utc_offset_seconds < 0; + + if (negative) + utc_offset_seconds = -utc_offset_seconds; + + hours = utc_offset_seconds / 3600; + minutes = (utc_offset_seconds - hours*3600) / 60; + + g_string_append (utc_str, negative ? "-" : "+"); + g_string_append_printf (utc_str, "%ld", hours); + + if (minutes != 0) + { + g_string_append (utc_str, ":"); + g_string_append_printf (utc_str, "%02ld", minutes); + } + } + + return g_string_free_and_steal (g_steal_pointer (&utc_str)); +} diff --git a/src/utils/gcal-date-time-utils.h b/src/utils/gcal-date-time-utils.h index 635338f8a1235770fce6d2cfc18672dab745624f..c61230d6e1a37a5987ac29bac09ffd0787226726 100644 --- a/src/utils/gcal-date-time-utils.h +++ b/src/utils/gcal-date-time-utils.h @@ -57,4 +57,7 @@ ICalTimezone* gcal_timezone_to_icaltimezone (GTimeZone GDateTime* gcal_date_time_add_floating_minutes (GDateTime *initial, gint minutes); +gchar* gcal_date_time_format_utc_offset (GDateTime *date_time); + G_END_DECLS + diff --git a/tests/test-event.c b/tests/test-event.c index 4df7c15213242aadaa499966fd83b42467901510..78a3ec3bb10fddbd58c5c106a07c00e3e7897579 100644 --- a/tests/test-event.c +++ b/tests/test-event.c @@ -334,55 +334,6 @@ event_date_create_tzid (void) /*********************************************************************************************************************/ -static void -event_date_edit_tzid (void) -{ - struct { - const gchar *string; - const gchar *tz; - } events[] = { - { - EVENT_STRING_FOR_DATE (";TZID=Europe/Madrid:20170818T130000", ";TZID=Europe/Madrid:20170818T140000"), - "Europe/Madrid" - }, - { - // https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/170 - EVENT_STRING_FOR_DATE (";TZID=Europe/London:20170818T130000", ";TZID=Europe/London:20170818T140000"), - "Europe/London" - }, - { - EVENT_STRING_FOR_DATE (";TZID=America/Costa_Rica:20170818T130000", ";TZID=America/Costa_Rica:20170818T140000"), - "America/Costa_Rica" - }, - }; - - g_test_bug ("170"); - - for (size_t i = 0; i < G_N_ELEMENTS (events); i++) - { - g_autoptr (GDateTime) datetime = NULL; - g_autoptr (GTimeZone) timezone = NULL; - g_autoptr (GcalEvent) event = NULL; - g_autoptr (GError) error = NULL; - - ECalComponentDateTime *dt_end = NULL; - - event = create_event_for_string (events[i].string, &error); - g_assert_no_error (error); - - // modify the event and check the tz in the result - timezone = g_time_zone_new_identifier (events[i].tz); - datetime = g_date_time_new (timezone, 2017, 8, 18, 15, 00, 00.); - gcal_event_set_date_end (event, datetime); - - dt_end = e_cal_component_get_dtend (gcal_event_get_component (event)); - g_assert_cmpstr (e_cal_component_datetime_get_tzid (dt_end), ==, "UTC"); - e_cal_component_datetime_free (dt_end); - } -} - -/*********************************************************************************************************************/ - static void event_date_check_tz (void) { @@ -456,7 +407,6 @@ main (gint argc, g_test_add_func ("/event/date/end", event_date_end); g_test_add_func ("/event/date/singleday", event_date_singleday); g_test_add_func ("/event/date/multiday", event_date_multiday); - g_test_add_func ("/event/date/edit-tzid", event_date_edit_tzid); g_test_add_func ("/event/date/create-tzid", event_date_create_tzid); g_test_add_func ("/event/date/check-tz", event_date_check_tz);