views/dazzling-month-view: Handle events
Most of the code is copied from GcalMonthView, so it sucks slightly. But it seems to work reasonably well so far.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#include "gcal-dazzling-month-view.h"
|
||||
#include "gcal-debug.h"
|
||||
#include "gcal-month-view-row.h"
|
||||
#include "gcal-range-tree.h"
|
||||
#include "gcal-timeline-subscriber.h"
|
||||
#include "gcal-utils.h"
|
||||
#include "gcal-view-private.h"
|
||||
@@ -50,6 +51,8 @@ struct _GcalDazzlingMonthView
|
||||
GtkWidget *weekday_label[7];
|
||||
|
||||
GPtrArray *week_rows;
|
||||
GcalRangeTree *events;
|
||||
GHashTable *events2;
|
||||
|
||||
/* Ranges from [0.0, 1.0] */
|
||||
gdouble row_offset;
|
||||
@@ -511,6 +514,44 @@ static void
|
||||
gcal_dazzling_month_view_add_event (GcalTimelineSubscriber *subscriber,
|
||||
GcalEvent *event)
|
||||
{
|
||||
GcalDazzlingMonthView *self;
|
||||
|
||||
GCAL_ENTRY;
|
||||
|
||||
g_assert (event != NULL);
|
||||
|
||||
self = GCAL_DAZZLING_MONTH_VIEW (subscriber);
|
||||
|
||||
if (!g_hash_table_add (self->events2, g_object_ref (event)))
|
||||
{
|
||||
g_warning ("Event with uuid: %s already added", gcal_event_get_uid (event));
|
||||
GCAL_RETURN ();
|
||||
}
|
||||
|
||||
for (guint i = 0; i < self->week_rows->len; i++)
|
||||
{
|
||||
g_autoptr (GcalRange) row_range = NULL;
|
||||
GcalRangePosition position;
|
||||
GcalRangeOverlap overlap;
|
||||
GcalMonthViewRow *row;
|
||||
|
||||
row = g_ptr_array_index (self->week_rows, i);
|
||||
row_range = gcal_month_view_row_get_range (row);
|
||||
|
||||
overlap = gcal_range_calculate_overlap (row_range, gcal_event_get_range (event), &position);
|
||||
if (overlap != GCAL_RANGE_NO_OVERLAP)
|
||||
{
|
||||
g_message ("Adding event %s to row %u", gcal_event_get_uid (event), i);
|
||||
gcal_month_view_row_add_event (row, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (position == GCAL_RANGE_AFTER)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GCAL_EXIT;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -523,6 +564,48 @@ static void
|
||||
gcal_dazzling_month_view_remove_event (GcalTimelineSubscriber *subscriber,
|
||||
GcalEvent *event)
|
||||
{
|
||||
g_autoptr (GcalEvent) owned_event = NULL;
|
||||
GcalDazzlingMonthView *self;
|
||||
|
||||
GCAL_ENTRY;
|
||||
|
||||
g_assert (event != NULL);
|
||||
|
||||
self = GCAL_DAZZLING_MONTH_VIEW (subscriber);
|
||||
|
||||
/* Keep event alive while removing it */
|
||||
owned_event = g_object_ref (event);
|
||||
|
||||
if (!g_hash_table_remove (self->events2, event))
|
||||
{
|
||||
g_warning ("Event with uuid: %s not in %s", gcal_event_get_uid (event), G_OBJECT_TYPE_NAME (self));
|
||||
GCAL_RETURN ();
|
||||
}
|
||||
|
||||
for (guint i = 0; i < self->week_rows->len; i++)
|
||||
{
|
||||
g_autoptr (GcalRange) row_range = NULL;
|
||||
GcalRangePosition position;
|
||||
GcalRangeOverlap overlap;
|
||||
GcalMonthViewRow *row;
|
||||
|
||||
row = g_ptr_array_index (self->week_rows, i);
|
||||
row_range = gcal_month_view_row_get_range (row);
|
||||
|
||||
overlap = gcal_range_calculate_overlap (row_range, gcal_event_get_range (event), &position);
|
||||
if (overlap != GCAL_RANGE_NO_OVERLAP)
|
||||
{
|
||||
g_message ("Remove event %s to row %u", gcal_event_get_uid (event), i);
|
||||
gcal_month_view_row_remove_event (row, event);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (position == GCAL_RANGE_AFTER)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GCAL_EXIT;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -814,6 +897,9 @@ gcal_dazzling_month_view_dispose (GObject *object)
|
||||
|
||||
GCAL_ENTRY;
|
||||
|
||||
g_clear_pointer (&self->events2, g_hash_table_destroy);
|
||||
g_clear_pointer (&self->events, gcal_range_tree_unref);
|
||||
|
||||
g_clear_object (&self->kinetic_scroll_animation);
|
||||
g_clear_object (&self->row_offset_animation);
|
||||
|
||||
@@ -932,6 +1018,9 @@ gcal_dazzling_month_view_init (GcalDazzlingMonthView *self)
|
||||
{
|
||||
g_autoptr (GDateTime) now = NULL;
|
||||
|
||||
self->events = gcal_range_tree_new_with_free_func (g_object_unref);
|
||||
self->events2 = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
update_weekday_labels (self);
|
||||
|
||||
|
||||
@@ -24,9 +24,19 @@
|
||||
#include "config.h"
|
||||
#include "gcal-date-time-utils.h"
|
||||
#include "gcal-debug.h"
|
||||
#include "gcal-event-widget.h"
|
||||
#include "gcal-month-cell.h"
|
||||
#include "gcal-month-view-row.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *event_widget;
|
||||
gboolean visible;
|
||||
guint8 length;
|
||||
guint8 height;
|
||||
guint8 cell;
|
||||
} GcalEventBlock;
|
||||
|
||||
struct _GcalMonthViewRow
|
||||
{
|
||||
GtkWidget parent;
|
||||
@@ -34,6 +44,33 @@ struct _GcalMonthViewRow
|
||||
GtkWidget *day_cells[7];
|
||||
|
||||
GcalRange *range;
|
||||
|
||||
/*
|
||||
* Hash to keep children widgets (all of them, parent widgets and its parts if there's any),
|
||||
* uuid as key and a list of all the instances of the event as value. Here, the first widget
|
||||
* on the list is the master, and the rest are the parts. Note: the master is a part itself.
|
||||
*/
|
||||
GHashTable *children;
|
||||
|
||||
/*
|
||||
* Hash containing single-cell events, day of the month, on month-view, month of the year on
|
||||
* year-view as key anda list of the events that belongs to this cell
|
||||
*/
|
||||
GHashTable *single_cell_children;
|
||||
|
||||
/*
|
||||
* A sorted list containing multiday events. This one contains only parents events, to find out
|
||||
* its parts @children will be used.
|
||||
*/
|
||||
GList *multi_cell_children;
|
||||
|
||||
/* Hash containing cells that who has overflow per list of hidden widgets */
|
||||
GHashTable *overflow_cells;
|
||||
|
||||
gboolean needs_reallocation;
|
||||
gint last_width;
|
||||
gint last_height;
|
||||
|
||||
GcalContext *context;
|
||||
};
|
||||
|
||||
@@ -49,6 +86,676 @@ enum
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
|
||||
|
||||
static void
|
||||
setup_child_widget (GcalMonthViewRow *self,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
gtk_widget_insert_after (widget, GTK_WIDGET (self), self->day_cells[6]);
|
||||
|
||||
// g_signal_connect_object (widget, "activate", G_CALLBACK (on_event_activated_cb), self, 0);
|
||||
// g_signal_connect_object (widget, "notify::visible", G_CALLBACK (on_event_widget_visibility_changed_cb), self, 0);
|
||||
}
|
||||
|
||||
static inline guint
|
||||
get_child_weekday (GcalMonthViewRow *self,
|
||||
GcalEventWidget *child)
|
||||
{
|
||||
GcalEvent *event;
|
||||
GDateTime *dt;
|
||||
gint weekday;
|
||||
|
||||
event = gcal_event_widget_get_event (child);
|
||||
dt = gcal_event_get_date_start (event);
|
||||
|
||||
/* Don't adjust the date when the event is all day */
|
||||
if (gcal_event_get_all_day (event))
|
||||
{
|
||||
weekday = g_date_time_get_day_of_week (dt) % 7;
|
||||
}
|
||||
else
|
||||
{
|
||||
dt = g_date_time_to_local (dt);
|
||||
weekday = g_date_time_get_day_of_week (dt) % 7;
|
||||
|
||||
g_clear_pointer (&dt, g_date_time_unref);
|
||||
}
|
||||
|
||||
return weekday;
|
||||
}
|
||||
|
||||
static void
|
||||
add_event_widget (GcalMonthViewRow *self,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
GcalEvent *event;
|
||||
const gchar *uuid;
|
||||
GList *l = NULL;
|
||||
|
||||
g_return_if_fail (GCAL_IS_EVENT_WIDGET (widget));
|
||||
g_return_if_fail (gtk_widget_get_parent (widget) == NULL);
|
||||
|
||||
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
|
||||
uuid = gcal_event_get_uid (event);
|
||||
|
||||
/* inserting in all children hash */
|
||||
if (g_hash_table_contains (self->children, uuid))
|
||||
{
|
||||
g_warning ("Event with uuid: %s already added", uuid);
|
||||
g_object_unref (widget);
|
||||
return;
|
||||
}
|
||||
|
||||
l = g_list_append (l, widget);
|
||||
g_hash_table_insert (self->children, g_strdup (uuid), l);
|
||||
|
||||
if (gcal_event_is_multiday (event))
|
||||
{
|
||||
self->multi_cell_children = g_list_insert_sorted (self->multi_cell_children,
|
||||
widget,
|
||||
(GCompareFunc) gcal_event_widget_sort_events);
|
||||
}
|
||||
else
|
||||
{
|
||||
guint weekday;
|
||||
|
||||
weekday = get_child_weekday (self, GCAL_EVENT_WIDGET (widget));
|
||||
|
||||
l = g_hash_table_lookup (self->single_cell_children, GINT_TO_POINTER (weekday));
|
||||
l = g_list_insert_sorted (l, widget, (GCompareFunc) gcal_event_widget_compare_by_start_date);
|
||||
|
||||
if (g_list_length (l) != 1)
|
||||
g_hash_table_steal (self->single_cell_children, GINT_TO_POINTER (weekday));
|
||||
|
||||
g_hash_table_insert (self->single_cell_children, GINT_TO_POINTER (weekday), l);
|
||||
}
|
||||
|
||||
setup_child_widget (self, widget);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_event_widget (GcalMonthViewRow *self,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
GtkWidget *master_widget;
|
||||
GcalEvent *event;
|
||||
GList *l, *aux;
|
||||
const gchar *uuid;
|
||||
|
||||
g_assert (gtk_widget_get_parent (widget) == GTK_WIDGET (self));
|
||||
|
||||
if (!GCAL_IS_EVENT_WIDGET (widget))
|
||||
return;
|
||||
|
||||
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (widget));
|
||||
uuid = gcal_event_get_uid (event);
|
||||
|
||||
l = g_hash_table_lookup (self->children, uuid);
|
||||
|
||||
if (l)
|
||||
{
|
||||
gtk_widget_unparent (widget);
|
||||
|
||||
master_widget = (GtkWidget*) l->data;
|
||||
if (widget == master_widget)
|
||||
{
|
||||
if (g_list_find (self->multi_cell_children, widget) != NULL)
|
||||
{
|
||||
self->multi_cell_children = g_list_remove (self->multi_cell_children, widget);
|
||||
|
||||
aux = g_list_next (l);
|
||||
if (aux != NULL)
|
||||
{
|
||||
l->next = NULL;
|
||||
aux->prev = NULL;
|
||||
g_list_foreach (aux, (GFunc) gtk_widget_unparent, NULL);
|
||||
g_list_free (aux);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
|
||||
/*
|
||||
* When an event is changed, we can't rely on it's old date
|
||||
* to remove the corresponding widget. Because of that, we have
|
||||
* to iter through all the widgets to see which one matches
|
||||
*/
|
||||
g_hash_table_iter_init (&iter, self->single_cell_children);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
gboolean should_break;
|
||||
|
||||
should_break = FALSE;
|
||||
|
||||
for (aux = value; aux != NULL; aux = aux->next)
|
||||
{
|
||||
if (aux->data == widget)
|
||||
{
|
||||
aux = g_list_remove (g_list_copy (value), widget);
|
||||
|
||||
/*
|
||||
* If we removed the event and there's no event left for
|
||||
* the day, remove the key from the table. If there are
|
||||
* events for that day, replace the list.
|
||||
*/
|
||||
if (!aux)
|
||||
g_hash_table_remove (self->single_cell_children, key);
|
||||
else
|
||||
g_hash_table_replace (self->single_cell_children, key, aux);
|
||||
|
||||
should_break = TRUE;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_break)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
l = g_list_remove (g_list_copy (l), widget);
|
||||
|
||||
if (!l)
|
||||
g_hash_table_remove (self->children, uuid);
|
||||
else
|
||||
g_hash_table_replace (self->children, g_strdup (uuid), l);
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
calculate_event_cells (GcalMonthViewRow *self,
|
||||
GcalEvent *event,
|
||||
gint *out_first_cell,
|
||||
gint *out_last_cell)
|
||||
{
|
||||
gboolean all_day;
|
||||
|
||||
all_day = gcal_event_get_all_day (event);
|
||||
|
||||
/* Start date */
|
||||
if (out_first_cell)
|
||||
{
|
||||
g_autoptr (GDateTime) start_date = NULL;
|
||||
gint first_cell = 0;
|
||||
|
||||
start_date = gcal_event_get_date_start (event);
|
||||
start_date = all_day ? g_date_time_ref (start_date) : g_date_time_to_local (start_date);
|
||||
|
||||
if (gcal_range_contains_datetime (self->range, start_date))
|
||||
first_cell = g_date_time_get_day_of_week (start_date) % 7;
|
||||
|
||||
*out_first_cell = first_cell;
|
||||
}
|
||||
|
||||
/*
|
||||
* The logic for the end date is the same, except that we have to check
|
||||
* if the event is all day or not.
|
||||
*/
|
||||
if (out_last_cell)
|
||||
{
|
||||
g_autoptr (GDateTime) end_date = NULL;
|
||||
gint last_cell = 6;
|
||||
|
||||
end_date = gcal_event_get_date_end (event);
|
||||
end_date = all_day ? g_date_time_ref (end_date) : g_date_time_to_local (end_date);
|
||||
|
||||
if (gcal_range_contains_datetime (self->range, end_date))
|
||||
{
|
||||
last_cell = g_date_time_get_day_of_week (end_date) % 7;
|
||||
|
||||
/* If the event is all day, we have to subtract 1 to find the the real date */
|
||||
if (all_day)
|
||||
last_cell--;
|
||||
}
|
||||
|
||||
*out_last_cell = last_cell;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup_overflow_information (GcalMonthViewRow *self)
|
||||
{
|
||||
g_autoptr (GList) widget_lists = NULL;
|
||||
GList *l = NULL;
|
||||
|
||||
/* Remove every widget' parts, but the master widget */
|
||||
widget_lists = g_hash_table_get_values (self->children);
|
||||
|
||||
for (l = widget_lists; l; l = l->next)
|
||||
{
|
||||
g_autoptr (GList) widgets = g_list_copy (l->data);
|
||||
GList *l2;
|
||||
|
||||
for (l2 = widgets->next; l2; l2 = l2->next)
|
||||
remove_event_widget (self, l2->data);
|
||||
}
|
||||
|
||||
/* Clean overflow information */
|
||||
g_hash_table_remove_all (self->overflow_cells);
|
||||
}
|
||||
|
||||
static void
|
||||
count_events_per_day (GcalMonthViewRow *self,
|
||||
gint events_per_day[7])
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
GList *children;
|
||||
|
||||
/* Multiday events */
|
||||
for (GList *l = self->multi_cell_children; l; l = l->next)
|
||||
{
|
||||
GcalEvent *event;
|
||||
gint first_cell;
|
||||
gint last_cell;
|
||||
gint i;
|
||||
|
||||
if (!gtk_widget_get_visible (l->data))
|
||||
continue;
|
||||
|
||||
event = gcal_event_widget_get_event (l->data);
|
||||
|
||||
calculate_event_cells (self, event, &first_cell, &last_cell);
|
||||
|
||||
for (i = first_cell; i <= last_cell; i++)
|
||||
events_per_day[i]++;
|
||||
}
|
||||
|
||||
/* Single day events */
|
||||
g_hash_table_iter_init (&iter, self->single_cell_children);
|
||||
while (g_hash_table_iter_next (&iter, &key, (gpointer*) &children))
|
||||
{
|
||||
gint cell;
|
||||
|
||||
cell = GPOINTER_TO_INT (key);
|
||||
|
||||
for (GList *l = children; l; l = l->next)
|
||||
{
|
||||
if (!gtk_widget_get_visible (l->data))
|
||||
continue;
|
||||
|
||||
events_per_day[cell]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static GPtrArray*
|
||||
calculate_multiday_event_blocks (GcalMonthViewRow *self,
|
||||
GtkWidget *event_widget,
|
||||
gdouble *vertical_cell_space,
|
||||
gdouble *size_left,
|
||||
gint *events_at_day,
|
||||
gint *allocated_events_at_day)
|
||||
{
|
||||
GcalEventBlock *block;
|
||||
GcalEvent *event;
|
||||
GPtrArray *blocks;
|
||||
gboolean was_visible;
|
||||
gdouble old_y;
|
||||
gdouble y;
|
||||
gint first_cell;
|
||||
gint last_cell;
|
||||
gint i;
|
||||
|
||||
GCAL_ENTRY;
|
||||
|
||||
old_y = -1.0;
|
||||
was_visible = FALSE;
|
||||
|
||||
/* Get the event cells */
|
||||
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
|
||||
calculate_event_cells (self, event, &first_cell, &last_cell);
|
||||
|
||||
blocks = g_ptr_array_new_full (last_cell - first_cell + 1, g_free);
|
||||
block = NULL;
|
||||
|
||||
GCAL_TRACE_MSG ("Positioning '%s' (multiday) from %d to %d", gcal_event_get_summary (event), first_cell, last_cell);
|
||||
|
||||
for (i = first_cell; i <= last_cell; i++)
|
||||
{
|
||||
gboolean visible_at_range;
|
||||
gboolean different_row;
|
||||
gboolean will_overflow;
|
||||
gdouble real_height;
|
||||
gint remaining_events;
|
||||
gint minimum_height;
|
||||
|
||||
real_height = size_left[i];
|
||||
|
||||
/* Calculate the minimum event height */
|
||||
gtk_widget_measure (GTK_WIDGET (event_widget),
|
||||
GTK_ORIENTATION_VERTICAL,
|
||||
-1,
|
||||
&minimum_height,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
/* Count this event at this cell */
|
||||
different_row = i / 7 != (i - 1) / 7;
|
||||
remaining_events = events_at_day[i] - allocated_events_at_day[i];
|
||||
will_overflow = remaining_events * minimum_height > real_height;
|
||||
|
||||
if (will_overflow)
|
||||
real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (self->day_cells[i]));
|
||||
|
||||
visible_at_range = real_height >= minimum_height;
|
||||
|
||||
if (visible_at_range)
|
||||
allocated_events_at_day[i]++;
|
||||
|
||||
y = vertical_cell_space[i] - size_left[i];
|
||||
|
||||
/* At the first iteration, make was_visible and visible_at_range equal */
|
||||
if (i == first_cell)
|
||||
was_visible = visible_at_range;
|
||||
|
||||
if (!block || y != old_y || different_row || was_visible != visible_at_range)
|
||||
{
|
||||
GCAL_TRACE_MSG ("Breaking event at cell %d", i);
|
||||
|
||||
/* Only create a new event widget after the first one is consumed */
|
||||
if (block)
|
||||
{
|
||||
GcalEvent *event;
|
||||
GList *aux;
|
||||
|
||||
GCAL_TRACE_MSG ("Cloning event widget");
|
||||
|
||||
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
|
||||
|
||||
event_widget = gcal_event_widget_clone (GCAL_EVENT_WIDGET (event_widget));
|
||||
setup_child_widget (self, event_widget);
|
||||
|
||||
aux = g_hash_table_lookup (self->children, gcal_event_get_uid (event));
|
||||
aux = g_list_append (aux, event_widget);
|
||||
}
|
||||
|
||||
old_y = y;
|
||||
|
||||
/* Add a new block */
|
||||
block = g_new (GcalEventBlock, 1);
|
||||
block->event_widget = event_widget;
|
||||
block->visible = visible_at_range;
|
||||
block->height = minimum_height;
|
||||
block->length = 1;
|
||||
block->cell = i;
|
||||
|
||||
g_ptr_array_add (blocks, block);
|
||||
}
|
||||
else
|
||||
{
|
||||
block->length++;
|
||||
}
|
||||
|
||||
was_visible = visible_at_range;
|
||||
}
|
||||
|
||||
GCAL_RETURN (blocks);
|
||||
}
|
||||
|
||||
static void
|
||||
allocate_multiday_events (GcalMonthViewRow *self,
|
||||
gdouble *vertical_cell_space,
|
||||
gdouble *size_left,
|
||||
gint *events_at_day,
|
||||
gint *allocated_events_at_day,
|
||||
gint baseline)
|
||||
{
|
||||
GtkAllocation event_allocation;
|
||||
GList *l;
|
||||
gboolean is_rtl;
|
||||
|
||||
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
||||
|
||||
for (l = self->multi_cell_children; l; l = g_list_next (l))
|
||||
{
|
||||
g_autoptr (GPtrArray) blocks = NULL;
|
||||
GtkWidget *child_widget;
|
||||
GcalEvent *event;
|
||||
gint block_idx;
|
||||
|
||||
child_widget = (GtkWidget*) l->data;
|
||||
|
||||
if (!gtk_widget_should_layout (child_widget))
|
||||
continue;
|
||||
|
||||
event = gcal_event_widget_get_event (l->data);
|
||||
|
||||
if (gcal_range_calculate_overlap (self->range, gcal_event_get_range (event), NULL) == GCAL_RANGE_NO_OVERLAP)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Multiday events can "break" following these rules:
|
||||
*
|
||||
* - Break when line changed
|
||||
* - Break when the vertical position in the cells changed
|
||||
* - Break when the previous part's visibility is different than the current
|
||||
*/
|
||||
blocks = calculate_multiday_event_blocks (self,
|
||||
child_widget,
|
||||
vertical_cell_space,
|
||||
size_left,
|
||||
events_at_day,
|
||||
allocated_events_at_day);
|
||||
|
||||
for (block_idx = 0; block_idx < blocks->len; block_idx++)
|
||||
{
|
||||
g_autoptr (GDateTime) range_start = NULL;
|
||||
g_autoptr (GDateTime) dt_start = NULL;
|
||||
g_autoptr (GDateTime) dt_end = NULL;
|
||||
GcalEventBlock *block;
|
||||
GtkAllocation first_cell_allocation;
|
||||
GtkAllocation last_cell_allocation;
|
||||
GtkWidget *last_month_cell;
|
||||
GtkWidget *month_cell;
|
||||
GList *aux;
|
||||
gdouble pos_x;
|
||||
gdouble pos_y;
|
||||
gdouble width;
|
||||
gint last_block_cell;
|
||||
gint minimum_height;
|
||||
gint length;
|
||||
gint cell;
|
||||
gint day;
|
||||
gint j;
|
||||
|
||||
block = g_ptr_array_index (blocks, block_idx);
|
||||
length = block->length;
|
||||
cell = block->cell;
|
||||
last_block_cell = cell + length - 1;
|
||||
day = cell;
|
||||
|
||||
child_widget = block->event_widget;
|
||||
|
||||
/* No space left, add to the overflow and continue */
|
||||
if (!block->visible)
|
||||
{
|
||||
gint idx;
|
||||
|
||||
gtk_widget_set_child_visible (child_widget, FALSE);
|
||||
|
||||
for (idx = cell; idx < cell + length; idx++)
|
||||
{
|
||||
aux = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (idx));
|
||||
aux = g_list_append (aux, child_widget);
|
||||
|
||||
if (g_list_length (aux) == 1)
|
||||
g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (idx), aux);
|
||||
else
|
||||
g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (idx), g_list_copy (aux));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
/* Retrieve the cell widget. On RTL languages, swap the first and last cells */
|
||||
month_cell = self->day_cells[cell];
|
||||
last_month_cell = self->day_cells[last_block_cell];
|
||||
|
||||
if (is_rtl)
|
||||
{
|
||||
GtkWidget *aux = month_cell;
|
||||
month_cell = last_month_cell;
|
||||
last_month_cell = aux;
|
||||
}
|
||||
|
||||
gtk_widget_get_allocation (month_cell, &first_cell_allocation);
|
||||
gtk_widget_get_allocation (last_month_cell, &last_cell_allocation);
|
||||
|
||||
gtk_widget_set_child_visible (child_widget, TRUE);
|
||||
|
||||
/*
|
||||
* Setup the widget's start date as the first day of the row,
|
||||
* and the widget's end date as the last day of the row. We don't
|
||||
* have to worry about the dates, since GcalEventWidget performs
|
||||
* some checks and only applies the dates when it's valid.
|
||||
*/
|
||||
range_start = gcal_range_get_start (self->range);
|
||||
dt_start = g_date_time_new (g_date_time_get_timezone (gcal_event_get_date_start (event)),
|
||||
g_date_time_get_year (range_start),
|
||||
g_date_time_get_month (range_start),
|
||||
g_date_time_get_day_of_month (range_start) + day,
|
||||
0, 0, 0);
|
||||
|
||||
/* FIXME: use end date's timezone here */
|
||||
dt_end = g_date_time_add_days (dt_start, length);
|
||||
|
||||
gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (child_widget), dt_start);
|
||||
gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (child_widget), dt_end);
|
||||
|
||||
pos_x = first_cell_allocation.x;
|
||||
pos_y = first_cell_allocation.y + gcal_month_cell_get_header_height (GCAL_MONTH_CELL (month_cell));
|
||||
width = last_cell_allocation.x + last_cell_allocation.width - first_cell_allocation.x;
|
||||
|
||||
/*
|
||||
* We can only get the minimum height after making all these calculations,
|
||||
* otherwise GTK complains about allocating without calling get_preferred_height.
|
||||
*/
|
||||
gtk_widget_measure (GTK_WIDGET (child_widget),
|
||||
GTK_ORIENTATION_VERTICAL,
|
||||
-1,
|
||||
&minimum_height,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
event_allocation.x = pos_x;
|
||||
event_allocation.y = pos_y + vertical_cell_space[cell] - size_left[cell];
|
||||
event_allocation.width = width;
|
||||
event_allocation.height = minimum_height;
|
||||
|
||||
gtk_widget_size_allocate (child_widget, &event_allocation, baseline);
|
||||
|
||||
/* update size_left */
|
||||
for (j = 0; j < length; j++)
|
||||
size_left[cell + j] -= minimum_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
allocate_single_day_events (GcalMonthViewRow *self,
|
||||
gdouble *vertical_cell_space,
|
||||
gdouble *size_left,
|
||||
gint *events_at_day,
|
||||
gint *allocated_events_at_day,
|
||||
gint baseline)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
GtkAllocation event_allocation;
|
||||
GtkAllocation cell_allocation;
|
||||
gpointer key, value;
|
||||
|
||||
g_hash_table_iter_init (&iter, self->single_cell_children);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
GtkWidget *month_cell;
|
||||
gboolean will_overflow;
|
||||
gint day;
|
||||
|
||||
day = GPOINTER_TO_INT (key);
|
||||
month_cell = self->day_cells[day];
|
||||
|
||||
gtk_widget_get_allocation (month_cell, &cell_allocation);
|
||||
|
||||
for (GList *l = (GList *)value; l; l = l->next)
|
||||
{
|
||||
GcalEvent *event;
|
||||
GtkWidget *child_widget;
|
||||
gdouble real_height;
|
||||
gdouble pos_y;
|
||||
gdouble pos_x;
|
||||
gdouble width;
|
||||
gint remaining_events;
|
||||
gint minimum_height;
|
||||
|
||||
child_widget = l->data;
|
||||
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (child_widget));
|
||||
|
||||
if (!gtk_widget_should_layout (child_widget))
|
||||
continue;
|
||||
|
||||
if (gcal_range_calculate_overlap (self->range, gcal_event_get_range (event), NULL) == GCAL_RANGE_NO_OVERLAP)
|
||||
continue;
|
||||
|
||||
gtk_widget_measure (GTK_WIDGET (child_widget),
|
||||
GTK_ORIENTATION_VERTICAL,
|
||||
-1,
|
||||
&minimum_height,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
/* Check for overflow */
|
||||
remaining_events = events_at_day[day] - allocated_events_at_day[day];
|
||||
will_overflow = remaining_events * minimum_height >= size_left[day];
|
||||
real_height = size_left[day];
|
||||
|
||||
if (will_overflow)
|
||||
real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (month_cell));
|
||||
|
||||
/* No space left, add to the overflow and continue */
|
||||
if (real_height < minimum_height)
|
||||
{
|
||||
gtk_widget_set_child_visible (child_widget, FALSE);
|
||||
|
||||
l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (day));
|
||||
l = g_list_append (l, child_widget);
|
||||
|
||||
if (g_list_length (l) == 1)
|
||||
g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (day), l);
|
||||
else
|
||||
g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (day), g_list_copy (l));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
allocated_events_at_day[day]++;
|
||||
|
||||
gtk_widget_set_child_visible (child_widget, TRUE);
|
||||
|
||||
pos_x = cell_allocation.x;
|
||||
pos_y = cell_allocation.y + gcal_month_cell_get_header_height (GCAL_MONTH_CELL (month_cell));
|
||||
width = cell_allocation.width;
|
||||
|
||||
event_allocation.x = pos_x;
|
||||
event_allocation.y = pos_y + vertical_cell_space[day] - size_left[day];
|
||||
event_allocation.width = width;
|
||||
event_allocation.height = minimum_height;
|
||||
|
||||
gtk_widget_set_child_visible (child_widget, TRUE);
|
||||
gtk_widget_size_allocate (child_widget, &event_allocation, baseline);
|
||||
|
||||
size_left[day] -= minimum_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GtkWidget overrides
|
||||
*/
|
||||
@@ -110,6 +817,7 @@ gcal_month_view_row_size_allocate (GtkWidget *widget,
|
||||
is_ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
|
||||
cell_width = width / 7.0;
|
||||
|
||||
/* Month cells */
|
||||
for (guint i = 0; i < 7; i++)
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
@@ -123,16 +831,53 @@ gcal_month_view_row_size_allocate (GtkWidget *widget,
|
||||
cell = is_ltr ? self->day_cells[i] : self->day_cells[7 - i + 1];
|
||||
gtk_widget_size_allocate (cell, &allocation, baseline);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gcal_month_view_row_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (widget);
|
||||
/* Event widgets */
|
||||
if (self->needs_reallocation || self->last_width != width || self->last_height != height)
|
||||
{
|
||||
gdouble vertical_cell_space [7];
|
||||
gdouble size_left [7];
|
||||
gint allocated_events_at_day [7] = { 0, };
|
||||
gint events_at_day [7] = { 0, };
|
||||
|
||||
for (guint i = 0; i < 7; i++)
|
||||
gtk_widget_snapshot_child (widget, self->day_cells[i], snapshot);
|
||||
/* Remove every widget' parts, but the main widget */
|
||||
cleanup_overflow_information (self);
|
||||
|
||||
/* Event widgets */
|
||||
count_events_per_day (self, events_at_day);
|
||||
|
||||
for (guint i = 0; i < 7; i++)
|
||||
{
|
||||
gint content_space = gcal_month_cell_get_content_space (GCAL_MONTH_CELL (self->day_cells[i]));
|
||||
|
||||
vertical_cell_space[i] = content_space;
|
||||
size_left[i] = content_space;
|
||||
}
|
||||
|
||||
allocate_multiday_events (self, vertical_cell_space, size_left, events_at_day, allocated_events_at_day, baseline);
|
||||
allocate_single_day_events (self, vertical_cell_space, size_left, events_at_day, allocated_events_at_day, baseline);
|
||||
}
|
||||
else
|
||||
{
|
||||
GtkWidget *child;
|
||||
|
||||
for (child = gtk_widget_get_first_child (widget);
|
||||
child;
|
||||
child = gtk_widget_get_next_sibling (child))
|
||||
{
|
||||
GtkAllocation allocation;
|
||||
|
||||
if (!GCAL_IS_EVENT_WIDGET (child))
|
||||
continue;
|
||||
|
||||
gtk_widget_get_allocation (child, &allocation);
|
||||
gtk_widget_size_allocate (child, &allocation, baseline);
|
||||
}
|
||||
}
|
||||
|
||||
self->last_width = width;
|
||||
self->last_height = height;
|
||||
self->needs_reallocation = FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -144,10 +889,19 @@ static void
|
||||
gcal_month_view_row_dispose (GObject *object)
|
||||
{
|
||||
GcalMonthViewRow *self = (GcalMonthViewRow *)object;
|
||||
GtkWidget *child;
|
||||
|
||||
for (guint i = 0; i < 7; i++)
|
||||
g_clear_pointer (&self->day_cells[i], gtk_widget_unparent);
|
||||
|
||||
while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))) != NULL)
|
||||
remove_event_widget (self, child);
|
||||
|
||||
g_clear_pointer (&self->children, g_hash_table_destroy);
|
||||
g_clear_pointer (&self->single_cell_children, g_hash_table_destroy);
|
||||
g_clear_pointer (&self->overflow_cells, g_hash_table_destroy);
|
||||
g_clear_pointer (&self->multi_cell_children, g_list_free);
|
||||
|
||||
G_OBJECT_CLASS (gcal_month_view_row_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
@@ -212,7 +966,6 @@ gcal_month_view_row_class_init (GcalMonthViewRowClass *klass)
|
||||
|
||||
widget_class->measure = gcal_month_view_row_measure;
|
||||
widget_class->size_allocate = gcal_month_view_row_size_allocate;
|
||||
widget_class->snapshot = gcal_month_view_row_snapshot;
|
||||
|
||||
properties[PROP_CONTEXT] = g_param_spec_object ("context",
|
||||
"Context",
|
||||
@@ -228,6 +981,12 @@ gcal_month_view_row_class_init (GcalMonthViewRowClass *klass)
|
||||
static void
|
||||
gcal_month_view_row_init (GcalMonthViewRow *self)
|
||||
{
|
||||
self->last_width = -1;
|
||||
self->last_height = -1;
|
||||
self->children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_list_free);
|
||||
self->single_cell_children = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
|
||||
self->overflow_cells = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free);
|
||||
|
||||
for (guint i = 0; i < 7; i++)
|
||||
{
|
||||
self->day_cells[i] = gcal_month_cell_new ();
|
||||
@@ -278,6 +1037,7 @@ gcal_month_view_row_set_range (GcalMonthViewRow *self,
|
||||
GcalRange *range)
|
||||
{
|
||||
g_autoptr (GDateTime) start = NULL;
|
||||
GtkWidget *child;
|
||||
|
||||
g_return_if_fail (GCAL_IS_MONTH_VIEW_ROW (self));
|
||||
g_return_if_fail (range != NULL);
|
||||
@@ -290,7 +1050,17 @@ gcal_month_view_row_set_range (GcalMonthViewRow *self,
|
||||
g_clear_pointer (&self->range, gcal_range_unref);
|
||||
self->range = gcal_range_ref (range);
|
||||
|
||||
/* TODO: remove all widgets */
|
||||
/* Preemptively remove all event widgets */
|
||||
child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
||||
while (child)
|
||||
{
|
||||
GtkWidget *next = gtk_widget_get_next_sibling (child);
|
||||
|
||||
if (GCAL_IS_EVENT_WIDGET (child))
|
||||
remove_event_widget (self, child);
|
||||
|
||||
child = next;
|
||||
}
|
||||
|
||||
start = gcal_range_get_start (range);
|
||||
for (guint i = 0; i < 7; i++)
|
||||
@@ -299,3 +1069,54 @@ gcal_month_view_row_set_range (GcalMonthViewRow *self,
|
||||
gcal_month_cell_set_date (GCAL_MONTH_CELL (self->day_cells[i]), day);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
gcal_month_view_row_add_event (GcalMonthViewRow *self,
|
||||
GcalEvent *event)
|
||||
{
|
||||
GcalCalendar *calendar;
|
||||
GtkWidget *event_widget;
|
||||
|
||||
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
||||
g_assert (GCAL_IS_EVENT (event));
|
||||
|
||||
calendar = gcal_event_get_calendar (event);
|
||||
|
||||
event_widget = gcal_event_widget_new (self->context, event);
|
||||
gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (event_widget), gcal_calendar_is_read_only (calendar));
|
||||
if (!gcal_event_get_all_day (event) && !gcal_event_is_multiday (event))
|
||||
gcal_event_widget_set_timestamp_policy (GCAL_EVENT_WIDGET (event_widget), GCAL_TIMESTAMP_POLICY_START);
|
||||
|
||||
add_event_widget (self, event_widget);
|
||||
|
||||
self->needs_reallocation = TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
gcal_month_view_row_remove_event (GcalMonthViewRow *self,
|
||||
GcalEvent *event)
|
||||
{
|
||||
const gchar *uid;
|
||||
GList *l;
|
||||
|
||||
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
||||
g_assert (GCAL_IS_EVENT (event));
|
||||
|
||||
GCAL_ENTRY;
|
||||
|
||||
uid = gcal_event_get_uid (event);
|
||||
l = g_hash_table_lookup (self->children, uid);
|
||||
|
||||
if (!l)
|
||||
{
|
||||
g_warning ("%s: Widget with uuid: %s not found in view: %s",
|
||||
G_STRFUNC,uid, G_OBJECT_TYPE_NAME (self));
|
||||
GCAL_RETURN ();
|
||||
}
|
||||
|
||||
remove_event_widget (self, l->data);
|
||||
|
||||
self->needs_reallocation = TRUE;
|
||||
|
||||
GCAL_EXIT;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gcal-context.h"
|
||||
#include "gcal-event.h"
|
||||
#include "gcal-range.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
@@ -43,4 +44,10 @@ GcalRange * gcal_month_view_row_get_range (GcalMonthViewR
|
||||
void gcal_month_view_row_set_range (GcalMonthViewRow *self,
|
||||
GcalRange *range);
|
||||
|
||||
void gcal_month_view_row_add_event (GcalMonthViewRow *self,
|
||||
GcalEvent *event);
|
||||
|
||||
void gcal_month_view_row_remove_event (GcalMonthViewRow *self,
|
||||
GcalEvent *event);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
Reference in New Issue
Block a user