From 569233f9484cb842e95ada1ef44f745a0745ed30 Mon Sep 17 00:00:00 2001 From: Titouan Real Date: Tue, 5 Nov 2024 21:55:34 +0100 Subject: [PATCH] event-editor: Create GcalTimeChooserRow Create a new GcalTimeChooserRow widget from AdwEntryRow that parses the entry into a time, or lets the user pick a time from a popover. [feaneron] Improved code style, reworked regex management. --- meson.build | 2 +- .../event-editor/event-editor.gresource.xml | 1 + src/gui/event-editor/gcal-time-chooser-row.c | 399 ++++++++++++++++++ src/gui/event-editor/gcal-time-chooser-row.h | 39 ++ src/gui/event-editor/gcal-time-chooser-row.ui | 45 ++ src/gui/event-editor/meson.build | 1 + src/gui/icons/clock-alt-symbolic.svg | 2 + src/gui/icons/icons.gresource.xml | 1 + 8 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 src/gui/event-editor/gcal-time-chooser-row.c create mode 100644 src/gui/event-editor/gcal-time-chooser-row.h create mode 100644 src/gui/event-editor/gcal-time-chooser-row.ui create mode 100644 src/gui/icons/clock-alt-symbolic.svg diff --git a/meson.build b/meson.build index 089e2006..74406900 100644 --- a/meson.build +++ b/meson.build @@ -164,7 +164,7 @@ libedataserverui_dep = dependency('libedataserverui4-1.0', version: '>= 3.45.1') libedataserver_dep = dependency('libedataserver-1.2', version: '>= 3.45.1') libecal_dep = dependency('libecal-2.0', version: '>= 3.45.1') libsoup_dep = dependency('libsoup-3.0') -libadwaita_dep = dependency('libadwaita-1', version: '>= 1.6.alpha') +libadwaita_dep = dependency('libadwaita-1', version: '>= 1.7.alpha') glib_dep = dependency('glib-2.0', version: '>= 2.67.5') gtk_dep = dependency('gtk4', version: '>= 4.15.2') gio_dep = dependency('gio-2.0', version: '>= 2.58.0') diff --git a/src/gui/event-editor/event-editor.gresource.xml b/src/gui/event-editor/event-editor.gresource.xml index 38165c51..f5491901 100644 --- a/src/gui/event-editor/event-editor.gresource.xml +++ b/src/gui/event-editor/event-editor.gresource.xml @@ -11,6 +11,7 @@ gcal-reminders-section.ui gcal-schedule-section.ui gcal-summary-section.ui + gcal-time-chooser-row.ui gcal-time-selector.ui diff --git a/src/gui/event-editor/gcal-time-chooser-row.c b/src/gui/event-editor/gcal-time-chooser-row.c new file mode 100644 index 00000000..0612d058 --- /dev/null +++ b/src/gui/event-editor/gcal-time-chooser-row.c @@ -0,0 +1,399 @@ +/* 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-chooser-row.h b/src/gui/event-editor/gcal-time-chooser-row.h new file mode 100644 index 00000000..4fc01b08 --- /dev/null +++ b/src/gui/event-editor/gcal-time-chooser-row.h @@ -0,0 +1,39 @@ +/* gcal-time-chooser-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 "gcal-enums.h" + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_TIME_CHOOSER_ROW (gcal_time_chooser_row_get_type ()) + +G_DECLARE_FINAL_TYPE (GcalTimeChooserRow, gcal_time_chooser_row, GCAL, TIME_CHOOSER_ROW, AdwEntryRow) + +GcalTimeFormat gcal_time_chooser_row_get_time_format (GcalTimeChooserRow *self); +void gcal_time_chooser_row_set_time_format (GcalTimeChooserRow *self, + GcalTimeFormat time_format); + +GDateTime* gcal_time_chooser_row_get_time (GcalTimeChooserRow *self); +void gcal_time_chooser_row_set_time (GcalTimeChooserRow *self, + GDateTime *date); + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-time-chooser-row.ui b/src/gui/event-editor/gcal-time-chooser-row.ui new file mode 100644 index 00000000..0c2e15eb --- /dev/null +++ b/src/gui/event-editor/gcal-time-chooser-row.ui @@ -0,0 +1,45 @@ + + + + diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index 1c9707ff..0b498ae4 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -18,5 +18,6 @@ sources += files( 'gcal-reminders-section.c', 'gcal-schedule-section.c', 'gcal-summary-section.c', + 'gcal-time-chooser-row.c', 'gcal-time-selector.c', ) diff --git a/src/gui/icons/clock-alt-symbolic.svg b/src/gui/icons/clock-alt-symbolic.svg new file mode 100644 index 00000000..b4b5066b --- /dev/null +++ b/src/gui/icons/clock-alt-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/gui/icons/icons.gresource.xml b/src/gui/icons/icons.gresource.xml index e586223d..b77b922a 100644 --- a/src/gui/icons/icons.gresource.xml +++ b/src/gui/icons/icons.gresource.xml @@ -5,6 +5,7 @@ calendar-month-symbolic.svg calendar-week-symbolic.svg calendar-today-symbolic.svg + clock-alt-symbolic.svg external-link-symbolic.svg edit-symbolic.svg eye-not-looking-symbolic.svg