This gives some control on what time is displayed on the widget. It will be used to show the time of the end of an event in the agenda view.
1022 lines
30 KiB
C
1022 lines
30 KiB
C
/* gcal-week-grid.c
|
|
*
|
|
* Copyright (C) 2016-2020 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
|
* Vamsi Krishna Gollapudi <pandu.sonu@yahoo.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalWeekGrid"
|
|
|
|
#include "gcal-application.h"
|
|
#include "gcal-clock.h"
|
|
#include "gcal-context.h"
|
|
#include "gcal-debug.h"
|
|
#include "gcal-week-grid.h"
|
|
#include "gcal-week-view.h"
|
|
#include "gcal-week-view-common.h"
|
|
#include "gcal-utils.h"
|
|
#include "gcal-view.h"
|
|
#include "gcal-event-widget.h"
|
|
#include "gcal-range-tree.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
typedef struct
|
|
{
|
|
GcalWeekGrid *self;
|
|
GcalEvent *event;
|
|
gint drop_cell;
|
|
} DropData;
|
|
|
|
typedef struct
|
|
{
|
|
GtkWidget *widget;
|
|
GcalEvent *event;
|
|
} ChildData;
|
|
|
|
struct _GcalWeekGrid
|
|
{
|
|
GtkWidget parent;
|
|
|
|
GtkWidget *hours_sidebar;
|
|
GtkWidget *now_strip;
|
|
GtkEventController *motion_controller;
|
|
|
|
GDateTime *active_date;
|
|
|
|
GcalRangeTree *events;
|
|
|
|
/*
|
|
* These fields are "cells" rather than minutes. Each cell
|
|
* correspond to 30 minutes.
|
|
*/
|
|
gint selection_start;
|
|
gint selection_end;
|
|
gint dnd_cell;
|
|
|
|
GcalContext *context;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GcalWeekGrid, gcal_week_grid, GTK_TYPE_WIDGET);
|
|
|
|
|
|
enum
|
|
{
|
|
EVENT_ACTIVATED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0, };
|
|
|
|
/*
|
|
* Auxiliary methods
|
|
*/
|
|
|
|
static ChildData*
|
|
child_data_new (GtkWidget *widget,
|
|
GcalEvent *event)
|
|
{
|
|
ChildData *data;
|
|
|
|
data = g_new (ChildData, 1);
|
|
data->widget = widget;
|
|
data->event = g_object_ref (event);
|
|
|
|
return data;
|
|
}
|
|
|
|
static void
|
|
child_data_free (gpointer data)
|
|
{
|
|
ChildData *child_data = data;
|
|
|
|
if (!child_data)
|
|
return;
|
|
|
|
g_clear_pointer (&child_data->widget, gtk_widget_unparent);
|
|
g_clear_object (&child_data->event);
|
|
g_free (child_data);
|
|
}
|
|
|
|
static inline gint
|
|
uint16_compare (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
return GPOINTER_TO_UINT (*(gint*)a) - GPOINTER_TO_UINT (*(gint*)b);
|
|
}
|
|
|
|
static inline guint
|
|
get_event_index (GcalRangeTree *tree,
|
|
GcalRange *range)
|
|
{
|
|
g_autoptr (GPtrArray) array = NULL;
|
|
gint idx, i;
|
|
|
|
i = idx = 0;
|
|
array = gcal_range_tree_get_data_at_range (tree, range);
|
|
|
|
if (!array)
|
|
return 0;
|
|
|
|
g_ptr_array_sort (array, uint16_compare);
|
|
|
|
for (i = 0; array && i < array->len; i++)
|
|
{
|
|
if (idx == GPOINTER_TO_INT (g_ptr_array_index (array, i)))
|
|
idx++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
static guint
|
|
count_overlaps_at_range (GcalRangeTree *self,
|
|
GcalRange *range)
|
|
{
|
|
g_autoptr (GDateTime) start = NULL;
|
|
g_autoptr (GDateTime) end = NULL;
|
|
guint64 counter;
|
|
gint minutes;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (self, 0);
|
|
|
|
counter = 0;
|
|
start = gcal_range_get_start (range);
|
|
end = gcal_range_get_end (range);
|
|
minutes = g_date_time_difference (end, start) / G_TIME_SPAN_MINUTE;
|
|
|
|
for (i = 0; i < minutes; i++)
|
|
{
|
|
g_autoptr (GcalRange) minute_range = NULL;
|
|
guint n_events;
|
|
|
|
minute_range = gcal_range_new_take (g_date_time_add_minutes (start, i),
|
|
g_date_time_add_minutes (start, i + 1),
|
|
GCAL_RANGE_DEFAULT);
|
|
n_events = gcal_range_tree_count_entries_at_range (self, minute_range);
|
|
|
|
if (n_events == 0)
|
|
break;
|
|
|
|
counter = MAX (counter, n_events);
|
|
}
|
|
|
|
return counter;
|
|
}
|
|
|
|
/*
|
|
* Callbacks
|
|
*/
|
|
|
|
static void
|
|
on_click_gesture_pressed_cb (GtkGestureClick *click_gesture,
|
|
gint n_press,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalWeekGrid *self)
|
|
{
|
|
GtkAllocation alloc;
|
|
gdouble minute_height;
|
|
gint column_width;
|
|
gint column;
|
|
gint minute;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
|
minute_height = (gdouble) alloc.height / MINUTES_PER_DAY;
|
|
column_width = floor (alloc.width / 7);
|
|
column = (gint) x / column_width;
|
|
minute = y / minute_height;
|
|
minute = minute - (minute % 30);
|
|
|
|
self->selection_start = (column * MINUTES_PER_DAY + minute) / 30;
|
|
self->selection_end = self->selection_start;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
|
|
gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_BUBBLE);
|
|
}
|
|
|
|
static void
|
|
on_event_widget_activated_cb (GcalEventWidget *widget,
|
|
GcalWeekGrid *self)
|
|
{
|
|
g_signal_emit (self, signals[EVENT_ACTIVATED], 0, widget);
|
|
}
|
|
|
|
static void
|
|
on_motion_controller_motion_cb (GtkEventControllerMotion *motion_controller,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalWeekGrid *self)
|
|
{
|
|
GtkAllocation alloc;
|
|
gdouble minute_height;
|
|
gint column;
|
|
gint minute;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
|
minute_height = (gdouble) alloc.height / MINUTES_PER_DAY;
|
|
column = self->selection_start * 30 / MINUTES_PER_DAY;
|
|
minute = y / minute_height;
|
|
minute = minute - (minute % 30);
|
|
|
|
self->selection_end = (column * MINUTES_PER_DAY + minute) / 30;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
}
|
|
|
|
static void
|
|
on_click_gesture_released_cb (GtkGestureClick *click_gesture,
|
|
gint n_press,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalWeekGrid *self)
|
|
{
|
|
g_autoptr (GDateTime) week_start = NULL;
|
|
g_autoptr (GDateTime) start = NULL;
|
|
g_autoptr (GDateTime) end = NULL;
|
|
GtkAllocation alloc;
|
|
GtkWidget *weekview;
|
|
gboolean ltr;
|
|
gdouble minute_height;
|
|
gdouble local_x;
|
|
gdouble local_y;
|
|
gdouble out_x;
|
|
gdouble out_y;
|
|
gint column;
|
|
gint minute;
|
|
gint start_cell;
|
|
gint end_cell;
|
|
|
|
ltr = gtk_widget_get_direction (GTK_WIDGET (self)) != GTK_TEXT_DIR_RTL;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
|
minute_height = (gdouble) alloc.height / MINUTES_PER_DAY;
|
|
column = self->selection_start * 30 / MINUTES_PER_DAY;
|
|
minute = y / minute_height;
|
|
minute = minute - (minute % 30);
|
|
|
|
self->selection_end = (column * MINUTES_PER_DAY + minute) / 30;
|
|
|
|
start_cell = self->selection_start;
|
|
end_cell = self->selection_end;
|
|
|
|
if (start_cell > end_cell)
|
|
{
|
|
start_cell = start_cell + end_cell;
|
|
end_cell = start_cell - end_cell;
|
|
start_cell = start_cell - end_cell;
|
|
}
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
|
|
/* Fake the week view's event so we can control the X and Y values */
|
|
weekview = gtk_widget_get_ancestor (GTK_WIDGET (self), GCAL_TYPE_WEEK_VIEW);
|
|
week_start = gcal_date_time_get_start_of_week (self->active_date);
|
|
|
|
if (ltr)
|
|
{
|
|
start = g_date_time_add_minutes (week_start, start_cell * 30);
|
|
end = g_date_time_add_minutes (week_start, (end_cell + 1) * 30);
|
|
}
|
|
else
|
|
{
|
|
guint rtl_start_cell, rtl_end_cell, rtl_column;
|
|
|
|
/* Fix the minute */
|
|
rtl_column = 6 - column;
|
|
rtl_start_cell = start_cell + (rtl_column - column) * 48;
|
|
rtl_end_cell = (rtl_column * MINUTES_PER_DAY + minute) / 30;
|
|
|
|
start = g_date_time_add_minutes (week_start, rtl_start_cell * 30);
|
|
end = g_date_time_add_minutes (week_start, (rtl_end_cell + 1) * 30);
|
|
}
|
|
|
|
local_x = round ((column + 0.5) * (alloc.width / 7.0));
|
|
local_y = (minute + 15) * minute_height;
|
|
|
|
gtk_widget_translate_coordinates (GTK_WIDGET (self),
|
|
weekview,
|
|
local_x,
|
|
local_y,
|
|
&out_x,
|
|
&out_y);
|
|
|
|
g_signal_emit_by_name (weekview,
|
|
"create-event",
|
|
start,
|
|
end,
|
|
out_x,
|
|
out_y);
|
|
|
|
gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_NONE);
|
|
}
|
|
|
|
static gint
|
|
get_dnd_cell (GcalWeekGrid *self,
|
|
gdouble x,
|
|
gdouble y)
|
|
{
|
|
GtkAllocation alloc;
|
|
gdouble column_width, cell_height;
|
|
gint column, row;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
|
|
|
column_width = alloc.width / 7.0;
|
|
cell_height = alloc.height / 48.0;
|
|
column = floor (x / column_width);
|
|
row = y / cell_height;
|
|
|
|
return column * 48 + row;
|
|
}
|
|
|
|
static void
|
|
move_event_to_cell (GcalWeekGrid *self,
|
|
GcalEvent *event,
|
|
guint cell,
|
|
GcalRecurrenceModType mod_type)
|
|
{
|
|
|
|
g_autoptr (GDateTime) week_start = NULL;
|
|
g_autoptr (GDateTime) dnd_date = NULL;
|
|
g_autoptr (GDateTime) new_end = NULL;
|
|
g_autoptr (GcalEvent) changed_event = NULL;
|
|
GTimeSpan timespan = 0;
|
|
|
|
/* RTL languages swap the drop cell column */
|
|
if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
|
|
{
|
|
gint column, row;
|
|
|
|
column = cell / (MINUTES_PER_DAY / 30);
|
|
row = cell - column * 48;
|
|
|
|
cell = (6 - column) * 48 + row;
|
|
}
|
|
|
|
changed_event = gcal_event_new_from_event (event);
|
|
week_start = gcal_date_time_get_start_of_week (self->active_date);
|
|
dnd_date = g_date_time_add_minutes (week_start, cell * 30);
|
|
|
|
/*
|
|
* Calculate the diff between the dropped cell and the event's start date,
|
|
* so we can update the end date accordingly.
|
|
*/
|
|
timespan = g_date_time_difference (gcal_event_get_date_end (changed_event), gcal_event_get_date_start (changed_event));
|
|
|
|
/*
|
|
* Set the event's start and end dates. Since the event may have a
|
|
* NULL end date, so we have to check it here
|
|
*/
|
|
gcal_event_set_all_day (changed_event, FALSE);
|
|
gcal_event_set_date_start (changed_event, dnd_date);
|
|
|
|
/* Setup the new end date */
|
|
new_end = g_date_time_add (dnd_date, timespan);
|
|
gcal_event_set_date_end (changed_event, new_end);
|
|
|
|
/* Commit the changes */
|
|
gcal_manager_update_event (gcal_context_get_manager (self->context), changed_event, mod_type);
|
|
}
|
|
|
|
static void
|
|
on_ask_recurrence_response_cb (GcalEvent *event,
|
|
GcalRecurrenceModType mod_type,
|
|
gpointer user_data)
|
|
{
|
|
DropData *data = user_data;
|
|
|
|
if (mod_type != GCAL_RECURRENCE_MOD_NONE)
|
|
move_event_to_cell (data->self, data->event, data->drop_cell, mod_type);
|
|
|
|
g_clear_object (&data->event);
|
|
g_clear_pointer (&data, g_free);
|
|
}
|
|
|
|
static gboolean
|
|
on_drop_target_drop_cb (GtkDropTarget *drop_target,
|
|
const GValue *value,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalWeekGrid *self)
|
|
{
|
|
GcalEventWidget *event_widget;
|
|
GcalEvent *event;
|
|
gint cell;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
if (!G_VALUE_HOLDS (value, GCAL_TYPE_EVENT_WIDGET))
|
|
GCAL_RETURN (FALSE);
|
|
|
|
cell = get_dnd_cell (self, x, y);
|
|
event_widget = g_value_get_object (value);
|
|
event = gcal_event_widget_get_event (event_widget);
|
|
|
|
if (gcal_event_has_recurrence (event))
|
|
{
|
|
DropData *data;
|
|
|
|
data = g_new0 (DropData, 1);
|
|
data->self = self;
|
|
data->event = g_object_ref (event);
|
|
data->drop_cell = cell;
|
|
|
|
gcal_utils_ask_recurrence_modification_type (GTK_WIDGET (self),
|
|
event,
|
|
on_ask_recurrence_response_cb,
|
|
data);
|
|
}
|
|
else
|
|
{
|
|
move_event_to_cell (self, event, cell, GCAL_RECURRENCE_MOD_THIS_ONLY);
|
|
}
|
|
|
|
GCAL_RETURN (TRUE);
|
|
}
|
|
|
|
static void
|
|
on_drop_target_leave_cb (GtkDropTarget *drop_target,
|
|
GcalWeekGrid *self)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
self->dnd_cell = -1;
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static GdkDragAction
|
|
on_drop_target_motion_cb (GtkDropTarget *drop_target,
|
|
gdouble x,
|
|
gdouble y,
|
|
GcalWeekGrid *self)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
self->dnd_cell = get_dnd_cell (self, x, y);
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
|
|
GCAL_RETURN (self->dnd_cell != -1 ? GDK_ACTION_COPY : 0);
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_dispose (GObject *object)
|
|
{
|
|
GcalWeekGrid *self = GCAL_WEEK_GRID (object);
|
|
|
|
g_clear_pointer (&self->events, gcal_range_tree_unref);
|
|
g_clear_pointer (&self->now_strip, gtk_widget_unparent);
|
|
|
|
G_OBJECT_CLASS (gcal_week_grid_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_finalize (GObject *object)
|
|
{
|
|
GcalWeekGrid *self = GCAL_WEEK_GRID (object);
|
|
|
|
gcal_clear_date_time (&self->active_date);
|
|
|
|
G_OBJECT_CLASS (gcal_week_grid_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
|
|
static inline gint
|
|
get_today_column (GcalWeekGrid *self)
|
|
{
|
|
g_autoptr(GDateTime) week_start = NULL;
|
|
g_autoptr(GDateTime) today = NULL;
|
|
gint days_diff;
|
|
|
|
today = g_date_time_new_now_local ();
|
|
week_start = gcal_date_time_get_start_of_week (self->active_date);
|
|
days_diff = g_date_time_difference (today, week_start) / G_TIME_SPAN_DAY;
|
|
|
|
/* Today is out of range */
|
|
if (g_date_time_compare (today, week_start) < 0 || days_diff > 7)
|
|
return -1;
|
|
|
|
return days_diff;
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_measure (GtkWidget *widget,
|
|
GtkOrientation orientation,
|
|
gint for_size,
|
|
gint *minimum,
|
|
gint *natural,
|
|
gint *minimum_baseline,
|
|
gint *natural_baseline)
|
|
{
|
|
g_autoptr (PangoLayout) layout = NULL;
|
|
PangoFontDescription *font_desc;
|
|
GtkStyleContext *context;
|
|
PangoContext *pango_context;
|
|
GtkBorder padding;
|
|
gint hours_12_height;
|
|
gint hours_24_height;
|
|
gint cell_height;
|
|
gint height;
|
|
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
|
{
|
|
if (minimum)
|
|
*minimum = 1;
|
|
if (natural)
|
|
*natural = 1;
|
|
return;
|
|
}
|
|
|
|
context = gtk_widget_get_style_context (widget);
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_add_class (context, "hours");
|
|
|
|
gtk_style_context_get_padding (context, &padding);
|
|
|
|
pango_context = gtk_widget_get_pango_context (widget);
|
|
font_desc = pango_context_get_font_description (pango_context);
|
|
|
|
layout = pango_layout_new (pango_context);
|
|
pango_layout_set_font_description (layout, font_desc);
|
|
|
|
pango_layout_set_text (layout, _("00 AM"), -1);
|
|
pango_layout_get_pixel_size (layout, NULL, &hours_12_height);
|
|
|
|
pango_layout_set_text (layout, _("00:00"), -1);
|
|
pango_layout_get_pixel_size (layout, NULL, &hours_24_height);
|
|
|
|
cell_height = MAX (hours_12_height, hours_24_height) + padding.top + padding.bottom;
|
|
height = cell_height * 48;
|
|
|
|
gtk_style_context_restore (context);
|
|
|
|
/* Report the height */
|
|
if (minimum)
|
|
*minimum = height;
|
|
|
|
if (natural)
|
|
*natural = height;
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_snapshot (GtkWidget *widget,
|
|
GtkSnapshot *snapshot)
|
|
{
|
|
g_autoptr (GPtrArray) widgets = NULL;
|
|
GtkStyleContext *context;
|
|
GtkStateFlags state;
|
|
GcalWeekGrid *self;
|
|
GtkBorder padding;
|
|
GdkRGBA color;
|
|
gdouble minutes_height;
|
|
gdouble x, column_width;
|
|
guint i;
|
|
gint width, height;
|
|
|
|
self = GCAL_WEEK_GRID (widget);
|
|
context = gtk_widget_get_style_context (widget);
|
|
state = gtk_widget_get_state_flags (widget);
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_add_class (context, "lines");
|
|
gtk_style_context_get_color (context, &color);
|
|
gtk_style_context_get_padding (context, &padding);
|
|
|
|
width = gtk_widget_get_width (widget);
|
|
height = gtk_widget_get_height (widget);
|
|
column_width = width / 7.0;
|
|
minutes_height = (gdouble) height / MINUTES_PER_DAY;
|
|
|
|
/* First, draw the selection */
|
|
if (self->selection_start != -1 && self->selection_end != -1)
|
|
{
|
|
gint selection_height;
|
|
gint column;
|
|
gint start;
|
|
gint end;
|
|
|
|
start = self->selection_start;
|
|
end = self->selection_end;
|
|
|
|
/* Swap cells if needed */
|
|
if (start > end)
|
|
{
|
|
start = start + end;
|
|
end = start - end;
|
|
start = start - end;
|
|
}
|
|
|
|
column = start * 30 / MINUTES_PER_DAY;
|
|
selection_height = (end - start + 1) * 30 * minutes_height;
|
|
|
|
x = column * column_width;
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_set_state (context, state | GTK_STATE_FLAG_SELECTED);
|
|
|
|
gtk_snapshot_render_background (snapshot,
|
|
context,
|
|
ALIGNED (x),
|
|
round ((start * 30 % MINUTES_PER_DAY) * minutes_height),
|
|
column_width,
|
|
selection_height);
|
|
|
|
gtk_style_context_restore (context);
|
|
}
|
|
|
|
/* Drag and Drop highlight */
|
|
if (self->dnd_cell != -1)
|
|
{
|
|
gdouble cell_height;
|
|
gint column, row;
|
|
|
|
cell_height = minutes_height * 30;
|
|
column = self->dnd_cell / (MINUTES_PER_DAY / 30);
|
|
row = self->dnd_cell - column * 48;
|
|
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_add_class (context, "dnd");
|
|
|
|
gtk_snapshot_render_background (snapshot,
|
|
context,
|
|
column * column_width,
|
|
row * cell_height,
|
|
column_width,
|
|
cell_height);
|
|
|
|
gtk_style_context_restore (context);
|
|
}
|
|
|
|
gcal_week_view_common_snapshot_hour_lines (widget, snapshot, GTK_ORIENTATION_HORIZONTAL, &color, width, height);
|
|
gcal_week_view_common_snapshot_hour_lines (widget, snapshot, GTK_ORIENTATION_VERTICAL, &color, width, height);
|
|
|
|
gtk_style_context_restore (context);
|
|
|
|
widgets = gcal_range_tree_get_all_data (self->events);
|
|
for (i = 0; i < widgets->len; i++)
|
|
{
|
|
ChildData *child_data = g_ptr_array_index (widgets, i);
|
|
|
|
gtk_widget_snapshot_child (widget, child_data->widget, snapshot);
|
|
}
|
|
|
|
gtk_widget_snapshot_child (widget, self->now_strip, snapshot);
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_size_allocate (GtkWidget *widget,
|
|
gint width,
|
|
gint height,
|
|
gint baseline)
|
|
{
|
|
GcalWeekGrid *self = GCAL_WEEK_GRID (widget);
|
|
g_autoptr (GDateTime) week_start = NULL;
|
|
GcalRangeTree *overlaps;
|
|
gboolean ltr;
|
|
gdouble minutes_height;
|
|
gdouble column_width;
|
|
guint x, y;
|
|
guint i;
|
|
gint today_column;
|
|
|
|
ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
|
|
|
|
/* Preliminary calculations */
|
|
minutes_height = (gdouble) height / MINUTES_PER_DAY;
|
|
column_width = (gdouble) width / 7.0;
|
|
|
|
/* Temporary range tree to hold positioned events' indexes */
|
|
overlaps = gcal_range_tree_new ();
|
|
|
|
week_start = gcal_date_time_get_start_of_week (self->active_date);
|
|
|
|
/*
|
|
* Iterate through weekdays; we don't have to worry about events that
|
|
* jump between days because they're already handled by GcalWeekHeader.
|
|
*/
|
|
for (i = 0; i < 7; i++)
|
|
{
|
|
g_autoptr (GcalRange) day_range = NULL;
|
|
GPtrArray *widgets_data;
|
|
guint j;
|
|
|
|
day_range = gcal_range_new_take (g_date_time_add_days (week_start, i),
|
|
g_date_time_add_days (week_start, i + 1),
|
|
GCAL_RANGE_DEFAULT);
|
|
widgets_data = gcal_range_tree_get_data_at_range (self->events, day_range);
|
|
|
|
for (j = 0; widgets_data && j < widgets_data->len; j++)
|
|
{
|
|
g_autoptr (GDateTime) event_start = NULL;
|
|
g_autoptr (GDateTime) event_end = NULL;
|
|
GtkAllocation child_allocation;
|
|
GtkWidget *event_widget;
|
|
GcalRange *event_range;
|
|
ChildData *data;
|
|
guint64 events_at_range;
|
|
gint event_minutes;
|
|
gint minimum_width;
|
|
gint minimum_height;
|
|
gint widget_index;
|
|
gint offset;
|
|
gint event_height;
|
|
gint event_width;
|
|
|
|
data = g_ptr_array_index (widgets_data, j);
|
|
event_widget = data->widget;
|
|
|
|
if (!gtk_widget_should_layout (event_widget))
|
|
continue;
|
|
|
|
event_range = gcal_event_get_range (data->event);
|
|
event_start = g_date_time_to_local (gcal_event_get_date_start (data->event));
|
|
event_end = g_date_time_to_local (gcal_event_get_date_end (data->event));
|
|
|
|
/* The total number of events available in this range */
|
|
events_at_range = count_overlaps_at_range (self->events, event_range);
|
|
|
|
/* The real horizontal position of this event */
|
|
widget_index = get_event_index (overlaps, event_range);
|
|
|
|
event_minutes = g_date_time_difference (event_end, event_start) / G_TIME_SPAN_MINUTE;
|
|
|
|
/* Compute the height of the widget */
|
|
gtk_widget_measure (event_widget, GTK_ORIENTATION_VERTICAL, -1, &minimum_height, NULL, NULL, NULL);
|
|
event_height = event_minutes * minutes_height;
|
|
event_height = MAX (minimum_height, event_height);
|
|
|
|
/* Compute the width of the widget */
|
|
gtk_widget_measure (event_widget, GTK_ORIENTATION_HORIZONTAL, event_height, &minimum_width, NULL, NULL, NULL);
|
|
event_width = column_width / events_at_range;
|
|
event_width = MAX (minimum_width, event_width);
|
|
|
|
offset = event_width * widget_index;
|
|
y = (g_date_time_get_hour (event_start) * 60 + g_date_time_get_minute (event_start)) * minutes_height;
|
|
|
|
if (ltr)
|
|
x = column_width * i + offset + 1;
|
|
else
|
|
x = width - event_width - (column_width * i + offset + x + 1);
|
|
|
|
/* Setup the child position and size */
|
|
child_allocation.x = x;
|
|
child_allocation.y = y;
|
|
child_allocation.width = event_width;
|
|
child_allocation.height = event_height;
|
|
|
|
gtk_widget_size_allocate (event_widget, &child_allocation, baseline);
|
|
|
|
/*
|
|
* Add the current event to the temporary overlaps tree so we have a way to
|
|
* know how many events are already positioned in the current column.
|
|
*/
|
|
gcal_range_tree_add_range (overlaps, event_range, GUINT_TO_POINTER (widget_index));
|
|
}
|
|
|
|
g_clear_pointer (&widgets_data, g_ptr_array_unref);
|
|
}
|
|
|
|
g_clear_pointer (&overlaps, gcal_range_tree_unref);
|
|
|
|
/* Today column */
|
|
today_column = get_today_column (GCAL_WEEK_GRID (widget));
|
|
|
|
gtk_widget_set_child_visible (self->now_strip, today_column != -1);
|
|
|
|
if (today_column != -1)
|
|
{
|
|
g_autoptr (GDateTime) now = NULL;
|
|
GtkAllocation allocation;
|
|
guint minutes_from_midnight;
|
|
gint now_strip_height;
|
|
|
|
now = g_date_time_new_now_local ();
|
|
minutes_from_midnight = g_date_time_get_hour (now) * 60 + g_date_time_get_minute (now);
|
|
|
|
gtk_widget_measure (self->now_strip,
|
|
GTK_ORIENTATION_VERTICAL,
|
|
-1,
|
|
&now_strip_height,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (ltr)
|
|
x = today_column * column_width;
|
|
else
|
|
x = width - (today_column * column_width) - column_width;
|
|
|
|
allocation.x = x;
|
|
allocation.y = round (minutes_from_midnight * ((gdouble) height / MINUTES_PER_DAY));
|
|
allocation.width = column_width;
|
|
allocation.height = MAX (1, now_strip_height);
|
|
|
|
gtk_widget_size_allocate (self->now_strip, &allocation, baseline);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_class_init (GcalWeekGridClass *klass)
|
|
{
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->dispose = gcal_week_grid_dispose;
|
|
object_class->finalize = gcal_week_grid_finalize;
|
|
object_class->get_property = gcal_week_grid_get_property;
|
|
object_class->set_property = gcal_week_grid_set_property;
|
|
|
|
widget_class->measure = gcal_week_grid_measure;
|
|
widget_class->size_allocate = gcal_week_grid_size_allocate;
|
|
widget_class->snapshot = gcal_week_grid_snapshot;
|
|
|
|
signals[EVENT_ACTIVATED] = g_signal_new ("event-activated",
|
|
GCAL_TYPE_WEEK_GRID,
|
|
G_SIGNAL_RUN_FIRST,
|
|
0, NULL, NULL, NULL,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GCAL_TYPE_EVENT_WIDGET);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, "weekgrid");
|
|
}
|
|
|
|
static void
|
|
gcal_week_grid_init (GcalWeekGrid *self)
|
|
{
|
|
GtkDropTarget *drop_target;
|
|
GtkGesture *click_gesture;
|
|
|
|
self->selection_start = -1;
|
|
self->selection_end = -1;
|
|
self->dnd_cell = -1;
|
|
|
|
self->events = gcal_range_tree_new_with_free_func (child_data_free);
|
|
|
|
self->now_strip = adw_bin_new ();
|
|
gtk_widget_add_css_class (self->now_strip, "now-strip");
|
|
gtk_widget_set_can_target (self->now_strip, FALSE);
|
|
gtk_widget_set_parent (self->now_strip, GTK_WIDGET (self));
|
|
|
|
click_gesture = gtk_gesture_click_new ();
|
|
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click_gesture), GDK_BUTTON_PRIMARY);
|
|
g_signal_connect (click_gesture, "pressed", G_CALLBACK (on_click_gesture_pressed_cb), self);
|
|
g_signal_connect (click_gesture, "released", G_CALLBACK (on_click_gesture_released_cb), self);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click_gesture));
|
|
|
|
self->motion_controller = gtk_event_controller_motion_new ();
|
|
g_signal_connect (self->motion_controller, "motion", G_CALLBACK (on_motion_controller_motion_cb), self);
|
|
gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_NONE);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), self->motion_controller);
|
|
|
|
drop_target = gtk_drop_target_new (GCAL_TYPE_EVENT_WIDGET, GDK_ACTION_COPY);
|
|
g_signal_connect (drop_target, "drop", G_CALLBACK (on_drop_target_drop_cb), self);
|
|
g_signal_connect (drop_target, "leave", G_CALLBACK (on_drop_target_leave_cb), self);
|
|
g_signal_connect (drop_target, "motion", G_CALLBACK (on_drop_target_motion_cb), self);
|
|
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
|
|
}
|
|
|
|
/* Public API */
|
|
void
|
|
gcal_week_grid_set_context (GcalWeekGrid *self,
|
|
GcalContext *context)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEEK_GRID (self));
|
|
|
|
self->context = context;
|
|
|
|
g_signal_connect_object (gcal_context_get_clock (context),
|
|
"minute-changed",
|
|
G_CALLBACK (gtk_widget_queue_resize),
|
|
self->now_strip,
|
|
G_CONNECT_SWAPPED);
|
|
}
|
|
|
|
void
|
|
gcal_week_grid_add_event (GcalWeekGrid *self,
|
|
GcalEvent *event)
|
|
{
|
|
GtkWidget *widget;
|
|
|
|
g_return_if_fail (GCAL_IS_WEEK_GRID (self));
|
|
|
|
g_object_ref (event);
|
|
|
|
widget = g_object_new (GCAL_TYPE_EVENT_WIDGET,
|
|
"context", self->context,
|
|
"event", event,
|
|
"orientation", GTK_ORIENTATION_VERTICAL,
|
|
"timestamp-policy", GCAL_TIMESTAMP_POLICY_START,
|
|
NULL);
|
|
|
|
gcal_range_tree_add_range (self->events,
|
|
gcal_event_get_range (event),
|
|
child_data_new (widget, event));
|
|
|
|
g_signal_connect (widget, "activate", G_CALLBACK (on_event_widget_activated_cb), self);
|
|
|
|
gtk_widget_set_parent (widget, GTK_WIDGET (self));
|
|
}
|
|
|
|
void
|
|
gcal_week_grid_remove_event (GcalWeekGrid *self,
|
|
const gchar *uid)
|
|
{
|
|
GPtrArray *widgets;
|
|
guint i;
|
|
|
|
g_return_if_fail (GCAL_IS_WEEK_GRID (self));
|
|
|
|
widgets = gcal_range_tree_get_all_data (self->events);
|
|
|
|
for (i = 0; widgets && i < widgets->len; i++)
|
|
{
|
|
ChildData *data;
|
|
GcalEvent *event;
|
|
|
|
data = g_ptr_array_index (widgets, i);
|
|
event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (data->widget));
|
|
|
|
if (g_strcmp0 (gcal_event_get_uid (event), uid) != 0)
|
|
continue;
|
|
|
|
gcal_range_tree_remove_range (self->events, gcal_event_get_range (event), data);
|
|
gtk_widget_queue_allocate (GTK_WIDGET (self));
|
|
}
|
|
|
|
g_clear_pointer (&widgets, g_ptr_array_unref);
|
|
}
|
|
|
|
GList*
|
|
gcal_week_grid_get_children_by_uuid (GcalWeekGrid *self,
|
|
GcalRecurrenceModType mod,
|
|
const gchar *uid)
|
|
{
|
|
g_autoptr (GList) result = NULL;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
result = filter_children_by_uid_and_modtype (GTK_WIDGET (self), mod, uid);
|
|
|
|
GCAL_RETURN (g_steal_pointer (&result));
|
|
}
|
|
|
|
void
|
|
gcal_week_grid_clear_marks (GcalWeekGrid *self)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEEK_GRID (self));
|
|
|
|
self->selection_start = -1;
|
|
self->selection_end = -1;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
}
|
|
|
|
void
|
|
gcal_week_grid_set_date (GcalWeekGrid *self,
|
|
GDateTime *date)
|
|
{
|
|
gcal_set_date_time (&self->active_date, date);
|
|
|
|
gtk_widget_queue_resize (GTK_WIDGET (self));
|
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
|
}
|