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.
This commit is contained in:
Titouan Real
2024-12-16 15:54:32 -03:00
parent b6c383fa90
commit 569233f948
8 changed files with 489 additions and 1 deletions
+1 -1
View File
@@ -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')
@@ -11,6 +11,7 @@
<file compressed="true">gcal-reminders-section.ui</file>
<file compressed="true">gcal-schedule-section.ui</file>
<file compressed="true">gcal-summary-section.ui</file>
<file compressed="true">gcal-time-chooser-row.ui</file>
<file compressed="true">gcal-time-selector.ui</file>
</gresource>
</gresources>
@@ -0,0 +1,399 @@
/* gcal-time-chooser-row.c
*
* Copyright (C) 2024 Titouan Real <titouan.real@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "GcalTimeChooserRow"
#include "gcal-time-chooser-row.h"
#include "gcal-time-selector.h"
#include <locale.h>
#include <langinfo.h>
#include <stdlib.h>
#include <glib/gi18n.h>
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 (&regex))
{
g_autoptr (GError) error = NULL;
g_autofree gchar *str = NULL;
str = g_strdup_printf ("^(?<hour>[0-9]{1,2})([\\-:./_,'a-z](?<minute>[0-9]{1,2}))? ?(?<ampm>%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 (&regex))
{
g_autoptr (GError) error = NULL;
regex = g_regex_new ("^(?<hour>[0-9]{1,2})([\\-:./_,'a-z](?<minute>[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);
}
@@ -0,0 +1,39 @@
/* gcal-time-chooser-row.h
*
* Copyright (C) 2024 Titouan Real <titouan.real@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "gcal-enums.h"
#include <adwaita.h>
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
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GcalTimeChooserRow" parent="AdwEntryRow">
<child>
<object class="GtkEventControllerFocus">
<signal name="notify::contains-focus" handler="on_contains_focus_changed_cb" object="GcalTimeChooserRow" swapped="no" />
</object>
</child>
<child>
<object class="GtkMenuButton">
<property name="icon-name">clock-alt-symbolic</property>
<property name="valign">center</property>
<property name="tooltip-text">Edit Time</property>
<property name="popover">
<object class="GtkPopover">
<property name="position">bottom</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<child>
<object class="GcalTimeSelector" id="time_selector">
<signal name="notify::time" handler="on_time_selected_changed_cb" object="GcalTimeChooserRow" swapped="no" />
</object>
</child>
<signal name="show" handler="on_time_popover_shown_cb" object="GcalTimeChooserRow" swapped="no" />
</object>
</property>
<accessibility>
<property name="label" translatable="yes">Edit time</property>
</accessibility>
<style>
<class name="flat" />
</style>
</object>
</child>
</template>
</interface>
+1
View File
@@ -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',
)
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 0 c -4.40625 0 -8 3.59375 -8 8 s 3.59375 8 8 8 s 8 -3.59375 8 -8 s -3.59375 -8 -8 -8 z m 0 2 c 3.324219 0 6 2.675781 6 6 s -2.675781 6 -6 6 s -6 -2.675781 -6 -6 s 2.675781 -6 6 -6 z m 0 0"/><path d="m 4.929688 4.953125 c -0.128907 0.003906 -0.257813 0.058594 -0.351563 0.152344 c -0.191406 0.195312 -0.1875 0.511719 0.007813 0.707031 l 3.113281 3.042969 c 0.105469 0.097656 0.246093 0.144531 0.386719 0.128906 h 2.914062 c 0.277344 0 0.5 -0.222656 0.5 -0.5 c 0 -0.273437 -0.222656 -0.5 -0.5 -0.5 h -2.761719 l -2.953125 -2.886719 c -0.09375 -0.09375 -0.222656 -0.144531 -0.355468 -0.144531 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 768 B

+1
View File
@@ -5,6 +5,7 @@
<file compressed="true">calendar-month-symbolic.svg</file>
<file compressed="true">calendar-week-symbolic.svg</file>
<file compressed="true">calendar-today-symbolic.svg</file>
<file compressed="true">clock-alt-symbolic.svg</file>
<file compressed="true">external-link-symbolic.svg</file>
<file compressed="true">edit-symbolic.svg</file>
<file compressed="true">eye-not-looking-symbolic.svg</file>