This was overlooked from the original commit: https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/598/diffs?commit_id=2ae3dd771cb9b2cc0d08a1cabb0b2f5359719d0b#c04c121e2c8eebcfb09fbcbeecff310223fe987f_708_767 Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/706>
1251 lines
38 KiB
C
1251 lines
38 KiB
C
/*
|
|
* gcal-month-view-row.c
|
|
*
|
|
* Copyright 2023 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/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalMonthViewRow"
|
|
|
|
#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"
|
|
#include "gcal-range-tree.h"
|
|
#include "gcal-utils.h"
|
|
|
|
#define SEPARATOR_OFFSET -1
|
|
|
|
typedef struct
|
|
{
|
|
GtkWidget *event_widget;
|
|
guint8 length;
|
|
guint8 cell;
|
|
|
|
/* These are updated during allocation */
|
|
gboolean visible;
|
|
gint height;
|
|
} GcalEventBlock;
|
|
|
|
struct _GcalMonthViewRow
|
|
{
|
|
GtkWidget parent;
|
|
|
|
GtkWidget *day_cells[7];
|
|
|
|
GcalRange *range;
|
|
|
|
GListStore *events;
|
|
GHashTable *layout_blocks;
|
|
gboolean layout_blocks_valid;
|
|
|
|
GcalContext *context;
|
|
};
|
|
|
|
static gint compare_events_cb (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data);
|
|
|
|
static void on_event_widget_activated_cb (GcalEventWidget *widget,
|
|
GcalMonthViewRow *self);
|
|
|
|
static gboolean widget_tick_cb (GtkWidget *widget,
|
|
GdkFrameClock *frame_clock,
|
|
gpointer user_data);
|
|
|
|
G_DEFINE_FINAL_TYPE (GcalMonthViewRow, gcal_month_view_row, GTK_TYPE_WIDGET)
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONTEXT,
|
|
N_PROPS,
|
|
};
|
|
|
|
enum
|
|
{
|
|
EVENT_ACTIVATED,
|
|
SHOW_OVERFLOW,
|
|
N_SIGNALS,
|
|
};
|
|
|
|
static guint signals[N_SIGNALS] = { 0, };
|
|
static GParamSpec *properties [N_PROPS];
|
|
|
|
|
|
/*
|
|
* FocusEventData
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
GtkWidget *widget;
|
|
graphene_rect_t rect;
|
|
gint focal_x;
|
|
} FocusEventData;
|
|
|
|
static void
|
|
focus_event_data_free (gpointer data)
|
|
{
|
|
FocusEventData *focus_event_data = data;
|
|
|
|
g_free (focus_event_data);
|
|
}
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FocusEventData, focus_event_data_free)
|
|
|
|
static FocusEventData *
|
|
focus_event_data_new (GtkWidget *widget,
|
|
const graphene_rect_t *rect,
|
|
gint focal_x)
|
|
{
|
|
g_autoptr (FocusEventData) focus_event_data = NULL;
|
|
|
|
focus_event_data = g_new0 (FocusEventData, 1);
|
|
focus_event_data->widget = widget;
|
|
focus_event_data->focal_x = focal_x;
|
|
graphene_rect_init_from_rect (&focus_event_data->rect, rect);
|
|
|
|
return g_steal_pointer (&focus_event_data);
|
|
}
|
|
|
|
static inline void
|
|
swap_focus_event_data (FocusEventData **dest,
|
|
FocusEventData *src)
|
|
{
|
|
g_assert (dest != NULL);
|
|
|
|
g_clear_pointer (dest, focus_event_data_free);
|
|
*dest = g_steal_pointer (&src);
|
|
}
|
|
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static FocusEventData *
|
|
create_focus_event_data (GcalMonthViewRow *self,
|
|
GtkWidget *child)
|
|
{
|
|
graphene_rect_t rect;
|
|
gboolean is_rtl;
|
|
gint focal_x;
|
|
|
|
g_assert (GTK_IS_WIDGET (child));
|
|
|
|
if (!gtk_widget_compute_bounds (child, GTK_WIDGET (self), &rect))
|
|
g_assert_not_reached ();
|
|
|
|
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
focal_x = rect.origin.x + (is_rtl ? rect.size.width : 0);
|
|
|
|
return focus_event_data_new (child, &rect, focal_x);
|
|
}
|
|
|
|
static FocusEventData *
|
|
create_focus_event_data_candidate (GcalMonthViewRow *self,
|
|
GtkWidget *child)
|
|
{
|
|
g_assert (GTK_IS_WIDGET (child));
|
|
|
|
if (!GCAL_IS_EVENT_WIDGET (child) || !gtk_widget_get_child_visible (child))
|
|
return NULL;
|
|
|
|
return create_focus_event_data (self, child);
|
|
}
|
|
|
|
static GtkWidget *
|
|
find_first_event_widget (GcalMonthViewRow *self)
|
|
{
|
|
g_autoptr (FocusEventData) candidate = NULL;
|
|
g_autoptr (FocusEventData) nearest = NULL;
|
|
GtkWidget *child;
|
|
gboolean is_rtl;
|
|
|
|
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
g_autoptr (FocusEventData) aux = NULL;
|
|
|
|
if (!(aux = create_focus_event_data_candidate (self, child)))
|
|
continue;
|
|
|
|
swap_focus_event_data (&candidate, g_steal_pointer (&aux));
|
|
g_assert (candidate != NULL);
|
|
|
|
if (nearest == NULL)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if ((is_rtl && candidate->focal_x > nearest->focal_x) ||
|
|
(!is_rtl && candidate->focal_x < nearest->focal_x))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if (candidate->focal_x == nearest->focal_x && candidate->rect.origin.y < nearest->rect.origin.y)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
}
|
|
|
|
g_assert (nearest == NULL || GTK_IS_WIDGET (nearest->widget));
|
|
|
|
GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL);
|
|
}
|
|
|
|
static GtkWidget *
|
|
find_last_event_widget (GcalMonthViewRow *self)
|
|
{
|
|
g_autoptr (FocusEventData) candidate = NULL;
|
|
g_autoptr (FocusEventData) nearest = NULL;
|
|
GtkWidget *child;
|
|
gboolean is_rtl;
|
|
|
|
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
g_autoptr (FocusEventData) aux = NULL;
|
|
|
|
if (!(aux = create_focus_event_data_candidate (self, child)))
|
|
continue;
|
|
|
|
swap_focus_event_data (&candidate, g_steal_pointer (&aux));
|
|
g_assert (candidate != NULL);
|
|
|
|
if (nearest == NULL)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if ((is_rtl && candidate->focal_x < nearest->focal_x) ||
|
|
(!is_rtl && candidate->focal_x > nearest->focal_x))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if (candidate->focal_x == nearest->focal_x && candidate->rect.origin.y > nearest->rect.origin.y)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
}
|
|
|
|
g_assert (nearest == NULL || GTK_IS_WIDGET (nearest->widget));
|
|
|
|
GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL);
|
|
}
|
|
|
|
static GtkWidget *
|
|
find_nearest_vertical_event_widget (GcalMonthViewRow *self,
|
|
FocusEventData *focused,
|
|
GtkDirectionType direction)
|
|
{
|
|
g_autoptr (FocusEventData) candidate = NULL;
|
|
g_autoptr (FocusEventData) nearest = NULL;
|
|
GtkWidget *child;
|
|
gboolean downward, forward_or_backward;
|
|
gboolean is_rtl;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
downward = direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD;
|
|
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
forward_or_backward = direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD;
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
g_autoptr (FocusEventData) aux = NULL;
|
|
|
|
if (!(aux = create_focus_event_data_candidate (self, child)))
|
|
continue;
|
|
|
|
swap_focus_event_data (&candidate, g_steal_pointer (&aux));
|
|
g_assert (candidate != NULL);
|
|
|
|
if (candidate->widget == focused->widget)
|
|
continue;
|
|
|
|
if (!graphene_rect_intersection (&GRAPHENE_RECT_INIT (focused->rect.origin.x, 0, focused->rect.size.width, 1),
|
|
&GRAPHENE_RECT_INIT (candidate->rect.origin.x, 0, candidate->rect.size.width, 1),
|
|
NULL))
|
|
continue;
|
|
|
|
if (forward_or_backward && focused->focal_x != candidate->focal_x)
|
|
continue;
|
|
|
|
if ((downward && candidate->rect.origin.y < focused->rect.origin.y) ||
|
|
(!downward && candidate->rect.origin.y > focused->rect.origin.y))
|
|
continue;
|
|
|
|
if (nearest == NULL)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if ((downward && candidate->rect.origin.y < nearest->rect.origin.y) ||
|
|
(!downward && candidate->rect.origin.y > nearest->rect.origin.y))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if (downward && candidate->rect.origin.y == nearest->rect.origin.y &&
|
|
((is_rtl && candidate->focal_x > nearest->focal_x) ||
|
|
(!is_rtl && candidate->focal_x < nearest->focal_x)))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
}
|
|
|
|
g_assert (nearest == NULL || nearest->widget != focused->widget);
|
|
|
|
GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL);
|
|
}
|
|
|
|
static GtkWidget *
|
|
find_nearest_horizontal_event_widget (GcalMonthViewRow *self,
|
|
FocusEventData *focused,
|
|
GtkDirectionType direction)
|
|
{
|
|
g_autoptr (FocusEventData) candidate = NULL;
|
|
g_autoptr (FocusEventData) nearest = NULL;
|
|
GtkWidget *child;
|
|
gboolean forward_or_backward;
|
|
GtkDirectionType lateral;
|
|
gboolean is_rtl;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (focused != NULL);
|
|
|
|
is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
|
|
forward_or_backward = direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD;
|
|
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_TAB_FORWARD:
|
|
lateral = is_rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT;
|
|
break;
|
|
|
|
case GTK_DIR_TAB_BACKWARD:
|
|
lateral = is_rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT;
|
|
break;
|
|
|
|
default:
|
|
g_assert (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT);
|
|
lateral = direction;
|
|
break;
|
|
}
|
|
|
|
for (child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
|
child != NULL;
|
|
child = gtk_widget_get_next_sibling (child))
|
|
{
|
|
g_autoptr (FocusEventData) aux = NULL;
|
|
gboolean candidate_intersects_vertically;
|
|
gboolean candidate_in_opposite_direction;
|
|
|
|
if (!(aux = create_focus_event_data_candidate (self, child)))
|
|
continue;
|
|
|
|
swap_focus_event_data (&candidate, g_steal_pointer (&aux));
|
|
g_assert (candidate != NULL);
|
|
|
|
if (candidate->widget == focused->widget)
|
|
continue;
|
|
|
|
if (forward_or_backward &&
|
|
((lateral == GTK_DIR_LEFT && candidate->focal_x >= focused->focal_x) ||
|
|
(lateral == GTK_DIR_RIGHT && candidate->focal_x <= focused->focal_x)))
|
|
continue;
|
|
|
|
candidate_in_opposite_direction = ((lateral == GTK_DIR_LEFT && candidate->focal_x > focused->focal_x) ||
|
|
(lateral == GTK_DIR_RIGHT && candidate->focal_x < focused->focal_x));
|
|
|
|
candidate_intersects_vertically =
|
|
graphene_rect_intersection (&GRAPHENE_RECT_INIT (0, focused->rect.origin.y, 1, focused->rect.size.height),
|
|
&GRAPHENE_RECT_INIT (0, candidate->rect.origin.y, 1, candidate->rect.size.height),
|
|
NULL);
|
|
|
|
if (!forward_or_backward && (!candidate_intersects_vertically || candidate_in_opposite_direction))
|
|
continue;
|
|
|
|
if (nearest == NULL)
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if ((lateral == GTK_DIR_LEFT && candidate->focal_x > nearest->focal_x) ||
|
|
(lateral == GTK_DIR_RIGHT && candidate->focal_x < nearest->focal_x))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
else if (candidate->focal_x == nearest->focal_x &&
|
|
((direction == GTK_DIR_TAB_FORWARD && candidate->rect.origin.y < nearest->rect.origin.y) ||
|
|
(direction == GTK_DIR_TAB_BACKWARD && candidate->rect.origin.y > nearest->rect.origin.y)))
|
|
swap_focus_event_data (&nearest, g_steal_pointer (&candidate));
|
|
}
|
|
|
|
g_assert (nearest == NULL || nearest->widget != focused->widget);
|
|
|
|
GCAL_RETURN (nearest ? g_steal_pointer (&nearest->widget) : NULL);
|
|
}
|
|
|
|
static void
|
|
layout_block_free (gpointer data)
|
|
{
|
|
GcalEventBlock *block = data;
|
|
|
|
if (!block)
|
|
return;
|
|
|
|
g_clear_pointer (&block->event_widget, gtk_widget_unparent);
|
|
g_free (block);
|
|
}
|
|
|
|
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_widget_activated_cb), self, 0);
|
|
}
|
|
|
|
static void
|
|
calculate_event_cells (GcalMonthViewRow *self,
|
|
GcalEvent *event,
|
|
gint *out_first_cell,
|
|
gint *out_last_cell)
|
|
{
|
|
g_autoptr (GDateTime) range_start = NULL;
|
|
g_autoptr (GDateTime) start_date = NULL;
|
|
gboolean all_day;
|
|
gint first_cell;
|
|
|
|
g_assert (out_first_cell != NULL || out_last_cell != NULL);
|
|
|
|
all_day = gcal_event_get_all_day (event);
|
|
range_start = gcal_range_get_start (self->range);
|
|
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);
|
|
first_cell = MAX (gcal_date_time_compare_date (start_date, range_start), 0);
|
|
|
|
/* Start date */
|
|
if (out_first_cell)
|
|
*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) range_start = NULL;
|
|
g_autoptr (GDateTime) end_date = NULL;
|
|
gint last_cell = 6;
|
|
|
|
range_start = gcal_range_get_start (self->range);
|
|
end_date = gcal_event_get_date_end (event);
|
|
|
|
if (all_day)
|
|
{
|
|
end_date = g_date_time_ref (end_date);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr (GDateTime) local_end_date = g_date_time_to_local (end_date);
|
|
end_date = g_date_time_add_seconds (local_end_date, -1);
|
|
}
|
|
|
|
last_cell = gcal_date_time_compare_date (end_date, range_start);
|
|
|
|
if (all_day)
|
|
last_cell--;
|
|
|
|
*out_last_cell = CLAMP (last_cell, first_cell, 6);
|
|
}
|
|
}
|
|
|
|
static void
|
|
prepare_layout_blocks (GcalMonthViewRow *self,
|
|
guint overflows[7])
|
|
{
|
|
GPtrArray *blocks_per_day[7];
|
|
gboolean cell_will_overflow[7] = { FALSE, };
|
|
guint available_height_without_overflow[7] = { 0, };
|
|
guint available_height_with_overflow[7] = { 0, };
|
|
guint weekday_heights[7] = { 0, };
|
|
guint n_events;
|
|
|
|
g_assert (self->layout_blocks_valid);
|
|
|
|
n_events = g_list_model_get_n_items (G_LIST_MODEL (self->events));
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
{
|
|
gint overflow_height;
|
|
gint content_space;
|
|
|
|
content_space = gcal_month_cell_get_content_space (GCAL_MONTH_CELL (self->day_cells[i]));
|
|
overflow_height = gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (self->day_cells[i]));
|
|
|
|
available_height_without_overflow[i] = content_space;
|
|
available_height_with_overflow[i] = content_space - overflow_height;
|
|
blocks_per_day[i] = g_ptr_array_sized_new (n_events);
|
|
}
|
|
|
|
/* Build a matrix with weekdays x blocks */
|
|
for (guint i = 0; i < n_events; i++)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
GPtrArray *blocks;
|
|
|
|
event = g_list_model_get_item (G_LIST_MODEL (self->events), i);
|
|
|
|
blocks = g_hash_table_lookup (self->layout_blocks, event);
|
|
g_assert (blocks != NULL);
|
|
|
|
for (guint block_index = 0; block_index < blocks->len; block_index++)
|
|
{
|
|
GcalEventBlock *block;
|
|
|
|
block = g_ptr_array_index (blocks, block_index);
|
|
block->visible = TRUE;
|
|
|
|
gtk_widget_measure (GTK_WIDGET (block->event_widget),
|
|
GTK_ORIENTATION_VERTICAL,
|
|
-1,
|
|
&block->height,
|
|
NULL, NULL, NULL);
|
|
|
|
for (guint j = 0; j < block->length; j++)
|
|
{
|
|
guint cell = block->cell + j;
|
|
|
|
g_ptr_array_add (blocks_per_day[cell], block);
|
|
weekday_heights[cell] += block->height;
|
|
cell_will_overflow[cell] |= weekday_heights[cell] > available_height_without_overflow[cell];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Figure out which blocks will be visible */
|
|
for (guint cell = 0; cell < 7; cell++)
|
|
{
|
|
for (guint block_index = 0;
|
|
block_index < blocks_per_day[cell]->len;
|
|
block_index++)
|
|
{
|
|
GcalEventBlock *block = g_ptr_array_index (blocks_per_day[cell], block_index);
|
|
|
|
if (block->cell != cell)
|
|
continue;
|
|
|
|
/* Will this block be visible in all cells it spans? */
|
|
for (guint block_cell = cell; block_cell < cell + block->length; block_cell++)
|
|
{
|
|
gint available_height;
|
|
|
|
if (cell_will_overflow[block_cell])
|
|
available_height = available_height_with_overflow[block_cell];
|
|
else
|
|
available_height = available_height_without_overflow[block_cell];
|
|
|
|
block->visible &= available_height > block->height;
|
|
}
|
|
|
|
for (guint block_cell = cell; block_cell < cell + block->length; block_cell++)
|
|
{
|
|
if (block->visible)
|
|
{
|
|
available_height_with_overflow[block_cell] -= block->height;
|
|
available_height_without_overflow[block_cell] -= block->height;
|
|
}
|
|
else
|
|
{
|
|
overflows[block_cell]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
g_clear_pointer (&blocks_per_day[i], g_ptr_array_unref);
|
|
}
|
|
|
|
static void
|
|
recalculate_layout_blocks (GcalMonthViewRow *self)
|
|
{
|
|
g_autoptr (GDateTime) range_start = NULL;
|
|
guint events_at_weekday[7] = { 0, };
|
|
guint n_events;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (!self->layout_blocks_valid);
|
|
|
|
range_start = gcal_range_get_start (self->range);
|
|
n_events = g_list_model_get_n_items (G_LIST_MODEL (self->events));
|
|
|
|
g_hash_table_remove_all (self->layout_blocks);
|
|
|
|
for (guint i = 0; i < n_events; i++)
|
|
{
|
|
g_autoptr (GPtrArray) blocks = NULL;
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
GcalEventBlock *block = NULL;
|
|
gint first_cell;
|
|
gint last_cell;
|
|
|
|
event = g_list_model_get_item (G_LIST_MODEL (self->events), i);
|
|
|
|
calculate_event_cells (self, event, &first_cell, &last_cell);
|
|
g_assert (last_cell >= first_cell);
|
|
|
|
blocks = g_ptr_array_new_full (last_cell - first_cell + 1, layout_block_free);
|
|
|
|
for (gint cell = first_cell; cell <= last_cell; cell++)
|
|
{
|
|
events_at_weekday[cell]++;
|
|
|
|
if (!block)
|
|
{
|
|
GtkWidget *event_widget;
|
|
|
|
event_widget = gcal_event_widget_new (self->context, event);
|
|
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);
|
|
setup_child_widget (self, event_widget);
|
|
|
|
block = g_new (GcalEventBlock, 1);
|
|
block->event_widget = g_steal_pointer (&event_widget);
|
|
block->length = 1;
|
|
block->cell = cell;
|
|
|
|
g_ptr_array_add (blocks, block);
|
|
}
|
|
else if (cell > first_cell && events_at_weekday[cell] != events_at_weekday[cell - 1])
|
|
{
|
|
GtkWidget *new_event_widget;
|
|
|
|
new_event_widget = gcal_event_widget_clone (GCAL_EVENT_WIDGET (block->event_widget));
|
|
setup_child_widget (self, new_event_widget);
|
|
|
|
block = g_new (GcalEventBlock, 1);
|
|
block->event_widget = g_steal_pointer (&new_event_widget);
|
|
block->length = 1;
|
|
block->cell = cell;
|
|
|
|
g_ptr_array_add (blocks, block);
|
|
}
|
|
else
|
|
{
|
|
block->length++;
|
|
}
|
|
}
|
|
|
|
/* Adjust slanted edges of multiday events */
|
|
if (gcal_event_is_multiday (event))
|
|
{
|
|
g_autoptr (GDateTime) adjusted_range_start = NULL;
|
|
|
|
adjusted_range_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),
|
|
0, 0, 0);
|
|
|
|
|
|
for (guint j = 0; j < blocks->len; j++)
|
|
{
|
|
g_autoptr (GDateTime) block_start = NULL;
|
|
g_autoptr (GDateTime) block_end = NULL;
|
|
|
|
block = g_ptr_array_index (blocks, j);
|
|
block_start = g_date_time_add_days (adjusted_range_start, block->cell);
|
|
block_end = g_date_time_add_days (block_start, block->length); /* FIXME: use end date's timezone here */
|
|
|
|
gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (block->event_widget), block_start);
|
|
gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (block->event_widget), block_end);
|
|
}
|
|
}
|
|
|
|
g_hash_table_insert (self->layout_blocks, event, g_steal_pointer (&blocks));
|
|
}
|
|
|
|
self->layout_blocks_valid = TRUE;
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
invalidate_layout_blocks (GcalMonthViewRow *self)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
if (!self->layout_blocks_valid)
|
|
GCAL_RETURN ();
|
|
|
|
if (gtk_widget_get_mapped (GTK_WIDGET (self)))
|
|
{
|
|
gtk_widget_add_tick_callback (GTK_WIDGET (self), widget_tick_cb, self, NULL);
|
|
gtk_widget_queue_allocate (GTK_WIDGET (self));
|
|
}
|
|
|
|
self->layout_blocks_valid = FALSE;
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static gint
|
|
compare_events_cb (gconstpointer a,
|
|
gconstpointer b,
|
|
gpointer user_data)
|
|
{
|
|
GcalEvent *event_a = (GcalEvent *)a;
|
|
GcalEvent *event_b = (GcalEvent *)b;
|
|
|
|
if (gcal_event_is_multiday (event_a) != gcal_event_is_multiday (event_b))
|
|
return gcal_event_is_multiday (event_b) - gcal_event_is_multiday (event_a);
|
|
|
|
return gcal_event_compare (event_a, event_b);
|
|
}
|
|
|
|
static void
|
|
on_event_widget_activated_cb (GcalEventWidget *widget,
|
|
GcalMonthViewRow *self)
|
|
{
|
|
g_signal_emit (self, signals[EVENT_ACTIVATED], 0, widget);
|
|
}
|
|
|
|
static void
|
|
on_month_cell_show_overflow_cb (GcalMonthCell *cell,
|
|
GtkWidget *button,
|
|
GcalMonthViewRow *self)
|
|
{
|
|
g_signal_emit (self, signals[SHOW_OVERFLOW], 0, cell);
|
|
}
|
|
|
|
static gboolean
|
|
widget_tick_cb (GtkWidget *widget,
|
|
GdkFrameClock *frame_clock,
|
|
gpointer user_data)
|
|
{
|
|
GcalMonthViewRow *self = (GcalMonthViewRow *) widget;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
recalculate_layout_blocks (self);
|
|
|
|
GCAL_RETURN (G_SOURCE_REMOVE);
|
|
}
|
|
|
|
|
|
/*
|
|
* GtkWidget overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_month_view_row_map (GtkWidget *widget)
|
|
{
|
|
GcalMonthViewRow *self = (GcalMonthViewRow *) widget;
|
|
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
GTK_WIDGET_CLASS (gcal_month_view_row_parent_class)->map (widget);
|
|
|
|
if (!self->layout_blocks_valid)
|
|
recalculate_layout_blocks (self);
|
|
}
|
|
|
|
static gboolean
|
|
gcal_month_view_row_focus (GtkWidget *widget,
|
|
GtkDirectionType direction)
|
|
{
|
|
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (widget);
|
|
GtkWidget *focused, *new_focus;
|
|
GtkRoot *root;
|
|
|
|
root = gtk_widget_get_root (widget);
|
|
focused = gtk_root_get_focus (root);
|
|
|
|
if (!focused)
|
|
return GTK_WIDGET_CLASS (gcal_month_view_row_parent_class)->focus (widget, direction);
|
|
|
|
if (!gtk_widget_is_ancestor (focused, widget))
|
|
{
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_TAB_FORWARD:
|
|
if ((new_focus = find_first_event_widget (self)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
else
|
|
return FALSE;
|
|
|
|
case GTK_DIR_TAB_BACKWARD:
|
|
if ((new_focus = find_last_event_widget (self)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
else
|
|
return FALSE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GCAL_IS_EVENT_WIDGET (focused))
|
|
{
|
|
g_autoptr (FocusEventData) focus_event_data = create_focus_event_data (self, focused);
|
|
|
|
switch (direction)
|
|
{
|
|
case GTK_DIR_TAB_FORWARD:
|
|
case GTK_DIR_TAB_BACKWARD:
|
|
if ((new_focus = find_nearest_vertical_event_widget (self, focus_event_data, direction)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
|
|
if ((new_focus = find_nearest_horizontal_event_widget (self, focus_event_data, direction)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
|
|
return FALSE;
|
|
|
|
case GTK_DIR_UP:
|
|
case GTK_DIR_DOWN:
|
|
if ((new_focus = find_nearest_vertical_event_widget (self, focus_event_data, direction)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
else
|
|
return FALSE;
|
|
|
|
case GTK_DIR_LEFT:
|
|
case GTK_DIR_RIGHT:
|
|
if ((new_focus = find_nearest_horizontal_event_widget (self, focus_event_data, direction)))
|
|
return gtk_widget_grab_focus (new_focus);
|
|
else
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return GTK_WIDGET_CLASS (gcal_month_view_row_parent_class)->focus (widget, direction);
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_measure (GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
gint for_size,
|
|
gint *out_minimum,
|
|
gint *out_natural,
|
|
gint *out_minimum_baseline,
|
|
gint *out_natural_baseline)
|
|
{
|
|
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (widget);
|
|
gint minimum = 0;
|
|
gint natural = 0;
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
{
|
|
gint child_minimum;
|
|
gint child_natural;
|
|
|
|
gtk_widget_measure (self->day_cells[i],
|
|
orientation,
|
|
for_size,
|
|
&child_minimum,
|
|
&child_natural,
|
|
NULL,
|
|
NULL);
|
|
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
minimum += child_minimum;
|
|
natural += child_natural;
|
|
}
|
|
else
|
|
{
|
|
minimum = MAX(minimum, child_minimum);
|
|
natural = MAX(natural, child_natural);
|
|
}
|
|
}
|
|
|
|
if (out_minimum)
|
|
*out_minimum = minimum;
|
|
|
|
if (out_natural)
|
|
*out_natural = natural;
|
|
|
|
if (out_minimum_baseline)
|
|
*out_minimum_baseline = -1;
|
|
|
|
if (out_natural_baseline)
|
|
*out_natural_baseline = -1;
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_size_allocate (GtkWidget *widget,
|
|
gint width,
|
|
gint height,
|
|
gint baseline)
|
|
{
|
|
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (widget);
|
|
gboolean is_ltr;
|
|
gdouble cell_width;
|
|
|
|
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;
|
|
GtkWidget *cell;
|
|
|
|
allocation.x = round (cell_width * i);
|
|
allocation.y = 0;
|
|
allocation.width = round (cell_width * (i + 1)) - allocation.x;
|
|
allocation.height = height;
|
|
|
|
cell = is_ltr ? self->day_cells[i] : self->day_cells[7 - i - 1];
|
|
gtk_widget_size_allocate (cell, &allocation, baseline);
|
|
}
|
|
|
|
/* Event widgets */
|
|
if (self->layout_blocks_valid)
|
|
{
|
|
gdouble cell_y[7];
|
|
guint overflows[7] = { 0, };
|
|
guint n_events;
|
|
|
|
prepare_layout_blocks (self, overflows);
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
cell_y[i] = gcal_month_cell_get_header_height (GCAL_MONTH_CELL (self->day_cells[i]));
|
|
|
|
n_events = g_list_model_get_n_items (G_LIST_MODEL (self->events));
|
|
|
|
for (guint i = 0; i < n_events; i++)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
GPtrArray *blocks;
|
|
|
|
event = g_list_model_get_item (G_LIST_MODEL (self->events), i);
|
|
|
|
blocks = g_hash_table_lookup (self->layout_blocks, event);
|
|
g_assert (blocks != NULL);
|
|
|
|
for (guint block_index = 0; block_index < blocks->len; block_index++)
|
|
{
|
|
GcalEventBlock *block;
|
|
GtkAllocation allocation;
|
|
gint start_cell;
|
|
gint end_cell;
|
|
|
|
block = g_ptr_array_index (blocks, block_index);
|
|
|
|
start_cell = is_ltr ? block->cell : 7 - block->cell - block->length;
|
|
end_cell = start_cell + block->length;
|
|
|
|
allocation.x = round (start_cell * cell_width - (is_ltr ? 0 : SEPARATOR_OFFSET));
|
|
allocation.y = cell_y[block->cell];
|
|
allocation.width = round (end_cell * cell_width) - allocation.x + (is_ltr ? SEPARATOR_OFFSET : 0);
|
|
allocation.height = block->height;
|
|
|
|
gtk_widget_set_child_visible (block->event_widget, block->visible);
|
|
gtk_widget_size_allocate (block->event_widget, &allocation, baseline);
|
|
|
|
for (guint j = 0; j < block->length; j++)
|
|
cell_y[block->cell + j] += block->height;
|
|
}
|
|
}
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
gcal_month_cell_set_overflow (GCAL_MONTH_CELL (self->day_cells[i]), overflows[i]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* GObject overrides
|
|
*/
|
|
|
|
static void
|
|
gcal_month_view_row_dispose (GObject *object)
|
|
{
|
|
GcalMonthViewRow *self = (GcalMonthViewRow *)object;
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
g_clear_pointer (&self->day_cells[i], gtk_widget_unparent);
|
|
|
|
g_clear_pointer (&self->layout_blocks, g_hash_table_destroy);
|
|
g_clear_object (&self->events);
|
|
|
|
G_OBJECT_CLASS (gcal_month_view_row_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_finalize (GObject *object)
|
|
{
|
|
GcalMonthViewRow *self = (GcalMonthViewRow *)object;
|
|
|
|
g_clear_pointer (&self->range, gcal_range_unref);
|
|
|
|
G_OBJECT_CLASS (gcal_month_view_row_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
g_value_set_object (value, self->context);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GcalMonthViewRow *self = GCAL_MONTH_VIEW_ROW (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CONTEXT:
|
|
gcal_month_view_row_set_context (self, g_value_get_object (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_class_init (GcalMonthViewRowClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->dispose = gcal_month_view_row_dispose;
|
|
object_class->finalize = gcal_month_view_row_finalize;
|
|
object_class->get_property = gcal_month_view_row_get_property;
|
|
object_class->set_property = gcal_month_view_row_set_property;
|
|
|
|
widget_class->map = gcal_month_view_row_map;
|
|
widget_class->focus = gcal_month_view_row_focus;
|
|
widget_class->measure = gcal_month_view_row_measure;
|
|
widget_class->size_allocate = gcal_month_view_row_size_allocate;
|
|
|
|
properties[PROP_CONTEXT] = g_param_spec_object ("context",
|
|
"Context",
|
|
"The GcalContext of the application",
|
|
GCAL_TYPE_CONTEXT,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
|
|
|
signals[EVENT_ACTIVATED] = g_signal_new ("event-activated",
|
|
GCAL_TYPE_MONTH_VIEW_ROW,
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_EVENT_WIDGET);
|
|
|
|
signals[SHOW_OVERFLOW] = g_signal_new ("show-overflow",
|
|
GCAL_TYPE_MONTH_VIEW_ROW,
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_MONTH_CELL);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, "monthviewrow");
|
|
}
|
|
|
|
static void
|
|
gcal_month_view_row_init (GcalMonthViewRow *self)
|
|
{
|
|
self->events = g_list_store_new (GCAL_TYPE_EVENT);
|
|
self->layout_blocks = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_ptr_array_unref);
|
|
self->layout_blocks_valid = TRUE;
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
{
|
|
self->day_cells[i] = gcal_month_cell_new ();
|
|
gcal_month_cell_set_overflow (GCAL_MONTH_CELL (self->day_cells[i]), 0);
|
|
gtk_widget_set_parent (self->day_cells[i], GTK_WIDGET (self));
|
|
g_signal_connect (self->day_cells[i], "show-overflow", G_CALLBACK (on_month_cell_show_overflow_cb), self);
|
|
}
|
|
}
|
|
|
|
GtkWidget *
|
|
gcal_month_view_row_new (void)
|
|
{
|
|
return g_object_new (GCAL_TYPE_MONTH_VIEW_ROW, NULL);
|
|
}
|
|
|
|
GcalContext*
|
|
gcal_month_view_row_get_context (GcalMonthViewRow *self)
|
|
{
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
return self->context;
|
|
}
|
|
|
|
void
|
|
gcal_month_view_row_set_context (GcalMonthViewRow *self,
|
|
GcalContext *context)
|
|
{
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
if (g_set_object (&self->context, context))
|
|
{
|
|
for (guint i = 0; i < 7; i++)
|
|
gcal_month_cell_set_context (GCAL_MONTH_CELL (self->day_cells[i]), context);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CONTEXT]);
|
|
}
|
|
}
|
|
|
|
GcalRange *
|
|
gcal_month_view_row_get_range (GcalMonthViewRow *self)
|
|
{
|
|
g_return_val_if_fail (GCAL_IS_MONTH_VIEW_ROW (self), NULL);
|
|
|
|
return self->range ? gcal_range_ref (self->range) : NULL;
|
|
}
|
|
|
|
void
|
|
gcal_month_view_row_set_range (GcalMonthViewRow *self,
|
|
GcalRange *range)
|
|
{
|
|
g_autoptr (GDateTime) start = NULL;
|
|
|
|
g_return_if_fail (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
g_return_if_fail (range != NULL);
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (self->range && gcal_range_compare (self->range, range) == 0)
|
|
GCAL_RETURN ();
|
|
|
|
g_clear_pointer (&self->range, gcal_range_unref);
|
|
self->range = gcal_range_ref (range);
|
|
|
|
/* Preemptively remove all event widgets */
|
|
g_list_store_remove_all (self->events);
|
|
g_hash_table_remove_all (self->layout_blocks);
|
|
invalidate_layout_blocks (self);
|
|
|
|
start = gcal_range_get_start (range);
|
|
for (guint i = 0; i < 7; i++)
|
|
{
|
|
g_autoptr (GDateTime) day = g_date_time_add_days (start, i);
|
|
gcal_month_cell_set_date (GCAL_MONTH_CELL (self->day_cells[i]), day);
|
|
}
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
void
|
|
gcal_month_view_row_add_event (GcalMonthViewRow *self,
|
|
GcalEvent *event)
|
|
{
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
g_assert (GCAL_IS_EVENT (event));
|
|
|
|
g_list_store_insert_sorted (self->events, event, compare_events_cb, self);
|
|
invalidate_layout_blocks (self);
|
|
}
|
|
|
|
void
|
|
gcal_month_view_row_remove_event (GcalMonthViewRow *self,
|
|
GcalEvent *event)
|
|
{
|
|
guint position;
|
|
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
g_assert (GCAL_IS_EVENT (event));
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (!g_list_store_find (self->events, event, &position))
|
|
{
|
|
g_warning ("%s: Widget with uuid: %s not found in view: %s",
|
|
G_STRFUNC,
|
|
gcal_event_get_uid (event),
|
|
G_OBJECT_TYPE_NAME (self));
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
g_list_store_remove (self->events, position);
|
|
invalidate_layout_blocks (self);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
GList*
|
|
gcal_month_view_row_get_children_by_uuid (GcalMonthViewRow *self,
|
|
GcalRecurrenceModType mod,
|
|
const gchar *uuid)
|
|
{
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
return filter_children_by_uid_and_modtype (GTK_WIDGET (self), mod, uuid);
|
|
}
|
|
|
|
GtkWidget*
|
|
gcal_month_view_row_get_cell_at_x (GcalMonthViewRow *self,
|
|
gdouble x)
|
|
{
|
|
gint column;
|
|
gint width;
|
|
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
width = gtk_widget_get_width (GTK_WIDGET (self));
|
|
if (x < 0.0 || x > width)
|
|
return NULL;
|
|
|
|
column = floor (7.0 * x / (gdouble) width);
|
|
|
|
if (gtk_widget_get_direction (GTK_WIDGET (self)) != GTK_TEXT_DIR_RTL)
|
|
return self->day_cells[column];
|
|
else
|
|
return self->day_cells[7 - column - 1];
|
|
}
|
|
|
|
void
|
|
gcal_month_view_row_update_selection (GcalMonthViewRow *self,
|
|
GcalRange *selection_range)
|
|
{
|
|
g_assert (GCAL_IS_MONTH_VIEW_ROW (self));
|
|
|
|
for (guint i = 0; i < 7; i++)
|
|
{
|
|
GcalMonthCell *month_cell;
|
|
GDateTime *cell_date;
|
|
gboolean selected;
|
|
|
|
month_cell = GCAL_MONTH_CELL (self->day_cells[i]);
|
|
cell_date = gcal_month_cell_get_date (month_cell);
|
|
selected = selection_range && gcal_range_contains_datetime (selection_range, cell_date);
|
|
|
|
gcal_month_cell_set_selected (month_cell, selected);
|
|
}
|
|
}
|