Files
federated-calendar/src/gcal-quick-add-popover.c
T
Georges Basile Stavracas Neto 2544773645 manager: use EDS selectable property to track disabled agendas
This way, we can keep GNOME Calendar and Evolution synchronized.
And we can save some code too.
2017-03-17 21:25:23 -03:00

845 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* gcal-quick-add-popover.c
*
* Copyright (C) 2016 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program 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.
*
* This program 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 "GcalQuickAddPopover"
#include <glib/gi18n.h>
#include "gcal-quick-add-popover.h"
#include "gcal-utils.h"
struct _GcalQuickAddPopover
{
GtkPopover parent;
GtkWidget *add_button;
GtkWidget *calendars_listbox;
GtkWidget *calendar_name_label;
GtkWidget *color_image;
GtkWidget *stack;
GtkWidget *summary_entry;
GtkWidget *title_label;
/* Internal data */
GDateTime *date_start;
GDateTime *date_end;
GtkWidget *selected_row;
gboolean clock_format_24h;
GcalManager *manager;
};
G_DEFINE_TYPE (GcalQuickAddPopover, gcal_quick_add_popover, GTK_TYPE_POPOVER)
enum {
PROP_0,
PROP_DATE_START,
PROP_DATE_END,
PROP_MANAGER,
N_PROPS
};
enum
{
EDIT_EVENT,
NUM_SIGNALS
};
static guint signals[NUM_SIGNALS] = { 0, };
/*
* Utility functions
*/
static GtkWidget*
create_row_for_source (GcalManager *manager,
ESource *source,
gboolean writable)
{
cairo_surface_t *surface;
GtkWidget *row, *box, *icon, *label, *selected_icon;
GdkRGBA color;
gchar *tooltip, *parent_name;
/* The main box */
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_set_margin_start (box, 6);
gtk_widget_set_margin_end (box, 6);
gtk_widget_set_margin_top (box, 3);
gtk_widget_set_margin_bottom (box, 3);
/* The icon with the source color */
get_color_name_from_source (source, &color);
surface = get_circle_surface_from_color (&color, 16);
icon = gtk_image_new_from_surface (surface);
gtk_container_add (GTK_CONTAINER (box), icon);
gtk_style_context_add_class (gtk_widget_get_style_context (icon), "calendar-color-image");
/* Source name label */
label = gtk_label_new (e_source_get_display_name (source));
gtk_widget_set_margin_end (label, 12);
gtk_container_add (GTK_CONTAINER (box), label);
/* Selected icon */
selected_icon = gtk_image_new_from_icon_name ("emblem-ok-symbolic", GTK_ICON_SIZE_BUTTON);
gtk_container_add (GTK_CONTAINER (box), selected_icon);
/* The row itself */
row = gtk_list_box_row_new ();
gtk_widget_set_sensitive (row, writable);
gtk_container_add (GTK_CONTAINER (row), box);
/* Setup also a cool tooltip */
get_source_parent_name_color (manager, source, &parent_name, NULL);
if (writable)
tooltip = g_strdup (parent_name);
else
tooltip = g_strdup_printf (_("%s (this calendar is read-only)"), parent_name);
gtk_widget_set_tooltip_text (row, tooltip);
/*
* Setup the row data. A reference for the source is
* taken to make sure it is not destroyed before this
* row.
*/
g_object_set_data_full (G_OBJECT (row), "source", g_object_ref (source), g_object_unref);
g_object_set_data (G_OBJECT (row), "selected-icon", selected_icon);
g_object_set_data (G_OBJECT (row), "color-icon", icon);
g_object_set_data (G_OBJECT (row), "name-label", label);
gtk_widget_show (label);
gtk_widget_show (icon);
gtk_widget_show (box);
gtk_widget_show (row);
g_clear_pointer (&surface, cairo_surface_destroy);
g_free (parent_name);
g_free (tooltip);
return row;
}
/*
* Retrieves the corresponding row for the given
* ESource from the listbox.
*/
static GtkWidget*
get_row_for_source (GcalQuickAddPopover *self,
ESource *source)
{
GList *children, *l;
GtkWidget *row;
row = NULL;
children = gtk_container_get_children (GTK_CONTAINER (self->calendars_listbox));
for (l = children; l != NULL; l = g_list_next (l))
{
ESource *row_source = g_object_get_data (l->data, "source");
if (row_source == source)
{
row = l->data;
break;
}
}
g_list_free (children);
return row;
}
static void
select_row (GcalQuickAddPopover *self,
GtkListBoxRow *row)
{
cairo_surface_t *surface;
GtkWidget *icon;
ESource *source;
GdkRGBA color;
/* First, unselect the previous row */
if (self->selected_row)
{
icon = g_object_get_data (G_OBJECT (self->selected_row), "selected-icon");
gtk_widget_set_visible (icon, FALSE);
}
/* Now, select and save the new row */
icon = g_object_get_data (G_OBJECT (row), "selected-icon");
gtk_widget_set_visible (icon, TRUE);
self->selected_row = GTK_WIDGET (row);
/* Setup the event page's source name and color */
source = g_object_get_data (G_OBJECT (row), "source");
gtk_label_set_label (GTK_LABEL (self->calendar_name_label), e_source_get_display_name (source));
get_color_name_from_source (source, &color);
surface = get_circle_surface_from_color (&color, 16);
gtk_image_set_from_surface (GTK_IMAGE (self->color_image), surface);
/* Return to the events page */
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "events");
/* Focus back the event Entry */
if (gtk_widget_get_visible (GTK_WIDGET (self)))
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->summary_entry));
g_clear_pointer (&surface, cairo_surface_destroy);
}
static void
update_header (GcalQuickAddPopover *self)
{
icaltimetype *dtstart;
struct tm stm_date, etm_date;
gboolean multiday_or_timed;
gboolean all_day;
gchar start[64], end[64];
gchar *title_date;
all_day = datetime_is_date (self->date_start) && (self->date_end ? datetime_is_date (self->date_end) : TRUE);
dtstart = datetime_to_icaltime (self->date_start);
stm_date = icaltimetype_to_tm (dtstart);
/* Translators:
* this is the format string for representing a date consisting of a month name
* and a date of month.
*/
e_utf8_strftime_fix_am_pm (start, 64, C_("event date format", "%B %d"), &stm_date);
if (self->date_end)
{
multiday_or_timed = g_date_time_difference (self->date_end, self->date_start) / G_TIME_SPAN_DAY > 1 ||
g_date_time_difference (self->date_end, self->date_start) / G_TIME_SPAN_MINUTE > 1;
}
else
{
multiday_or_timed = FALSE;
}
if (multiday_or_timed)
{
icaltimetype *dtend;
dtend = datetime_to_icaltime (self->date_end);
if (dtend->is_date)
icaltime_adjust (dtend, -1, 0, 0, 0);
if (all_day &&
(g_date_time_difference (self->date_end, self->date_start) / G_TIME_SPAN_DAY > 1 &&
g_date_time_difference (self->date_end, self->date_start) / G_TIME_SPAN_MINUTE > 1))
{
etm_date = icaltimetype_to_tm (dtend);
/* Translators:
* this is the format string for representing a date consisting of a month name
* and a date of month.
*/
e_utf8_strftime_fix_am_pm (end, 64, C_("event date format", "%B %d"), &etm_date);
title_date = g_strdup_printf (_("New Event from %s to %s"), start, end);
}
else
{
g_autofree gchar *start_hour, *end_hour;
if (self->clock_format_24h)
{
start_hour = g_date_time_format (self->date_start, "%R");
end_hour = g_date_time_format (self->date_end, "%R");
}
else
{
start_hour = g_date_time_format (self->date_start, "%I:%M %P");
end_hour = g_date_time_format (self->date_end, "%I:%M %P");
}
title_date = g_strdup_printf (_("New Event on %s, %s – %s"),
start,
start_hour,
end_hour);
}
g_free (dtend);
}
else
{
title_date = g_strdup_printf (_("New Event on %s"), start);
}
gtk_label_set_label (GTK_LABEL (self->title_label), title_date);
g_free (title_date);
g_free (dtstart);
}
static void
update_default_calendar_row (GcalQuickAddPopover *self)
{
GtkWidget *row;
ESource *default_source;
default_source = gcal_manager_get_default_source (self->manager);
row = get_row_for_source (self, default_source);
select_row (self, GTK_LIST_BOX_ROW (row));
g_clear_object (&default_source);
}
/*
* Callbacks
*/
static void
on_source_added (GcalManager *manager,
ESource *source,
gboolean enabled,
GcalQuickAddPopover *self)
{
ESource *default_source;
GtkWidget *row;
/* Since we can't add on read-only calendars, lets not show them at all */
if (!gcal_manager_is_client_writable (manager, source))
return;
default_source = gcal_manager_get_default_source (manager);
row = create_row_for_source (manager, source, gcal_manager_is_client_writable (manager, source));
gtk_container_add (GTK_CONTAINER (self->calendars_listbox), row);
/* Select the default source whe first adding events */
if (source == default_source && !self->selected_row)
select_row (self, GTK_LIST_BOX_ROW (row));
g_clear_object (&default_source);
}
static void
on_source_changed (GcalManager *manager,
ESource *source,
GcalQuickAddPopover *self)
{
cairo_surface_t *surface;
GtkWidget *row, *color_icon, *name_label;
GdkRGBA color;
row = get_row_for_source (self, source);
/* If the calendar changed from/to read-only, we add or remove it here */
if (!gcal_manager_is_client_writable (self->manager, source))
{
gtk_container_remove (GTK_CONTAINER (self->calendars_listbox), row);
return;
}
else if (!row)
{
on_source_added (self->manager,
source,
is_source_enabled (source),
self);
row = get_row_for_source (self, source);
}
color_icon = g_object_get_data (G_OBJECT (row), "color-icon");
name_label = g_object_get_data (G_OBJECT (row), "name-label");
/* If the source is writable now (or not), update sensitivity */
gtk_widget_set_sensitive (row, gcal_manager_is_client_writable (manager, source));
/* Setup the source color, in case it changed */
get_color_name_from_source (source, &color);
surface = get_circle_surface_from_color (&color, 16);
gtk_image_set_from_surface (GTK_IMAGE (color_icon), surface);
g_clear_pointer (&surface, cairo_surface_destroy);
/* Also setup the row name, in case we just changed the source name */
gtk_label_set_text (GTK_LABEL (name_label), e_source_get_display_name (source));
gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->calendars_listbox));
/*
* If the row is the selected one, make a fake selection so the
* icon & name of the source is updated at the events' page.
*/
if (row == self->selected_row)
select_row (self, GTK_LIST_BOX_ROW (row));
}
static void
on_source_removed (GcalManager *manager,
ESource *source,
GcalQuickAddPopover *self)
{
GtkWidget *row;
row = get_row_for_source (self, source);
if (!row)
return;
gtk_container_remove (GTK_CONTAINER (self->calendars_listbox), row);
}
/* Sort the calendars by their display name */
static gint
sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
ESource *source1, *source2;
gchar *name1, *name2;
gint retval;
source1 = g_object_get_data (G_OBJECT (row1), "source");
source2 = g_object_get_data (G_OBJECT (row2), "source");
name1 = g_utf8_casefold (e_source_get_display_name (source1), -1);
name2 = g_utf8_casefold (e_source_get_display_name (source2), -1);
retval = g_strcmp0 (name1, name2);
g_free (name1);
g_free (name2);
return retval;
}
static void
select_calendar_button_clicked (GcalQuickAddPopover *self)
{
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "calendars");
}
static void
back_button_clicked (GcalQuickAddPopover *self)
{
gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "events");
}
static void
edit_or_create_event (GcalQuickAddPopover *self,
GtkWidget *button)
{
ECalComponent *component;
GDateTime *date_start, *date_end;
GTimeZone *tz;
GcalEvent *event;
ESource *source;
const gchar *summary;
gboolean all_day, single_day;
if (!self->selected_row)
return;
source = g_object_get_data (G_OBJECT (self->selected_row), "source");
single_day = datetime_compare_date (self->date_end, self->date_start) == 0;
all_day = datetime_compare_date (self->date_end, self->date_start) > 1 ||
g_date_time_compare (self->date_end, self->date_start) == 0;
tz = all_day ? g_time_zone_new_utc () : g_time_zone_new_local ();
/* Gather start date */
date_start = g_date_time_new (tz,
g_date_time_get_year (self->date_start),
g_date_time_get_month (self->date_start),
g_date_time_get_day_of_month (self->date_start),
g_date_time_get_hour (self->date_start),
g_date_time_get_minute (self->date_start),
0);
/* Gather date end */
if (self->date_end)
{
date_end = g_date_time_new (tz,
g_date_time_get_year (self->date_end),
g_date_time_get_month (self->date_end),
g_date_time_get_day_of_month (self->date_end) + (single_day && all_day ? 1 : 0),
g_date_time_get_hour (self->date_end),
g_date_time_get_minute (self->date_end),
0);
}
else
{
date_end = all_day ? g_date_time_add_days (date_start, 1) : g_date_time_add_hours (date_start, 1);
}
/* Gather the summary */
if (gtk_entry_get_text_length (GTK_ENTRY (self->summary_entry)) > 0)
summary = gtk_entry_get_text (GTK_ENTRY (self->summary_entry));
else
summary = _("Unnamed event");
/* Create an ECalComponent from the data above */
component = build_component_from_details (summary, date_start, date_end);
event = gcal_event_new (source, component, NULL);
gcal_event_set_all_day (event, all_day);
gcal_event_set_timezone (event, tz);
/* If we clicked edit button, send a signal; otherwise, create the event */
if (button == self->add_button)
gcal_manager_create_event (self->manager, event);
else
g_signal_emit (self, signals[EDIT_EVENT], 0, event);
gtk_widget_hide (GTK_WIDGET (self));
g_clear_pointer (&date_start, g_date_time_unref);
g_clear_pointer (&date_end, g_date_time_unref);
g_clear_pointer (&tz, g_time_zone_unref);
g_clear_object (&event);
}
static void
summary_entry_text_changed (GtkEntry *entry,
GParamSpec *pspec,
GcalQuickAddPopover *self)
{
gtk_widget_set_sensitive (self->add_button, gtk_entry_get_text_length (entry) > 0);
}
static void
summary_entry_activated (GtkEntry *entry,
GcalQuickAddPopover *self)
{
if (gtk_entry_get_text_length (entry) > 0)
edit_or_create_event (self, self->add_button);
}
/*
* Overrides
*/
static void
gcal_quick_add_popover_finalize (GObject *object)
{
GcalQuickAddPopover *self = (GcalQuickAddPopover *)object;
g_clear_object (&self->manager);
G_OBJECT_CLASS (gcal_quick_add_popover_parent_class)->finalize (object);
}
static void
gcal_quick_add_popover_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GcalQuickAddPopover *self = GCAL_QUICK_ADD_POPOVER (object);
switch (prop_id)
{
case PROP_DATE_END:
g_value_set_boxed (value, self->date_end);
break;
case PROP_DATE_START:
g_value_set_boxed (value, self->date_start);
break;
case PROP_MANAGER:
g_value_set_object (value, self->manager);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_quick_add_popover_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GcalQuickAddPopover *self = GCAL_QUICK_ADD_POPOVER (object);
switch (prop_id)
{
case PROP_DATE_END:
gcal_quick_add_popover_set_date_end (self, g_value_get_boxed (value));
break;
case PROP_DATE_START:
gcal_quick_add_popover_set_date_start (self, g_value_get_boxed (value));
break;
case PROP_MANAGER:
gcal_quick_add_popover_set_manager (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_quick_add_popover_closed (GtkPopover *popover)
{
GcalQuickAddPopover *self;
self = GCAL_QUICK_ADD_POPOVER (popover);
/* Clear text */
gtk_entry_set_text (GTK_ENTRY (self->summary_entry), "");
/* Select the default row again */
update_default_calendar_row (self);
g_clear_pointer (&self->date_start, g_date_time_unref);
g_clear_pointer (&self->date_end, g_date_time_unref);
}
static void
gcal_quick_add_popover_class_init (GcalQuickAddPopoverClass *klass)
{
GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
popover_class->closed = gcal_quick_add_popover_closed;
object_class->finalize = gcal_quick_add_popover_finalize;
object_class->get_property = gcal_quick_add_popover_get_property;
object_class->set_property = gcal_quick_add_popover_set_property;
/**
* GcalQuickAddPopover::edit-event:
* @self: a #GcalQuickAddPopover
* @event: the new #GcalEvent
*
* Emited when the user clicks 'Edit event' button.
*/
signals[EDIT_EVENT] = g_signal_new ("edit-event",
GCAL_TYPE_QUICK_ADD_POPOVER,
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
1,
GCAL_TYPE_EVENT);
/**
* GcalQuickAddPopover::date-end:
*
* The end date of the new event.
*/
g_object_class_install_property (object_class,
PROP_DATE_END,
g_param_spec_boxed ("date-end",
"End date of the event",
"The end date of the new event",
G_TYPE_DATE_TIME,
G_PARAM_READWRITE));
/**
* GcalQuickAddPopover::date-start:
*
* The start date of the new event.
*/
g_object_class_install_property (object_class,
PROP_DATE_START,
g_param_spec_boxed ("date-start",
"Start date of the event",
"The start date of the new event",
G_TYPE_DATE_TIME,
G_PARAM_READWRITE));
/**
* GcalQuickAddPopover::manager:
*
* The manager of the application.
*/
g_object_class_install_property (object_class,
PROP_MANAGER,
g_param_spec_object ("manager",
"Manager of the application",
"The singleton manager of the application",
GCAL_TYPE_MANAGER,
G_PARAM_READWRITE));
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/quick-add-popover.ui");
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, add_button);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, calendars_listbox);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, calendar_name_label);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, color_image);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, stack);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, summary_entry);
gtk_widget_class_bind_template_child (widget_class, GcalQuickAddPopover, title_label);
gtk_widget_class_bind_template_callback (widget_class, back_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, edit_or_create_event);
gtk_widget_class_bind_template_callback (widget_class, select_calendar_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, select_row);
gtk_widget_class_bind_template_callback (widget_class, summary_entry_activated);
gtk_widget_class_bind_template_callback (widget_class, summary_entry_text_changed);
}
static void
gcal_quick_add_popover_init (GcalQuickAddPopover *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->calendars_listbox), sort_func, NULL, NULL);
self->clock_format_24h = is_clock_format_24h ();
}
GtkWidget*
gcal_quick_add_popover_new (void)
{
return g_object_new (GCAL_TYPE_QUICK_ADD_POPOVER, NULL);
}
/**
* gcal_quick_add_popover_set_manager:
* @self: a #GcalQuickAddPopover
* @manager: a #GcalManager
*
* Sets the manager of the popover.
*/
void
gcal_quick_add_popover_set_manager (GcalQuickAddPopover *self,
GcalManager *manager)
{
g_return_if_fail (GCAL_IS_QUICK_ADD_POPOVER (self));
if (g_set_object (&self->manager, manager))
{
GList *sources, *l;
/* Add currently leaded sources */
sources = gcal_manager_get_sources_connected (manager);
for (l = sources; l != NULL; l = g_list_next (l))
on_source_added (manager, l->data, gcal_manager_is_client_writable (manager, l->data), self);
g_list_free (sources);
/* Connect to the manager signals and keep the list updates */
g_signal_connect (manager, "source-added", G_CALLBACK (on_source_added), self);
g_signal_connect (manager, "source-changed", G_CALLBACK (on_source_changed), self);
g_signal_connect (manager, "source-removed", G_CALLBACK (on_source_removed), self);
g_signal_connect_swapped (manager, "notify::default-calendar", G_CALLBACK (update_default_calendar_row), self);
g_object_notify (G_OBJECT (self), "manager");
}
}
/**
* gcal_quick_add_popover_get_date_start:
* @self: a #GcalQuickAddPopover
*
* Retrieves the start date of @self.
*
* Returns: (transfer none): a #GDateTime
*/
GDateTime*
gcal_quick_add_popover_get_date_start (GcalQuickAddPopover *self)
{
g_return_val_if_fail (GCAL_IS_QUICK_ADD_POPOVER (self), NULL);
return self->date_start;
}
/**
* gcal_quick_add_popover_set_date_start:
* @self: a #GcalQuickAddPopover
* @dt: a #GDateTime
*
* Sets the start date of the to-be-created event.
*/
void
gcal_quick_add_popover_set_date_start (GcalQuickAddPopover *self,
GDateTime *dt)
{
g_return_if_fail (GCAL_IS_QUICK_ADD_POPOVER (self));
if (self->date_start != dt &&
(!self->date_start || !dt ||
(self->date_start && dt && !g_date_time_equal (self->date_start, dt))))
{
g_clear_pointer (&self->date_start, g_date_time_unref);
self->date_start = g_date_time_ref (dt);
update_header (self);
g_object_notify (G_OBJECT (self), "date-start");
}
}
/**
* gcal_quick_add_popover_get_date_end:
* @self: a #GcalQuickAddPopover
*
* Retrieves the end date of @self.
*
* Returns: (transfer none): a #GDateTime
*/
GDateTime*
gcal_quick_add_popover_get_date_end (GcalQuickAddPopover *self)
{
g_return_val_if_fail (GCAL_IS_QUICK_ADD_POPOVER (self), NULL);
return self->date_end;
}
/**
* gcal_quick_add_popover_set_date_end:
* @self: a #GcalQuickAddPopover
* @dt: a #GDateTime
*
* Sets the end date of the to-be-created event.
*/
void
gcal_quick_add_popover_set_date_end (GcalQuickAddPopover *self,
GDateTime *dt)
{
g_return_if_fail (GCAL_IS_QUICK_ADD_POPOVER (self));
if (self->date_end != dt &&
(!self->date_end || !dt ||
(self->date_end && dt && !g_date_time_equal (self->date_end, dt))))
{
g_clear_pointer (&self->date_end, g_date_time_unref);
self->date_end = g_date_time_ref (dt);
update_header (self);
g_object_notify (G_OBJECT (self), "date-end");
}
}