This reworks the style of the event widgets to make them match the latest designs. This also supports the light and dark theme preferences and fixes some spacing inconsistencies and errors. - The orientation of events only affects how their content is laid out, not their style. - Events are fully colored accordingly to their calendar's color. - Timed events have a semi-transparent background and a kind of opaque knob on their left. - Timed events have their text colored according to the theme. - Lasting events are fully opaque, with no knob. - Lasting events have their text colored according to their calendar's color. This purposefully leaves the indentation broken to help reviewing the changes.
1090 lines
33 KiB
C
1090 lines
33 KiB
C
/* gcal-event-widget.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/>.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalEventWidget"
|
|
|
|
#include <libecal/libecal.h>
|
|
#include <glib/gi18n.h>
|
|
#include <string.h>
|
|
|
|
#include "gcal-application.h"
|
|
#include "gcal-context.h"
|
|
#include "gcal-clock.h"
|
|
#include "gcal-debug.h"
|
|
#include "gcal-event-popover.h"
|
|
#include "gcal-event-widget.h"
|
|
#include "gcal-overflow-bin.h"
|
|
#include "gcal-utils.h"
|
|
|
|
#define DESC_MAX_CHAR 200
|
|
#define INTENSITY(c) ((c->red) * 0.30 + (c->green) * 0.59 + (c->blue) * 0.11)
|
|
#define ICON_SIZE 16
|
|
|
|
typedef struct
|
|
{
|
|
GcalEventWidget *event_widget;
|
|
GcalEventPreviewCallback callback;
|
|
gpointer user_data;
|
|
} PreviewData;
|
|
|
|
struct _GcalEventWidget
|
|
{
|
|
GtkWidget parent;
|
|
|
|
/* properties */
|
|
GDateTime *dt_start;
|
|
GDateTime *dt_end;
|
|
|
|
/* widgets */
|
|
GtkWidget *horizontal_box;
|
|
GtkWidget *timestamp_label;
|
|
GtkWidget *squeezer;
|
|
GtkWidget *summary_label;
|
|
GtkWidget *vertical_box;
|
|
GtkEventController *drag_source;
|
|
|
|
/* internal data */
|
|
gboolean clock_format_24h : 1;
|
|
gboolean read_only : 1;
|
|
gchar *css_class;
|
|
|
|
GcalEvent *event;
|
|
|
|
GtkOrientation orientation;
|
|
|
|
GcalTimestampPolicy timestamp_policy;
|
|
|
|
gint old_width;
|
|
gint old_height;
|
|
|
|
GcalContext *context;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONTEXT,
|
|
PROP_DATE_END,
|
|
PROP_DATE_START,
|
|
PROP_EVENT,
|
|
PROP_TIMESTAMP_POLICY,
|
|
PROP_ORIENTATION,
|
|
NUM_PROPS
|
|
};
|
|
|
|
enum
|
|
{
|
|
ACTIVATE,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
CURSOR_NONE,
|
|
CURSOR_GRAB,
|
|
CURSOR_GRABBING
|
|
} CursorType;
|
|
|
|
static guint signals[NUM_SIGNALS] = { 0, };
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GcalEventWidget, gcal_event_widget, GTK_TYPE_WIDGET,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL));
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static gboolean
|
|
get_vertical_enabled (gpointer user_data,
|
|
GcalEvent *event,
|
|
GtkOrientation orientation)
|
|
{
|
|
return orientation == GTK_ORIENTATION_VERTICAL && !gcal_event_get_all_day (event);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_update_style (GcalEventWidget *self)
|
|
{
|
|
gboolean slanted_start;
|
|
gboolean slanted_end;
|
|
gboolean timed;
|
|
|
|
slanted_start = FALSE;
|
|
slanted_end = FALSE;
|
|
|
|
/* Clear previous style classes */
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "slanted");
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "slanted-start");
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "slanted-end");
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "timed");
|
|
|
|
/*
|
|
* If the event's dates differs from the widget's dates,
|
|
* add a slanted edge class at the widget.
|
|
*/
|
|
|
|
if (self->dt_start)
|
|
slanted_start = g_date_time_compare (gcal_event_get_date_start (self->event), self->dt_start) != 0;
|
|
|
|
if (self->dt_end)
|
|
slanted_end = g_date_time_compare (gcal_event_get_date_end (self->event), self->dt_end) != 0;
|
|
|
|
if (slanted_start && slanted_end)
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "slanted");
|
|
else if (slanted_start)
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "slanted-start");
|
|
else if (slanted_end)
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "slanted-end");
|
|
|
|
/* Add style classes for orientation selectors */
|
|
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "vertical");
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "horizontal");
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "horizontal");
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "vertical");
|
|
}
|
|
|
|
/*
|
|
* If the event is a timed, single-day event, draw it differently
|
|
* from all-day or multi-day events.
|
|
*/
|
|
timed = !gcal_event_get_all_day (self->event) && !gcal_event_is_multiday (self->event);
|
|
|
|
if (timed)
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "timed");
|
|
}
|
|
|
|
static void
|
|
update_color (GcalEventWidget *self)
|
|
{
|
|
GdkRGBA *color;
|
|
GQuark color_id;
|
|
gchar *color_str;
|
|
gchar *css_class;
|
|
GDateTime *now;
|
|
gint date_compare;
|
|
|
|
color = gcal_event_get_color (self->event);
|
|
now = g_date_time_new_now_local ();
|
|
date_compare = g_date_time_compare (self->dt_end, now);
|
|
|
|
/* Fades out an event that's earlier than the current date */
|
|
gtk_widget_set_opacity (GTK_WIDGET (self), date_compare < 0 ? 0.6 : 1.0);
|
|
|
|
/* Remove the old style class */
|
|
if (self->css_class)
|
|
{
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), self->css_class);
|
|
g_clear_pointer (&self->css_class, g_free);
|
|
}
|
|
|
|
color_str = gdk_rgba_to_string (color);
|
|
color_id = g_quark_from_string (color_str);
|
|
css_class = g_strdup_printf ("color-%d", color_id);
|
|
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), css_class);
|
|
|
|
if (INTENSITY (color) > 0.5)
|
|
{
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "color-dark");
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "color-light");
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_remove_css_class (GTK_WIDGET (self), "color-light");
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "color-dark");
|
|
}
|
|
|
|
/* Keep the current style around, so we can remove it later */
|
|
self->css_class = css_class;
|
|
|
|
g_clear_pointer (&now, g_date_time_unref);
|
|
g_free (color_str);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_set_event_tooltip (GcalEventWidget *self,
|
|
GcalEvent *event)
|
|
{
|
|
g_autoptr (GDateTime) tooltip_start, tooltip_end;
|
|
g_autofree gchar *start, *end, *escaped_summary;
|
|
GString *tooltip_mesg;
|
|
gboolean allday, multiday, is_ltr;
|
|
guint description_len;
|
|
|
|
tooltip_mesg = g_string_new (NULL);
|
|
escaped_summary = g_markup_escape_text (gcal_event_get_summary (event), -1);
|
|
g_string_append_printf (tooltip_mesg, "<b>%s</b>", escaped_summary);
|
|
|
|
allday = gcal_event_get_all_day (event);
|
|
multiday = gcal_event_is_multiday (event);
|
|
|
|
is_ltr = gtk_widget_get_direction (GTK_WIDGET (self)) != GTK_TEXT_DIR_RTL;
|
|
|
|
if (allday)
|
|
{
|
|
/* All day events span from [ start, end - 1 day ] */
|
|
tooltip_start = g_date_time_ref (gcal_event_get_date_start (event));
|
|
tooltip_end = g_date_time_add_days (gcal_event_get_date_end (event), -1);
|
|
|
|
if (multiday)
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x");
|
|
end = g_date_time_format (tooltip_end, "%x");
|
|
}
|
|
else
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x");
|
|
end = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tooltip_start = g_date_time_to_local (gcal_event_get_date_start (event));
|
|
tooltip_end = g_date_time_to_local (gcal_event_get_date_end (event));
|
|
|
|
if (multiday)
|
|
{
|
|
if (self->clock_format_24h)
|
|
{
|
|
if (is_ltr)
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x %R");
|
|
end = g_date_time_format (tooltip_end, "%x %R");
|
|
}
|
|
else
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%R %x");
|
|
end = g_date_time_format (tooltip_end, "%R %x");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_ltr)
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x %I:%M %P");
|
|
end = g_date_time_format (tooltip_end, "%x %I:%M %P");
|
|
}
|
|
else
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%P %M:%I %x");
|
|
end = g_date_time_format (tooltip_end, "%P %M:%I %x");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (self->clock_format_24h)
|
|
{
|
|
if (is_ltr)
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x, %R");
|
|
end = g_date_time_format (tooltip_end, "%R");
|
|
}
|
|
else
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%R ,%x");
|
|
end = g_date_time_format (tooltip_end, "%R");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_ltr)
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%x, %I:%M %P");
|
|
end = g_date_time_format (tooltip_end, "%I:%M %P");
|
|
}
|
|
else
|
|
{
|
|
start = g_date_time_format (tooltip_start, "%P %M:%I ,%x");
|
|
end = g_date_time_format (tooltip_end, "%P %M:%I");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (allday && !multiday)
|
|
{
|
|
g_string_append_printf (tooltip_mesg, "\n%s", start);
|
|
}
|
|
else
|
|
{
|
|
g_string_append_printf (tooltip_mesg,
|
|
"\n%s - %s",
|
|
is_ltr ? start : end,
|
|
is_ltr ? end : start);
|
|
}
|
|
|
|
/* Append event location */
|
|
if (g_utf8_strlen (gcal_event_get_location (event), -1) > 0)
|
|
{
|
|
g_autofree gchar *escaped_location;
|
|
|
|
escaped_location = g_markup_escape_text (gcal_event_get_location (event), -1);
|
|
|
|
g_string_append (tooltip_mesg, "\n\n");
|
|
|
|
/* Translators: %s is the location of the event (e.g. "Downtown, 3rd Avenue") */
|
|
g_string_append_printf (tooltip_mesg, _("At %s"), escaped_location);
|
|
}
|
|
|
|
description_len = g_utf8_strlen (gcal_event_get_description (event), -1);
|
|
|
|
/* Truncate long descriptions at a white space and ellipsize */
|
|
if (description_len > 0)
|
|
{
|
|
g_autofree gchar *escaped_description;
|
|
GString *tooltip_desc;
|
|
|
|
tooltip_desc = g_string_new (gcal_event_get_description (event));
|
|
|
|
/* If the description is larger than DESC_MAX_CHAR, ellipsize it */
|
|
if (description_len > DESC_MAX_CHAR)
|
|
{
|
|
g_string_truncate (tooltip_desc, DESC_MAX_CHAR - 1);
|
|
g_string_append (tooltip_desc, "…");
|
|
}
|
|
|
|
escaped_description = g_markup_escape_text (tooltip_desc->str, -1);
|
|
|
|
g_string_append_printf (tooltip_mesg, "\n\n%s", escaped_description);
|
|
|
|
g_string_free (tooltip_desc, TRUE);
|
|
}
|
|
|
|
gtk_widget_set_tooltip_markup (GTK_WIDGET (self), tooltip_mesg->str);
|
|
|
|
g_string_free (tooltip_mesg, TRUE);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_update_timestamp (GcalEventWidget *self)
|
|
{
|
|
g_autofree gchar *timestamp_str = NULL;
|
|
|
|
if (GCAL_IS_EVENT (self->event) &&
|
|
self->timestamp_policy != GCAL_TIMESTAMP_POLICY_NONE)
|
|
{
|
|
g_autoptr (GDateTime) time = NULL;
|
|
|
|
if (self->timestamp_policy == GCAL_TIMESTAMP_POLICY_START)
|
|
time = g_date_time_to_local (gcal_event_get_date_start (self->event));
|
|
else
|
|
time = g_date_time_to_local (gcal_event_get_date_end (self->event));
|
|
|
|
if (gcal_event_get_all_day (self->event) || gcal_event_is_multiday (self->event))
|
|
timestamp_str = g_date_time_format (time, "%a %B %e");
|
|
else if (self->clock_format_24h)
|
|
timestamp_str = g_date_time_format (time, "%R");
|
|
else
|
|
timestamp_str = g_date_time_format (time, "%I:%M %P");
|
|
}
|
|
|
|
gtk_widget_set_visible (self->timestamp_label, timestamp_str != NULL);
|
|
gtk_label_set_label (GTK_LABEL (self->timestamp_label), timestamp_str);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_set_event_internal (GcalEventWidget *self,
|
|
GcalEvent *event)
|
|
{
|
|
/*
|
|
* This function is called only once, since the property is
|
|
* set as CONSTRUCT_ONLY. Any other attempt to set an event
|
|
* will be ignored.
|
|
*
|
|
* Because of that condition, we don't really have to care about
|
|
* disconnecting functions or cleaning up the previous event.
|
|
*/
|
|
|
|
/* The event spawns with a floating reference, and we take it's ownership */
|
|
g_set_object (&self->event, event);
|
|
|
|
/*
|
|
* Initially, the widget's start and end dates are the same
|
|
* of the event's ones. We may change it afterwards.
|
|
*/
|
|
gcal_event_widget_set_date_start (self, gcal_event_get_date_start (event));
|
|
gcal_event_widget_set_date_end (self, gcal_event_get_date_end (event));
|
|
|
|
/* Update color */
|
|
update_color (self);
|
|
|
|
g_signal_connect_object (event,
|
|
"notify::color",
|
|
G_CALLBACK (update_color),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
|
|
g_signal_connect_object (event,
|
|
"notify::summary",
|
|
G_CALLBACK (gtk_widget_queue_draw),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
|
|
/* Tooltip */
|
|
gcal_event_widget_set_event_tooltip (self, event);
|
|
|
|
/* Summary label */
|
|
g_object_bind_property (event,
|
|
"summary",
|
|
self->summary_label,
|
|
"label",
|
|
G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
|
|
|
|
gcal_event_widget_update_style (self);
|
|
gcal_event_widget_update_timestamp (self);
|
|
}
|
|
|
|
static void
|
|
reply_preview_callback (GtkWidget *event_popover,
|
|
PreviewData *data,
|
|
GcalEventPreviewAction action)
|
|
{
|
|
if (data->callback)
|
|
data->callback (data->event_widget, action, data->user_data);
|
|
|
|
g_signal_handlers_disconnect_by_data (event_popover, data);
|
|
|
|
gtk_popover_popdown (GTK_POPOVER (event_popover));
|
|
gtk_widget_unparent (event_popover);
|
|
|
|
g_clear_pointer (&data, g_free);
|
|
}
|
|
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static void
|
|
on_click_gesture_pressed_cb (GtkGestureClick *click_gesture,
|
|
gint n_press,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalEventWidget *self)
|
|
{
|
|
gtk_gesture_set_state (GTK_GESTURE (click_gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
}
|
|
|
|
static void
|
|
on_click_gesture_release_cb (GtkGestureClick *click_gesture,
|
|
gint n_press,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalEventWidget *self)
|
|
{
|
|
gtk_gesture_set_state (GTK_GESTURE (click_gesture), GTK_EVENT_SEQUENCE_CLAIMED);
|
|
g_signal_emit (self, signals[ACTIVATE], 0);
|
|
}
|
|
|
|
static void
|
|
on_drag_source_begin_cb (GtkDragSource *source,
|
|
GdkDrag *drag,
|
|
GcalEventWidget *self)
|
|
{
|
|
g_autoptr (GdkPaintable) paintable = NULL;
|
|
|
|
paintable = gtk_widget_paintable_new (GTK_WIDGET (self));
|
|
gtk_drag_source_set_icon (source, paintable, 0, 0);
|
|
}
|
|
|
|
static GdkContentProvider*
|
|
on_drag_source_prepare_cb (GtkDragSource *source,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalEventWidget *self)
|
|
{
|
|
return gdk_content_provider_new_typed (GCAL_TYPE_EVENT_WIDGET, self);
|
|
}
|
|
|
|
static void
|
|
on_event_popover_closed_cb (GtkWidget *event_popover,
|
|
PreviewData *data)
|
|
{
|
|
reply_preview_callback (event_popover, data, GCAL_EVENT_PREVIEW_ACTION_NONE);
|
|
}
|
|
|
|
static void
|
|
on_event_popover_edit_cb (GtkWidget *event_popover,
|
|
PreviewData *data)
|
|
{
|
|
reply_preview_callback (event_popover, data, GCAL_EVENT_PREVIEW_ACTION_EDIT);
|
|
}
|
|
|
|
|
|
/*
|
|
* GObject overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_event_widget_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalEventWidget *self = GCAL_EVENT_WIDGET (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
g_assert (self->context == NULL);
|
|
self->context = g_value_dup_object (value);
|
|
|
|
g_signal_connect_object (gcal_context_get_clock (self->context),
|
|
"minute-changed",
|
|
G_CALLBACK (update_color),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
break;
|
|
|
|
case PROP_DATE_END:
|
|
gcal_event_widget_set_date_end (self, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_DATE_START:
|
|
gcal_event_widget_set_date_start (self, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_EVENT:
|
|
gcal_event_widget_set_event_internal (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_TIMESTAMP_POLICY:
|
|
gcal_event_widget_set_timestamp_policy (self, g_value_get_enum (value));
|
|
break;
|
|
|
|
case PROP_ORIENTATION:
|
|
self->orientation = g_value_get_enum (value);
|
|
gtk_widget_set_visible (self->vertical_box, self->orientation == GTK_ORIENTATION_VERTICAL);
|
|
gcal_event_widget_update_style (self);
|
|
gcal_event_widget_update_timestamp (self);
|
|
g_object_notify (object, "orientation");
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalEventWidget *self = GCAL_EVENT_WIDGET (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
g_value_set_object (value, self->context);
|
|
break;
|
|
|
|
case PROP_DATE_END:
|
|
g_value_set_boxed (value, self->dt_end);
|
|
break;
|
|
|
|
case PROP_DATE_START:
|
|
g_value_set_boxed (value, self->dt_start);
|
|
break;
|
|
|
|
case PROP_EVENT:
|
|
g_value_set_object (value, self->event);
|
|
break;
|
|
|
|
case PROP_TIMESTAMP_POLICY:
|
|
g_value_set_enum (value, self->timestamp_policy);
|
|
break;
|
|
|
|
case PROP_ORIENTATION:
|
|
g_value_set_enum (value, self->orientation);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_dispose (GObject *object)
|
|
{
|
|
GcalEventWidget *self = GCAL_EVENT_WIDGET (object);
|
|
|
|
g_clear_pointer (&self->squeezer, gtk_widget_unparent);
|
|
|
|
G_OBJECT_CLASS (gcal_event_widget_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_finalize (GObject *object)
|
|
{
|
|
GcalEventWidget *self;
|
|
|
|
self = GCAL_EVENT_WIDGET (object);
|
|
|
|
/* disconnect signals */
|
|
g_signal_handlers_disconnect_by_func (self->event, update_color, self);
|
|
g_signal_handlers_disconnect_by_func (self->event, gtk_widget_queue_draw, self);
|
|
|
|
/* releasing properties */
|
|
g_clear_pointer (&self->css_class, g_free);
|
|
g_clear_object (&self->event);
|
|
g_clear_object (&self->context);
|
|
|
|
G_OBJECT_CLASS (gcal_event_widget_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_class_init (GcalEventWidgetClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->set_property = gcal_event_widget_set_property;
|
|
object_class->get_property = gcal_event_widget_get_property;
|
|
object_class->dispose = gcal_event_widget_dispose;
|
|
object_class->finalize = gcal_event_widget_finalize;
|
|
|
|
/**
|
|
* GcalEventWidget::context:
|
|
*
|
|
* The context of the event.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_CONTEXT,
|
|
g_param_spec_object ("context",
|
|
"Context",
|
|
"Context",
|
|
GCAL_TYPE_CONTEXT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
/**
|
|
* GcalEventWidget::date-end:
|
|
*
|
|
* The end date this widget represents. Notice that this may
|
|
* differ from the event's end date. For example, if the event
|
|
* spans more than one month and we're in Month View, the end
|
|
* date marks the last day this event widget is visible.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_DATE_END,
|
|
g_param_spec_boxed ("date-end",
|
|
"End date",
|
|
"The end date of the widget",
|
|
G_TYPE_DATE_TIME,
|
|
G_PARAM_READWRITE));
|
|
|
|
/**
|
|
* GcalEventWidget::date-start:
|
|
*
|
|
* The start date this widget represents. Notice that this may
|
|
* differ from the event's start date. For example, if the event
|
|
* spans more than one month and we're in Month View, the start
|
|
* date marks the first day this event widget is visible.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_DATE_START,
|
|
g_param_spec_boxed ("date-start",
|
|
"Start date",
|
|
"The start date of the widget",
|
|
G_TYPE_DATE_TIME,
|
|
G_PARAM_READWRITE));
|
|
|
|
/**
|
|
* GcalEventWidget::event:
|
|
*
|
|
* The event this widget represents.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_EVENT,
|
|
g_param_spec_object ("event",
|
|
"Event",
|
|
"The event this widget represents",
|
|
GCAL_TYPE_EVENT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
/**
|
|
* GcalEventWidget::timestamp-policy:
|
|
*
|
|
* The policy for this widget's timestamp.
|
|
*
|
|
* Whether to show the start time, end time, or no time for the
|
|
* event. Depending on the event's kind, it will be an hour or a day.
|
|
*/
|
|
g_object_class_install_property (object_class,
|
|
PROP_TIMESTAMP_POLICY,
|
|
g_param_spec_enum ("timestamp-policy",
|
|
"Timestamp Policy",
|
|
"The policy for this widget's timestamp",
|
|
GCAL_TYPE_TIMESTAMP_POLICY,
|
|
GCAL_TIMESTAMP_POLICY_NONE,
|
|
G_PARAM_READWRITE));
|
|
|
|
/**
|
|
* GcalEventWidget::orientation:
|
|
*
|
|
* The orientation of the event widget.
|
|
*/
|
|
g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
|
|
|
|
signals[ACTIVATE] = g_signal_new ("activate",
|
|
GCAL_TYPE_EVENT_WIDGET,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/gcal-event-widget.ui");
|
|
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, drag_source);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, horizontal_box);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, timestamp_label);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, squeezer);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, summary_label);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalEventWidget, vertical_box);
|
|
|
|
gtk_widget_class_bind_template_callback (widget_class, get_vertical_enabled);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_pressed_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_release_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_drag_source_begin_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_drag_source_prepare_cb);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, "event");
|
|
|
|
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
|
}
|
|
|
|
static void
|
|
gcal_event_widget_init (GcalEventWidget *self)
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
widget = GTK_WIDGET (self);
|
|
self->clock_format_24h = is_clock_format_24h ();
|
|
|
|
g_type_ensure (GCAL_TYPE_OVERFLOW_BIN);
|
|
|
|
gtk_widget_init_template (widget);
|
|
|
|
gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "pointer");
|
|
|
|
/* Starts with horizontal */
|
|
self->orientation = GTK_ORIENTATION_HORIZONTAL;
|
|
gtk_widget_add_css_class (widget, "horizontal");
|
|
}
|
|
|
|
GtkWidget*
|
|
gcal_event_widget_new (GcalContext *context,
|
|
GcalEvent *event)
|
|
{
|
|
return g_object_new (GCAL_TYPE_EVENT_WIDGET,
|
|
"context", context,
|
|
"event", event,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
gcal_event_widget_set_read_only (GcalEventWidget *event,
|
|
gboolean read_only)
|
|
{
|
|
g_return_if_fail (GCAL_IS_EVENT_WIDGET (event));
|
|
|
|
gtk_event_controller_set_propagation_phase (event->drag_source,
|
|
read_only ? GTK_PHASE_NONE : GTK_PHASE_BUBBLE);
|
|
|
|
event->read_only = read_only;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_get_date_end:
|
|
* @self: a #GcalEventWidget
|
|
*
|
|
* Retrieves the visible end date of this widget. This
|
|
* may differ from the event's end date.
|
|
*
|
|
* Returns: (transfer none): a #GDateTime
|
|
*/
|
|
GDateTime*
|
|
gcal_event_widget_get_date_end (GcalEventWidget *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_EVENT_WIDGET (self), NULL);
|
|
|
|
return self->dt_end ? self->dt_end : self->dt_start;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_set_date_end:
|
|
* @self: a #GcalEventWidget
|
|
* @date_end: the end date of this widget
|
|
*
|
|
* Sets the visible end date of this widget. This
|
|
* may differ from the event's end date, but cannot
|
|
* be after it.
|
|
*/
|
|
void
|
|
gcal_event_widget_set_date_end (GcalEventWidget *self,
|
|
GDateTime *date_end)
|
|
{
|
|
g_return_if_fail (GCAL_IS_EVENT_WIDGET (self));
|
|
|
|
if (self->dt_end != date_end &&
|
|
(!self->dt_end || !date_end ||
|
|
(self->dt_end && date_end && !g_date_time_equal (self->dt_end, date_end))))
|
|
{
|
|
/* The end date should never be after the event's end date */
|
|
if (date_end && g_date_time_compare (date_end, gcal_event_get_date_end (self->event)) > 0)
|
|
return;
|
|
|
|
g_clear_pointer (&self->dt_end, g_date_time_unref);
|
|
self->dt_end = g_date_time_ref (date_end);
|
|
|
|
gcal_event_widget_update_style (self);
|
|
|
|
g_object_notify (G_OBJECT (self), "date-end");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_get_date_start:
|
|
* @self: a #GcalEventWidget
|
|
*
|
|
* Retrieves the visible start date of this widget. This
|
|
* may differ from the event's start date.
|
|
*
|
|
* Returns: (transfer none): a #GDateTime
|
|
*/
|
|
GDateTime*
|
|
gcal_event_widget_get_date_start (GcalEventWidget *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_EVENT_WIDGET (self), NULL);
|
|
|
|
return self->dt_start;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_set_date_start:
|
|
* @self: a #GcalEventWidget
|
|
* @date_end: the start date of this widget
|
|
*
|
|
* Sets the visible start date of this widget. This
|
|
* may differ from the event's start date, but cannot
|
|
* be before it.
|
|
*/
|
|
void
|
|
gcal_event_widget_set_date_start (GcalEventWidget *self,
|
|
GDateTime *date_start)
|
|
{
|
|
g_return_if_fail (GCAL_IS_EVENT_WIDGET (self));
|
|
|
|
if (self->dt_start != date_start &&
|
|
(!self->dt_start || !date_start ||
|
|
(self->dt_start && date_start && !g_date_time_equal (self->dt_start, date_start))))
|
|
{
|
|
/* The start date should never be before the event's start date */
|
|
if (date_start && g_date_time_compare (date_start, gcal_event_get_date_start (self->event)) < 0)
|
|
return;
|
|
|
|
g_clear_pointer (&self->dt_start, g_date_time_unref);
|
|
self->dt_start = g_date_time_ref (date_start);
|
|
|
|
gcal_event_widget_update_style (self);
|
|
|
|
g_object_notify (G_OBJECT (self), "date-start");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_set_timestamp_policy:
|
|
* @self: a #GcalEventWidget
|
|
* @policy: the timestamp policy
|
|
*
|
|
* Sets whether to show the start time, end time, or no time for the
|
|
* event. Depending on the event's kind, it will be an hour or a day.
|
|
*/
|
|
void
|
|
gcal_event_widget_set_timestamp_policy (GcalEventWidget *self,
|
|
GcalTimestampPolicy policy)
|
|
{
|
|
g_return_if_fail (GCAL_IS_EVENT_WIDGET (self));
|
|
g_return_if_fail (policy >= GCAL_TIMESTAMP_POLICY_NONE && policy <= GCAL_TIMESTAMP_POLICY_END);
|
|
|
|
if (self->timestamp_policy != policy)
|
|
{
|
|
self->timestamp_policy = policy;
|
|
|
|
gcal_event_widget_update_timestamp (self);
|
|
|
|
g_object_notify (G_OBJECT (self), "timestamp-policy");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_show_preview:
|
|
* @self: a #GcalEventWidget
|
|
* @callback: (nullable): callback for the event preview
|
|
* @user_data: (nullable): user data for @callback
|
|
*
|
|
* Shows an event preview popover for this event widget, and
|
|
* calls @callback when the popover is either dismissed, or
|
|
* the edit button is clicked.
|
|
*/
|
|
void
|
|
gcal_event_widget_show_preview (GcalEventWidget *self,
|
|
GcalEventPreviewCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
PreviewData *data;
|
|
GtkWidget *event_popover;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
data = g_new0 (PreviewData, 1);
|
|
data->event_widget = self;
|
|
data->callback = callback;
|
|
data->user_data = user_data;
|
|
|
|
event_popover = gcal_event_popover_new (self->context, self->event);
|
|
gtk_widget_set_parent (event_popover, GTK_WIDGET (self));
|
|
g_signal_connect (event_popover, "closed", G_CALLBACK (on_event_popover_closed_cb), data);
|
|
g_signal_connect (event_popover, "edit", G_CALLBACK (on_event_popover_edit_cb), data);
|
|
gtk_popover_popup (GTK_POPOVER (event_popover));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_get_event:
|
|
* @self: a #GcalEventWidget
|
|
*
|
|
* Retrieves the @GcalEvent this widget represents.
|
|
*
|
|
* Returns: (transfer none): a #GcalEvent
|
|
*/
|
|
GcalEvent*
|
|
gcal_event_widget_get_event (GcalEventWidget *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_EVENT_WIDGET (self), NULL);
|
|
|
|
return self->event;
|
|
}
|
|
|
|
GtkWidget*
|
|
gcal_event_widget_clone (GcalEventWidget *widget)
|
|
{
|
|
GtkWidget *new_widget;
|
|
|
|
new_widget = gcal_event_widget_new (widget->context, widget->event);
|
|
gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (new_widget), widget->read_only);
|
|
|
|
return new_widget;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_equal:
|
|
* @widget1: an #GcalEventWidget representing an event
|
|
* @widget2: an #GcalEventWidget representing an event
|
|
*
|
|
* Check if two widget represent the same event.
|
|
*
|
|
* Returns: %TRUE if both widget represent the same event,
|
|
* false otherwise
|
|
**/
|
|
gboolean
|
|
gcal_event_widget_equal (GcalEventWidget *widget1,
|
|
GcalEventWidget *widget2)
|
|
{
|
|
return g_strcmp0 (gcal_event_get_uid (widget1->event), gcal_event_get_uid (widget2->event)) == 0;
|
|
}
|
|
|
|
/**
|
|
* gcal_event_widget_compare_by_length:
|
|
* @widget1:
|
|
* @widget2:
|
|
*
|
|
* Compare two widgets by the duration of the events they represent. From shortest to longest span.
|
|
*
|
|
* Returns: negative value if a < b ; zero if a = b ; positive value if a > b
|
|
**/
|
|
gint
|
|
gcal_event_widget_compare_by_length (GcalEventWidget *widget1,
|
|
GcalEventWidget *widget2)
|
|
{
|
|
time_t time_s1, time_s2;
|
|
time_t time_e1, time_e2;
|
|
|
|
time_e1 = time_s1 = g_date_time_to_unix (widget1->dt_start);
|
|
time_e2 = time_s2 = g_date_time_to_unix (widget2->dt_start);
|
|
|
|
if (widget1->dt_end != NULL)
|
|
time_e1 = g_date_time_to_unix (widget1->dt_end);
|
|
if (widget2->dt_end)
|
|
time_e2 = g_date_time_to_unix (widget2->dt_end);
|
|
|
|
return (time_e2 - time_s2) - (time_e1 - time_s1);
|
|
}
|
|
|
|
gint
|
|
gcal_event_widget_compare_by_start_date (GcalEventWidget *widget1,
|
|
GcalEventWidget *widget2)
|
|
{
|
|
return g_date_time_compare (widget1->dt_start, widget2->dt_start);
|
|
}
|
|
|
|
gint
|
|
gcal_event_widget_sort_events (GcalEventWidget *widget1,
|
|
GcalEventWidget *widget2)
|
|
{
|
|
g_autoptr (GDateTime) dt_time1 = NULL;
|
|
g_autoptr (GDateTime) dt_time2 = NULL;
|
|
ICalTime *itt;
|
|
gint diff;
|
|
|
|
diff = gcal_event_is_multiday (widget2->event) - gcal_event_is_multiday (widget1->event);
|
|
|
|
if (diff != 0)
|
|
return diff;
|
|
|
|
diff = gcal_event_widget_compare_by_start_date (widget1, widget2);
|
|
|
|
if (diff != 0)
|
|
return diff;
|
|
|
|
diff = gcal_event_widget_compare_by_length (widget1, widget2);
|
|
|
|
if (diff != 0)
|
|
return diff;
|
|
|
|
itt = e_cal_component_get_last_modified (gcal_event_get_component (widget1->event));
|
|
if (itt)
|
|
dt_time1 = gcal_date_time_from_icaltime (itt);
|
|
g_clear_object (&itt);
|
|
|
|
itt = e_cal_component_get_last_modified (gcal_event_get_component (widget2->event));
|
|
if (itt)
|
|
dt_time2 = gcal_date_time_from_icaltime (itt);
|
|
g_clear_object (&itt);
|
|
|
|
return dt_time1 && dt_time2 ? g_date_time_compare (dt_time2, dt_time1) : 0;
|
|
}
|