From 42f891ff42ccae4b598a570875e74f6fa717ff9c Mon Sep 17 00:00:00 2001 From: Titouan Real Date: Mon, 16 Dec 2024 22:34:48 +0100 Subject: [PATCH 1/2] gui/event-editor: Add time zone selection Creates a chooser managing a GDateTime. The chooser allows the user to change the date, the time and the timezone independently. This replaces the precedent GcalTimeChooserRow widget, because it is easier to manage the different interactions with the user in a single widget. Co-authored-by: Georges Basile Stavracas Neto --- po/POTFILES.in | 2 + src/core/gcal-event.c | 2 +- .../event-editor/event-editor.gresource.xml | 5 +- src/gui/event-editor/gcal-date-chooser-row.c | 9 +- src/gui/event-editor/gcal-date-time-chooser.c | 829 ++++++++++++++++++ src/gui/event-editor/gcal-date-time-chooser.h | 56 ++ .../event-editor/gcal-date-time-chooser.ui | 158 ++++ src/gui/event-editor/gcal-schedule-section.c | 564 ++++++------ src/gui/event-editor/gcal-schedule-section.ui | 65 +- src/gui/event-editor/gcal-time-chooser-row.c | 399 --------- src/gui/event-editor/gcal-time-selector.c | 295 ------- src/gui/event-editor/gcal-time-selector.h | 43 - src/gui/event-editor/gcal-time-selector.ui | 74 -- .../event-editor/gcal-time-zone-dialog-row.c | 89 ++ .../event-editor/gcal-time-zone-dialog-row.h | 34 + .../event-editor/gcal-time-zone-dialog-row.ui | 9 + src/gui/event-editor/gcal-time-zone-dialog.c | 313 +++++++ src/gui/event-editor/gcal-time-zone-dialog.h | 34 + src/gui/event-editor/gcal-time-zone-dialog.ui | 60 ++ src/gui/event-editor/meson.build | 5 +- src/gui/gcal-window.c | 9 +- src/gui/icons/check-plain-symbolic.svg | 2 + src/gui/icons/globe-symbolic.svg | 2 + src/gui/icons/icons.gresource.xml | 3 + src/gui/icons/loupe-large-symbolic.svg | 2 + src/utils/gcal-date-time-utils.c | 45 + src/utils/gcal-date-time-utils.h | 3 + tests/test-event.c | 50 -- 28 files changed, 1979 insertions(+), 1182 deletions(-) create mode 100644 src/gui/event-editor/gcal-date-time-chooser.c create mode 100644 src/gui/event-editor/gcal-date-time-chooser.h create mode 100644 src/gui/event-editor/gcal-date-time-chooser.ui delete mode 100644 src/gui/event-editor/gcal-time-chooser-row.c delete mode 100644 src/gui/event-editor/gcal-time-selector.c delete mode 100644 src/gui/event-editor/gcal-time-selector.h delete mode 100644 src/gui/event-editor/gcal-time-selector.ui create mode 100644 src/gui/event-editor/gcal-time-zone-dialog-row.c create mode 100644 src/gui/event-editor/gcal-time-zone-dialog-row.h create mode 100644 src/gui/event-editor/gcal-time-zone-dialog-row.ui create mode 100644 src/gui/event-editor/gcal-time-zone-dialog.c create mode 100644 src/gui/event-editor/gcal-time-zone-dialog.h create mode 100644 src/gui/event-editor/gcal-time-zone-dialog.ui create mode 100644 src/gui/icons/check-plain-symbolic.svg create mode 100644 src/gui/icons/globe-symbolic.svg create mode 100644 src/gui/icons/loupe-large-symbolic.svg diff --git a/po/POTFILES.in b/po/POTFILES.in index 6245cf142..dfdec3a9d 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 09c90b4ea..7f675123c 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 196b62db4..8ff039a73 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 301a07fe2..b64952eb4 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 000000000..344cb5447 --- /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 000000000..cb78bd9ff --- /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 000000000..2a69948c8 --- /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 afabfc550..d5f33498a 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 dbc2fe123..23e47e1ee 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 0612d058e..000000000 --- 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 2b466db34..000000000 --- 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 84c2147c9..000000000 --- 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 6aa5540a6..000000000 --- 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 000000000..2f0b00830 --- /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 000000000..262596b42 --- /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 000000000..70fc7ccae --- /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 000000000..0eb97069f --- /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 000000000..276a04680 --- /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 000000000..677538b1b --- /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 dcc40a13d..7e7abb2d9 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 2f88560b1..f6446470c 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 000000000..096db0780 --- /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 000000000..40184d655 --- /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 2fe45bb15..4ec18af57 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 000000000..8472dea75 --- /dev/null +++ b/src/gui/icons/loupe-large-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/utils/gcal-date-time-utils.c b/src/utils/gcal-date-time-utils.c index df3ebad4e..e9fec0423 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 635338f8a..c61230d6e 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 4df7c1521..78a3ec3bb 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); -- GitLab From 905a813f58f1368ed0b3653590e98ab8fe10bfe0 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 31 Jan 2025 19:38:27 -0300 Subject: [PATCH 2/2] views/week: Protect against 0-height scrollview Closes https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/1352 --- src/gui/views/gcal-week-view.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/views/gcal-week-view.c b/src/gui/views/gcal-week-view.c index 4eb14a8a2..4752cf8b2 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; } -- GitLab