From bd989aae665fefa53a6826e72edca2323b5075c9 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 17 Aug 2023 21:09:44 -0300 Subject: [PATCH] views/month: Implement event creation flow Add a click gesture to detect when we have to start monitoring cursor motion. Also add a motion event controller to react to cursor motion, and enable and disable the this motion controller on button press and release, respectively. Selection is implemented as a pair of GDateTimes stored in the month view struct. Whenever a selection begins, the start date is set; the end date is only set when moving the cursor. It is valid to have a start of the selection, and no end (meaning, a 1 day selection); it is, however, forbidden to have an end but not a start. Future improvements can be added later, such as scrolling when moving the cursor close to the edges of the month view. We're gonna have to live without that for now though. Implement the GcalView.clear_marks vfunc now that we have a selection to operate on. --- src/gui/views/gcal-month-view-row.c | 41 +++++++ src/gui/views/gcal-month-view-row.h | 5 + src/gui/views/gcal-month-view.c | 176 +++++++++++++++++++++++++++- src/gui/views/gcal-month-view.ui | 15 +++ 4 files changed, 236 insertions(+), 1 deletion(-) diff --git a/src/gui/views/gcal-month-view-row.c b/src/gui/views/gcal-month-view-row.c index 87aea581..45461759 100644 --- a/src/gui/views/gcal-month-view-row.c +++ b/src/gui/views/gcal-month-view-row.c @@ -775,3 +775,44 @@ gcal_month_view_row_get_children_by_uuid (GcalMonthViewRow *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); + } +} diff --git a/src/gui/views/gcal-month-view-row.h b/src/gui/views/gcal-month-view-row.h index 2d4177ad..d3428409 100644 --- a/src/gui/views/gcal-month-view-row.h +++ b/src/gui/views/gcal-month-view-row.h @@ -57,4 +57,9 @@ GList* gcal_month_view_row_get_children_by_uuid (GcalMonthViewR GcalRecurrenceModType mod, const gchar *uuid); +GtkWidget* gcal_month_view_row_get_cell_at_x (GcalMonthViewRow *self, + gdouble x); + +void gcal_month_view_row_update_selection (GcalMonthViewRow *self, + GcalRange *selection_range); G_END_DECLS diff --git a/src/gui/views/gcal-month-view.c b/src/gui/views/gcal-month-view.c index e20eee8f..af0bcfc3 100644 --- a/src/gui/views/gcal-month-view.c +++ b/src/gui/views/gcal-month-view.c @@ -52,6 +52,8 @@ struct _GcalMonthView GtkWidget *year_label; GtkWidget *weekday_label[7]; + GtkEventController *motion_controller; + GPtrArray *week_rows; GHashTable *events; @@ -61,6 +63,11 @@ struct _GcalMonthView GtkWidget *relative_to; } overflow; + struct { + GDateTime *start; + GDateTime *end; + } selection; + /* Ranges from [0.0, 1.0] */ gdouble row_offset; AdwAnimation *row_offset_animation; @@ -676,11 +683,161 @@ animate_row_scroll (GcalMonthView *self, GCAL_EXIT; } +static void +update_selection_range (GcalMonthView *self) +{ + g_autoptr (GcalRange) selection_range = NULL; + GDateTime *selection_start; + GDateTime *selection_end; + + selection_start = self->selection.start; + selection_end = self->selection.end; + + if (selection_start) + { + if (!selection_end) + selection_end = selection_start; + + /* Swap dates if end is before start */ + if (gcal_date_time_compare_date (selection_start, selection_end) > 0) + { + GDateTime *aux = selection_end; + selection_end = selection_start; + selection_start = aux; + } + + selection_range = gcal_range_new (selection_start, selection_end, GCAL_RANGE_DATE_ONLY); + } + else + { + g_assert (selection_end == NULL); + selection_range = NULL; + } + + for (gint i = 0; i < self->week_rows->len; i++) + { + GcalMonthViewRow *row = g_ptr_array_index (self->week_rows, i); + gcal_month_view_row_update_selection (row, selection_range); + } +} + + /* * Callbacks */ +static void +on_click_gesture_pressed_cb (GtkGestureClick *click_gesture, + gint n_press, + gdouble x, + gdouble y, + GcalMonthView *self) +{ + GtkWidget *widget_at_position; + GtkWidget *day_cell; + + GCAL_ENTRY; + + widget_at_position = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT); + day_cell = gtk_widget_get_ancestor (widget_at_position, GCAL_TYPE_MONTH_CELL); + + if (!day_cell) + GCAL_RETURN (); + + g_assert (GCAL_IS_MONTH_CELL (day_cell)); + + gcal_clear_date_time (&self->selection.start); + self->selection.start = g_date_time_ref (gcal_month_cell_get_date (GCAL_MONTH_CELL (day_cell))); + + gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_BUBBLE); + + update_selection_range (self); + + GCAL_EXIT; +} + +static void +on_click_gesture_released_cb (GtkGestureClick *click_gesture, + gint n_press, + gdouble x, + gdouble y, + GcalMonthView *self) +{ + g_autoptr (GcalRange) selection_range = NULL; + g_autoptr (GDateTime) selection_start = NULL; + g_autoptr (GDateTime) selection_end = NULL; + GtkWidget *widget_at_position; + + GCAL_ENTRY; + + gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_NONE); + + widget_at_position = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT); + + if (!widget_at_position || !gtk_widget_get_ancestor (widget_at_position, GCAL_TYPE_MONTH_VIEW_ROW)) + { + gcal_view_clear_marks (GCAL_VIEW (self)); + GCAL_RETURN (); + } + + g_assert (self->selection.start != NULL); + + selection_start = self->selection.start; + selection_end = self->selection.end ?: selection_start; + + /* Swap dates if end is before start */ + if (gcal_date_time_compare_date (selection_start, selection_end) > 0) + { + GDateTime *aux = selection_end; + selection_end = selection_start; + selection_start = aux; + } + + selection_start = g_date_time_ref (selection_start); + selection_end = g_date_time_add_days (selection_end, 1); + + selection_range = gcal_range_new (selection_start, selection_end, GCAL_RANGE_DEFAULT); + gcal_view_create_event (GCAL_VIEW (self), selection_range, x, y); + + GCAL_EXIT; +} + +static void +on_motion_controller_motion_cb (GtkEventControllerMotion *motion_controller, + gdouble x, + gdouble y, + GcalMonthView *self) +{ + GtkWidget *widget_at_position; + GtkWidget *day_cell; + GtkWidget *row; + + GCAL_ENTRY; + + widget_at_position = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT); + + if (!widget_at_position) + GCAL_RETURN (); + + row = gtk_widget_get_ancestor (widget_at_position, GCAL_TYPE_MONTH_VIEW_ROW); + + if (!row) + GCAL_RETURN (); + + day_cell = gcal_month_view_row_get_cell_at_x (GCAL_MONTH_VIEW_ROW (row), x); + + g_assert (day_cell != NULL); + g_assert (GCAL_IS_MONTH_CELL (day_cell)); + + gcal_clear_date_time (&self->selection.end); + self->selection.end = g_date_time_ref (gcal_month_cell_get_date (GCAL_MONTH_CELL (day_cell))); + + update_selection_range (self); + + GCAL_EXIT; +} + static void on_event_widget_activated_cb (GcalMonthViewRow *row, GcalEventWidget *event_widget, @@ -1076,7 +1233,18 @@ gcal_month_view_set_date (GcalView *view, static void gcal_month_view_clear_marks (GcalView *view) { - GCAL_TODO ("gcal_month_view_clear_marks"); + GcalMonthView *self; + + GCAL_ENTRY; + + self = GCAL_MONTH_VIEW (view); + + gcal_clear_date_time (&self->selection.start); + gcal_clear_date_time (&self->selection.end); + + update_selection_range (self); + + GCAL_EXIT; } static GList* @@ -1347,6 +1515,8 @@ gcal_month_view_finalize (GObject *object) { GcalMonthView *self = (GcalMonthView *)object; + gcal_clear_date_time (&self->selection.start); + gcal_clear_date_time (&self->selection.end); gcal_clear_date_time (&self->date); g_clear_object (&self->context); @@ -1426,6 +1596,7 @@ gcal_month_view_class_init (GcalMonthViewClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/views/gcal-month-view.ui"); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, month_label); + gtk_widget_class_bind_template_child (widget_class, GcalMonthView, motion_controller); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, year_label); gtk_widget_class_bind_template_child_full (widget_class, "label_0", FALSE, G_STRUCT_OFFSET (GcalMonthView, weekday_label[0])); @@ -1436,7 +1607,10 @@ gcal_month_view_class_init (GcalMonthViewClass *klass) gtk_widget_class_bind_template_child_full (widget_class, "label_5", FALSE, G_STRUCT_OFFSET (GcalMonthView, weekday_label[5])); gtk_widget_class_bind_template_child_full (widget_class, "label_6", FALSE, G_STRUCT_OFFSET (GcalMonthView, weekday_label[6])); + gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_click_gesture_released_cb); gtk_widget_class_bind_template_callback (widget_class, on_discrete_scroll_controller_scroll_cb); + gtk_widget_class_bind_template_callback (widget_class, on_motion_controller_motion_cb); gtk_widget_class_bind_template_callback (widget_class, on_scroll_controller_scroll_begin_cb); gtk_widget_class_bind_template_callback (widget_class, on_scroll_controller_scroll_cb); gtk_widget_class_bind_template_callback (widget_class, on_scroll_controller_scroll_end_cb); diff --git a/src/gui/views/gcal-month-view.ui b/src/gui/views/gcal-month-view.ui index 4ee26af5..cac56cd1 100644 --- a/src/gui/views/gcal-month-view.ui +++ b/src/gui/views/gcal-month-view.ui @@ -20,6 +20,21 @@ + + + 1 + + + + + + + + none + + + +