Files
federated-calendar/src/gui/event-editor/gcal-event-editor-dialog.c
T
Hari Rana ddb54c8c1c event-editor: Improve event name validation handling
- Fix a bug where clicking the "Add Event" button created an empty title in the event editor dialog.
- Add "error" class when the user writes an invalid event name.
- Add new function to validate event name.

Because the sections are split into files, the event editor will need a signal to check that each section is correct, and each section will have to emit the signal. This will need to be done in a future commit.
2024-02-25 08:55:17 -05:00

804 lines
25 KiB
C

/* gcal-ediat-dialog.c
*
* Copyright (C) 2015 Erick Pérez Castellanos <erickpc@gnome.org>
*
* 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/>.
*/
#include "config.h"
#define G_LOG_DOMAIN "GcalEventEditorDialog"
#include "gcal-alarm-row.h"
#include "gcal-context.h"
#include "gcal-debug.h"
#include "gcal-event-editor-dialog.h"
#include "gcal-event-editor-section.h"
#include "gcal-utils.h"
#include "gcal-event.h"
#include "gcal-notes-section.h"
#include "gcal-reminders-section.h"
#include "gcal-schedule-section.h"
#include "gcal-summary-section.h"
#include <libecal/libecal.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
/**
* SECTION:gcal-event-editor-dialog
* @short_description: Event editor dialog
* @title:GcalEventEditorDialog
* @image:gcal-event-editor-dialog.png
*
* #GcalEventEditorDialog is the event editor dialog of GNOME Calendar. It
* allows the user to change the various aspects of the events, as
* well as managing alarms.
*/
struct _GcalEventEditorDialog
{
AdwDialog parent;
GtkWidget *cancel_button;
GtkWidget *delete_button;
GtkWidget *done_button;
GtkWidget *lock;
GcalEventEditorSection *notes_section;
GcalEventEditorSection *reminders_section;
GcalEventEditorSection *schedule_section;
GtkWidget *sources_button;
GtkPopoverMenu *sources_popover;
GtkWidget *source_image;
GtkWidget *source_label;
GtkWidget *subtitle_label;
GcalEventEditorSection *summary_section;
GtkWidget *titlebar;
GtkWidget *title_label;
GcalEventEditorSection *sections[4];
GMenu *sources_menu;
GSimpleActionGroup *action_group;
GcalContext *context;
GcalEvent *event;
GcalCalendar *selected_calendar;
GBinding *event_title_binding;
/* flags */
gboolean event_is_new;
gboolean recurrence_changed;
gboolean writable;
};
G_DEFINE_TYPE (GcalEventEditorDialog, gcal_event_editor_dialog, ADW_TYPE_DIALOG)
enum
{
PROP_0,
PROP_CONTEXT,
PROP_EVENT,
PROP_WRITABLE,
N_PROPS
};
enum
{
REMOVE_EVENT,
NUM_SIGNALS,
};
static guint signals[NUM_SIGNALS] = { 0, };
static GParamSpec* properties[N_PROPS] = { NULL, };
/*
* Auxiliary methods
*/
static gint
sources_menu_sort_func (gconstpointer a,
gconstpointer b)
{
GcalCalendar *calendar1, *calendar2;
calendar1 = GCAL_CALENDAR ((gpointer) a);
calendar2 = GCAL_CALENDAR ((gpointer) b);
return g_ascii_strcasecmp (gcal_calendar_get_name (calendar1),
gcal_calendar_get_name (calendar2));
}
static void
fill_sources_menu (GcalEventEditorDialog *self)
{
g_autoptr (GList) list = NULL;
GcalManager *manager;
GList *aux;
if (self->context == NULL)
return;
manager = gcal_context_get_manager (self->context);
list = gcal_manager_get_calendars (manager);
self->sources_menu = g_menu_new ();
list = g_list_sort (list, sources_menu_sort_func);
for (aux = list; aux != NULL; aux = aux->next)
{
g_autoptr (GdkPaintable) paintable = NULL;
g_autoptr (GMenuItem) item = NULL;
g_autoptr (GdkPixbuf) pix = NULL;
g_autoptr (GVariant) target = NULL;
GcalCalendar *calendar;
const GdkRGBA *color;
calendar = GCAL_CALENDAR (aux->data);
/* retrieve color */
color = gcal_calendar_get_color (calendar);
paintable = get_circle_paintable_from_color (color, 16);
pix = paintable_to_pixbuf (paintable);
/* menu item */
item = g_menu_item_new (gcal_calendar_get_name (calendar), "select-calendar");
g_menu_item_set_icon (item, G_ICON (pix));
/* set insensitive for read-only calendars */
if (!gcal_calendar_is_read_only (calendar))
target = g_variant_new_string (gcal_calendar_get_id (calendar));
g_menu_item_set_action_and_target_value (item,
"event-editor-dialog.select-calendar",
g_steal_pointer (&target));
g_menu_append_item (self->sources_menu, item);
}
gtk_popover_menu_set_menu_model (self->sources_popover,
G_MENU_MODEL (self->sources_menu));
/* HACK: show the popover menu icons */
fix_popover_menu_icons (GTK_POPOVER (self->sources_popover));
}
static void
set_writable (GcalEventEditorDialog *self,
gboolean writable)
{
if (self->writable == writable)
return;
gtk_button_set_label (GTK_BUTTON (self->done_button), writable ? _("Save") : _("Done"));
self->writable = writable;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WRITABLE]);
}
static void
clear_and_hide_dialog (GcalEventEditorDialog *self)
{
gcal_event_editor_dialog_set_event (self, NULL, FALSE);
adw_dialog_close (ADW_DIALOG (self));
}
static void
apply_event_properties_to_template_event (GcalEvent *template_event,
GcalEvent *event)
{
g_autoptr (GDateTime) start_date = NULL;
g_autoptr (GDateTime) end_date = NULL;
GDateTime *template_start_date;
GDateTime *event_start_date;
GDateTime *template_end_date;
GDateTime *event_end_date;
gboolean was_all_day;
gboolean is_all_day;
is_all_day = gcal_event_get_all_day (event);
was_all_day = gcal_event_get_all_day (template_event);
template_start_date = gcal_event_get_date_start (template_event);
template_end_date = gcal_event_get_date_end (template_event);
event_start_date = gcal_event_get_date_start (event);
event_end_date = gcal_event_get_date_end (event);
start_date = g_date_time_new (g_date_time_get_timezone (event_start_date),
g_date_time_get_year (template_start_date),
g_date_time_get_month (template_start_date),
g_date_time_get_day_of_month (template_start_date),
g_date_time_get_hour (event_start_date),
g_date_time_get_minute (event_start_date),
g_date_time_get_second (event_start_date));
end_date = g_date_time_new (g_date_time_get_timezone (event_end_date),
g_date_time_get_year (template_end_date),
g_date_time_get_month (template_end_date),
g_date_time_get_day_of_month (template_end_date),
g_date_time_get_hour (event_end_date),
g_date_time_get_minute (event_end_date),
g_date_time_get_second (event_end_date));
if (was_all_day != is_all_day)
{
g_autoptr (GDateTime) fake_end_date = NULL;
if (is_all_day)
fake_end_date = g_date_time_add_days (end_date, -1);
else
fake_end_date = g_date_time_add_days (end_date, 1);
gcal_set_date_time (&end_date, fake_end_date);
}
gcal_event_set_summary (template_event, gcal_event_get_summary (event));
gcal_event_set_location (template_event, gcal_event_get_location (event));
gcal_event_set_description (template_event, gcal_event_get_description (event));
gcal_event_set_recurrence (template_event, gcal_event_get_recurrence (event));
gcal_event_set_all_day (template_event, gcal_event_get_all_day (event));
gcal_event_set_date_start (template_event, start_date);
gcal_event_set_date_end (template_event, end_date);
}
/*
* Callbacks
*/
static void
on_calendar_selected_action_cb (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
g_autoptr (GList) list = NULL;
GcalEventEditorDialog *self;
GcalManager *manager;
GList *aux;
const gchar *uid;
GCAL_ENTRY;
self = GCAL_EVENT_EDITOR_DIALOG (user_data);
manager = gcal_context_get_manager (self->context);
list = gcal_manager_get_calendars (manager);
/* retrieve selected calendar uid */
g_variant_get (value, "&s", &uid);
/* search for any source with the given UID */
for (aux = list; aux != NULL; aux = aux->next)
{
GcalCalendar *calendar = GCAL_CALENDAR (aux->data);
if (g_strcmp0 (gcal_calendar_get_id (calendar), uid) == 0)
{
g_autoptr (GdkPaintable) paintable = NULL;
const GdkRGBA *color;
/* retrieve color */
color = gcal_calendar_get_color (calendar);
paintable = get_circle_paintable_from_color (color, 16);
gtk_image_set_from_paintable (GTK_IMAGE (self->source_image), paintable);
self->selected_calendar = calendar;
gtk_label_set_label (GTK_LABEL (self->subtitle_label), gcal_calendar_get_name (calendar));
break;
}
}
GCAL_EXIT;
}
static void
on_cancel_button_clicked_cb (GtkButton *button,
GcalEventEditorDialog *self)
{
GCAL_ENTRY;
clear_and_hide_dialog (self);
GCAL_EXIT;
}
static void
on_ask_recurrence_response_delete_cb (GcalEvent *event,
GcalRecurrenceModType mod_type,
gpointer user_data)
{
GcalEventEditorDialog *self = GCAL_EVENT_EDITOR_DIALOG (user_data);
if (mod_type == GCAL_RECURRENCE_MOD_NONE)
return;
g_signal_emit (self, signals[REMOVE_EVENT], 0, event, mod_type);
clear_and_hide_dialog (self);
}
static void
on_delete_button_clicked_cb (GtkButton *button,
GcalEventEditorDialog *self)
{
GcalRecurrenceModType mod = GCAL_RECURRENCE_MOD_THIS_ONLY;
GCAL_ENTRY;
if (gcal_event_has_recurrence (self->event))
{
gcal_utils_ask_recurrence_modification_type (GTK_WIDGET (self),
self->event,
TRUE,
on_ask_recurrence_response_delete_cb,
self);
}
else
{
g_signal_emit (self, signals[REMOVE_EVENT], 0, self->event, mod);
clear_and_hide_dialog (self);
}
GCAL_EXIT;
}
static void
on_ask_recurrence_response_save_cb (GcalEvent *event,
GcalRecurrenceModType mod_type,
gpointer user_data)
{
GcalEventEditorDialog *self = GCAL_EVENT_EDITOR_DIALOG (user_data);
GcalManager *manager;
GCAL_ENTRY;
manager = gcal_context_get_manager (self->context);
switch (mod_type)
{
case GCAL_RECURRENCE_MOD_NONE:
GCAL_RETURN ();
case GCAL_RECURRENCE_MOD_ALL:
{
g_autoptr (GcalEvent) template_event = NULL;
g_autoptr (GError) error = NULL;
ECalComponentId *component_id;
ICalComponent *template_icomponent;
ECalComponent *template_ecomponent;
ECalComponent *component;
GcalCalendar *calendar;
ECalClient *client;
calendar = gcal_event_get_calendar (event);
client = gcal_calendar_get_client (calendar);
component = gcal_event_get_component (event);
component_id = e_cal_component_get_id (component);
e_cal_client_get_object_sync (client,
e_cal_component_id_get_uid (component_id),
NULL,
&template_icomponent,
NULL,
&error);
g_clear_pointer (&component_id, e_cal_component_id_free);
if (error)
{
g_warning ("Error updating event: %s", error->message);
break;
}
template_ecomponent = e_cal_component_new_from_icalcomponent (template_icomponent);
template_event = gcal_event_new (calendar, template_ecomponent, &error);
if (error)
{
g_warning ("Error updating event: %s", error->message);
break;
}
apply_event_properties_to_template_event (template_event, event);
gcal_manager_update_event (manager, template_event, mod_type);
}
break;
case GCAL_RECURRENCE_MOD_THIS_AND_FUTURE:
case GCAL_RECURRENCE_MOD_THIS_ONLY:
gcal_manager_update_event (manager, self->event, mod_type);
break;
}
clear_and_hide_dialog (self);
GCAL_EXIT;
}
static void
on_done_button_clicked_cb (GtkButton *button,
GcalEventEditorDialog *self)
{
GcalCalendar *selected_calendar;
GcalCalendar *calendar;
GcalManager *manager;
gboolean can_show_mod_all;
gboolean was_recurrent;
gboolean calendar_changed;
gint i;
manager = gcal_context_get_manager (self->context);
calendar = gcal_event_get_calendar (self->event);
if (gcal_calendar_is_read_only (calendar))
GCAL_GOTO (out);
selected_calendar = g_steal_pointer (&self->selected_calendar);
calendar_changed = selected_calendar && calendar != selected_calendar;
can_show_mod_all = TRUE;
if (!self->event_is_new)
{
gboolean anything_changed = calendar_changed;
for (i = 0; i < G_N_ELEMENTS (self->sections); i++)
{
gboolean section_changed;
section_changed = gcal_event_editor_section_changed (self->sections[i]);
anything_changed |= section_changed;
}
if (!anything_changed)
goto out;
can_show_mod_all =
!gcal_schedule_section_recurrence_changed (GCAL_SCHEDULE_SECTION (self->schedule_section)) &&
!gcal_schedule_section_day_changed (GCAL_SCHEDULE_SECTION (self->schedule_section));
}
/*
* We don't want to ask the recurrence mod type if the event wasn't
* actually recurrent.
*/
was_recurrent = gcal_event_has_recurrence (self->event);
for (i = 0; i < G_N_ELEMENTS (self->sections); i++)
{
if (gcal_event_editor_section_changed (self->sections[i]))
gcal_event_editor_section_apply (self->sections[i]);
}
if (calendar_changed)
{
if (self->event_is_new)
{
gcal_event_set_calendar (self->event, selected_calendar);
}
else
{
ESource *source = gcal_calendar_get_source (selected_calendar);
gcal_manager_move_event_to_source (manager, self->event, source);
goto out;
}
}
if (self->event_is_new)
{
gcal_manager_create_event (manager, self->event);
}
else if (was_recurrent && gcal_event_has_recurrence (self->event))
{
gcal_utils_ask_recurrence_modification_type (GTK_WIDGET (self),
self->event,
can_show_mod_all,
on_ask_recurrence_response_save_cb,
self);
return;
}
else
{
gcal_manager_update_event (manager, self->event, GCAL_RECURRENCE_MOD_THIS_ONLY);
}
out:
clear_and_hide_dialog (self);
}
/*
* Gobject overrides
*/
static void
gcal_event_editor_dialog_finalize (GObject *object)
{
GcalEventEditorDialog *self;
GCAL_ENTRY;
self = GCAL_EVENT_EDITOR_DIALOG (object);
g_clear_object (&self->action_group);
g_clear_object (&self->context);
g_clear_object (&self->event);
G_OBJECT_CLASS (gcal_event_editor_dialog_parent_class)->finalize (object);
GCAL_EXIT;
}
static void
gcal_event_editor_dialog_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GcalEventEditorDialog *self = GCAL_EVENT_EDITOR_DIALOG (object);
switch (prop_id)
{
case PROP_EVENT:
g_value_set_object (value, self->event);
break;
case PROP_CONTEXT:
g_value_set_object (value, self->context);
break;
case PROP_WRITABLE:
g_value_set_boolean (value, self->writable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_event_editor_dialog_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GcalEventEditorDialog *self = GCAL_EVENT_EDITOR_DIALOG (object);
switch (prop_id)
{
case PROP_CONTEXT:
g_assert (self->context == NULL);
self->context = g_value_dup_object (value);
g_object_notify_by_pspec (object, properties[PROP_CONTEXT]);
break;
case PROP_WRITABLE:
set_writable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_event_editor_dialog_class_init (GcalEventEditorDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
g_type_ensure (GCAL_TYPE_NOTES_SECTION);
g_type_ensure (GCAL_TYPE_REMINDERS_SECTION);
g_type_ensure (GCAL_TYPE_SCHEDULE_SECTION);
g_type_ensure (GCAL_TYPE_SUMMARY_SECTION);
object_class->finalize = gcal_event_editor_dialog_finalize;
object_class->get_property = gcal_event_editor_dialog_get_property;
object_class->set_property = gcal_event_editor_dialog_set_property;
signals[REMOVE_EVENT] = g_signal_new ("remove-event",
GCAL_TYPE_EVENT_EDITOR_DIALOG,
G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
2,
GCAL_TYPE_EVENT,
G_TYPE_INT);
/**
* GcalEventEditorDialog::event:
*
* The #GcalEvent being edited.
*/
properties[PROP_EVENT] = g_param_spec_object ("event",
"event of the dialog",
"The event being edited",
GCAL_TYPE_EVENT,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalEventEditorDialog::manager:
*
* The #GcalManager of the dialog.
*/
properties[PROP_CONTEXT] = g_param_spec_object ("context",
"Context of the dialog",
"The context of the dialog",
GCAL_TYPE_CONTEXT,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* GcalEventEditorDialog::writable:
*
* Whether the current event can be edited or not.
*/
properties[PROP_WRITABLE] = g_param_spec_boolean ("writable",
"Whether the current event can be edited",
"Whether the current event can be edited or not",
FALSE,
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-event-editor-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, cancel_button);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, delete_button);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, done_button);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, lock);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, notes_section);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, reminders_section);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, schedule_section);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, sources_button);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, sources_popover);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, source_image);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, subtitle_label);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, summary_section);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, titlebar);
gtk_widget_class_bind_template_child (widget_class, GcalEventEditorDialog, title_label);
/* callbacks */
gtk_widget_class_bind_template_callback (widget_class, on_cancel_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, on_delete_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, on_done_button_clicked_cb);
}
static void
gcal_event_editor_dialog_init (GcalEventEditorDialog *self)
{
const GActionEntry action_entries[] = {
{ "select-calendar", on_calendar_selected_action_cb, "s" },
};
gint i = 0;
/* Actions */
self->action_group = g_simple_action_group_new ();
g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
action_entries,
G_N_ELEMENTS (action_entries),
self);
gtk_widget_insert_action_group (GTK_WIDGET (self),
"event-editor-dialog",
G_ACTION_GROUP (self->action_group));
gtk_widget_init_template (GTK_WIDGET (self));
self->sections[i++] = self->notes_section;
self->sections[i++] = self->reminders_section;
self->sections[i++] = self->schedule_section;
self->sections[i++] = self->summary_section;
for (i = 0; i < G_N_ELEMENTS (self->sections); i++)
g_object_bind_property (self, "context", self->sections[i], "context", G_BINDING_DEFAULT);
}
/**
* gcal_event_editor_dialog_new:
*
* Creates a new #GcalEventEditorDialog
*
* Returns: (transfer full): a #GcalEventEditorDialog
*/
GtkWidget*
gcal_event_editor_dialog_new (void)
{
return g_object_new (GCAL_TYPE_EVENT_EDITOR_DIALOG, NULL);
}
/**
* gcal_event_editor_dialog_set_event:
* @dialog: a #GcalDialog
* @event: (nullable): a #GcalEvent
*
* Sets the event of the @dialog. When @event is
* %NULL, the current event information is unset.
*/
void
gcal_event_editor_dialog_set_event (GcalEventEditorDialog *self,
GcalEvent *event,
gboolean new_event)
{
g_autoptr (GdkPaintable) paintable = NULL;
g_autoptr (GcalEvent) cloned_event = NULL;
GcalEventEditorFlags flags;
GcalCalendar *calendar;
gint i;
GCAL_ENTRY;
g_return_if_fail (GCAL_IS_EVENT_EDITOR_DIALOG (self));
g_clear_object (&self->event);
/* If we just set the event to NULL, simply send a property notify */
if (!event)
GCAL_GOTO (out);
cloned_event = gcal_event_new_from_event (event);
self->event = g_object_ref (cloned_event);
calendar = gcal_event_get_calendar (cloned_event);
/* update sources list */
if (self->sources_menu != NULL)
g_menu_remove_all (self->sources_menu);
fill_sources_menu (self);
/* dialog titlebar's title & subtitle */
paintable = get_circle_paintable_from_color (gcal_event_get_color (cloned_event), 10);
gtk_image_set_from_paintable (GTK_IMAGE (self->source_image), paintable);
g_clear_pointer (&self->event_title_binding, g_binding_unbind);
self->event_title_binding = g_object_bind_property (cloned_event,
"summary",
self->title_label,
"label",
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
g_object_add_weak_pointer (G_OBJECT (self->event_title_binding),
(gpointer*) &self->event_title_binding);
/* change title when event name is empty */
if (!gcal_is_valid_event_name (gtk_label_get_label (GTK_LABEL (self->title_label))))
gtk_label_set_label (GTK_LABEL (self->title_label), _("New Event"));
gtk_label_set_label (GTK_LABEL (self->subtitle_label), gcal_calendar_get_name (calendar));
self->selected_calendar = calendar;
/* recurrence_changed */
self->recurrence_changed = FALSE;
set_writable (self, !gcal_calendar_is_read_only (calendar));
self->event_is_new = new_event;
gtk_widget_set_visible (self->delete_button, !new_event);
out:
flags = GCAL_EVENT_EDITOR_FLAG_NONE;
if (new_event)
flags |= GCAL_EVENT_EDITOR_FLAG_NEW_EVENT;
for (i = 0; i < G_N_ELEMENTS (self->sections); i++)
gcal_event_editor_section_set_event (self->sections[i], cloned_event, flags);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EVENT]);
GCAL_EXIT;
}