[gnome-calendar/gbsneto/gtk4: 44/46] Reimplement event drag n' drop




commit ee5acbd493ab82ae4f18080dc926b969f1789f93
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Sat Feb 12 09:49:51 2022 -0300

    Reimplement event drag n' drop
    
    Reintroduce the code using GTK4 tooling for in-process DnD.
    This is looking much better!

 src/core/gcal-recurrence.h       |   1 +
 src/gui/views/gcal-month-cell.c  | 201 +++++++++++------------
 src/gui/views/gcal-week-grid.c   | 325 ++++++++++++++++++-------------------
 src/gui/views/gcal-week-header.c | 334 +++++++++++++++++++++------------------
 src/theme/Adwaita.css            |   3 +
 src/utils/gcal-utils.c           |  83 ++++++++++
 src/utils/gcal-utils.h           |   9 ++
 7 files changed, 529 insertions(+), 427 deletions(-)
---
diff --git a/src/core/gcal-recurrence.h b/src/core/gcal-recurrence.h
index 340db66a..7b536706 100644
--- a/src/core/gcal-recurrence.h
+++ b/src/core/gcal-recurrence.h
@@ -48,6 +48,7 @@ typedef enum
 
 typedef enum
 {
+  GCAL_RECURRENCE_MOD_NONE            = 0,
   GCAL_RECURRENCE_MOD_THIS_ONLY       = E_CAL_OBJ_MOD_THIS,
   GCAL_RECURRENCE_MOD_THIS_AND_FUTURE = E_CAL_OBJ_MOD_THIS_AND_FUTURE,
   GCAL_RECURRENCE_MOD_ALL             = E_CAL_OBJ_MOD_ALL,
diff --git a/src/gui/views/gcal-month-cell.c b/src/gui/views/gcal-month-cell.c
index c0b6cc3c..1e38fa5e 100644
--- a/src/gui/views/gcal-month-cell.c
+++ b/src/gui/views/gcal-month-cell.c
@@ -88,92 +88,23 @@ update_style_flags (GcalMonthCell *self)
     gtk_widget_remove_css_class (GTK_WIDGET (self), "today");
 }
 
-
-/*
- * Callbacks
- */
-
 static void
-day_changed_cb (GcalClock     *clock,
-                GcalMonthCell *self)
+move_event (GcalMonthCell         *self,
+            GcalEvent             *event,
+            GcalRecurrenceModType  mod_type)
 {
-  update_style_flags (self);
-}
 
-static void
-overflow_button_clicked_cb (GtkWidget     *button,
-                            GcalMonthCell *self)
-{
-  g_signal_emit (self, signals[SHOW_OVERFLOW], 0, button);
-}
-
-/*
- * GtkWidget overrides
- */
-
-#if 0 // TODO: DND
-
-static gboolean
-gcal_month_cell_drag_motion (GtkWidget      *widget,
-                             GdkDragContext *context,
-                             gint            x,
-                             gint            y,
-                             guint           time)
-{
-  GcalMonthCell *self;
-
-  self = GCAL_MONTH_CELL (widget);
-
-  if (self->different_month)
-    gtk_drag_unhighlight (widget);
-  else
-    gtk_drag_highlight (widget);
-
-  gdk_drag_status (context, self->different_month ? 0 : GDK_ACTION_MOVE, time);
-
-  return !self->different_month;
-}
-
-static gboolean
-gcal_month_cell_drag_drop (GtkWidget      *widget,
-                           GdkDragContext *context,
-                           gint            x,
-                           gint            y,
-                           guint           time)
-{
   g_autoptr (GcalEvent) changed_event = NULL;
-  GcalRecurrenceModType mod;
-  GcalMonthCell *self;
-  GcalCalendar *calendar;
-  GtkWidget *event_widget;
-  GDateTime *start_dt, *end_dt;
-  GcalEvent *event;
+  g_autoptr (GDateTime) start_dt = NULL;
+  GTimeSpan timespan = 0;
+  GDateTime *end_dt;
   gint diff;
   gint start_month, current_month;
   gint start_year, current_year;
-  GTimeSpan timespan = 0;
-
-  self = GCAL_MONTH_CELL (widget);
-  event_widget = gtk_drag_get_source_widget (context);
-  mod = GCAL_RECURRENCE_MOD_THIS_ONLY;
 
   GCAL_ENTRY;
 
-  if (!GCAL_IS_EVENT_WIDGET (event_widget))
-    GCAL_RETURN (FALSE);
-
-  if (self->different_month)
-    GCAL_RETURN (FALSE);
-
-  event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
   changed_event = gcal_event_new_from_event (event);
-  calendar = gcal_event_get_calendar (changed_event);
-
-  if (gcal_event_has_recurrence (changed_event) &&
-      !ask_recurrence_modification_type (widget, &mod, calendar))
-    {
-      GCAL_GOTO (out);
-    }
 
   /* Move the event's date */
   start_dt = gcal_event_get_date_start (changed_event);
@@ -194,47 +125,107 @@ gcal_month_cell_drag_drop (GtkWidget      *widget,
 
   diff = g_date_time_get_day_of_month (self->date) - g_date_time_get_day_of_month (start_dt);
 
-  if (diff != 0 ||
-      current_month != start_month ||
-      current_year != start_year)
+  if (diff != 0 || current_month != start_month || current_year != start_year)
     {
-      g_autoptr (GDateTime) new_start = NULL;
-
-       new_start = g_date_time_add_days (start_dt, diff);
+      g_autoptr (GDateTime) new_start = g_date_time_add_days (start_dt, diff);
 
       gcal_event_set_date_start (changed_event, new_start);
 
       /* The event may have a NULL end date, so we have to check it here */
       if (end_dt)
         {
-          GDateTime *new_end = g_date_time_add (new_start, timespan);
+          g_autoptr (GDateTime) new_end = g_date_time_add (new_start, timespan);
 
           gcal_event_set_date_end (changed_event, new_end);
-          g_clear_pointer (&new_end, g_date_time_unref);
         }
 
-      gcal_manager_update_event (gcal_context_get_manager (self->context), changed_event, mod);
+      gcal_manager_update_event (gcal_context_get_manager (self->context), changed_event, mod_type);
     }
+}
 
-  g_clear_pointer (&start_dt, g_date_time_unref);
 
-out:
-  /* Cancel the DnD */
-  gtk_drag_unhighlight (widget);
-  gtk_drag_finish (context, TRUE, FALSE, time);
+/*
+ * Callbacks
+ */
 
-  GCAL_RETURN (TRUE);
+static void
+day_changed_cb (GcalClock     *clock,
+                GcalMonthCell *self)
+{
+  update_style_flags (self);
+}
+
+static void
+overflow_button_clicked_cb (GtkWidget     *button,
+                            GcalMonthCell *self)
+{
+  g_signal_emit (self, signals[SHOW_OVERFLOW], 0, button);
+}
+
+static gboolean
+on_drop_target_accept_cb (GtkDropTarget *drop_target,
+                          GdkDrop       *drop,
+                          GcalMonthCell *self)
+{
+  GCAL_ENTRY;
+
+  if ((gdk_drop_get_actions (drop) & gtk_drop_target_get_actions (drop_target)) == 0)
+    GCAL_RETURN (FALSE);
+
+  if (!gdk_content_formats_contain_gtype (gdk_drop_get_formats (drop), GCAL_TYPE_EVENT_WIDGET))
+    GCAL_RETURN (FALSE);
+
+  GCAL_RETURN (!self->different_month);
 }
 
 static void
-gcal_month_cell_drag_leave (GtkWidget      *widget,
-                            GdkDragContext *context,
-                            guint           time)
+on_ask_recurrence_response_cb (GcalEvent             *event,
+                               GcalRecurrenceModType  mod_type,
+                               gpointer               user_data)
+{
+  GcalMonthCell *self = GCAL_MONTH_CELL (user_data);
+
+  if (mod_type != GCAL_RECURRENCE_MOD_NONE)
+    move_event (self, event, mod_type);
+}
+
+static gboolean
+on_drop_target_drop_cb (GtkDropTarget *drop_target,
+                        const GValue  *value,
+                        gdouble        x,
+                        gdouble        y,
+                        GcalMonthCell *self)
 {
-  gtk_drag_unhighlight (widget);
+  GcalEventWidget *event_widget;
+  GcalEvent *event;
+
+  GCAL_ENTRY;
+
+  if (self->different_month)
+    GCAL_RETURN (FALSE);
+
+  if (!G_VALUE_HOLDS (value, GCAL_TYPE_EVENT_WIDGET))
+    GCAL_RETURN (FALSE);
+
+  event_widget = g_value_get_object (value);
+
+  event = gcal_event_widget_get_event (event_widget);
+
+  if (gcal_event_has_recurrence (event))
+    {
+      gcal_utils_ask_recurrence_modification_type (GTK_WIDGET (self),
+                                                   event,
+                                                   on_ask_recurrence_response_cb,
+                                                   self);
+    }
+  else
+    {
+      move_event (self, event, GCAL_RECURRENCE_MOD_THIS_ONLY);
+    }
+
+  GCAL_RETURN (TRUE);
 }
 
-#endif
 
 /*
  * GObject overrides
@@ -302,12 +293,6 @@ gcal_month_cell_class_init (GcalMonthCellClass *klass)
   object_class->set_property = gcal_month_cell_set_property;
   object_class->get_property = gcal_month_cell_get_property;
 
-#if 0 // TODO: DND
-  widget_class->drag_motion = gcal_month_cell_drag_motion;
-  widget_class->drag_drop = gcal_month_cell_drag_drop;
-  widget_class->drag_leave = gcal_month_cell_drag_leave;
-#endif
-
   signals[SHOW_OVERFLOW] = g_signal_new ("show-overflow",
                                          GCAL_TYPE_MONTH_CELL,
                                          G_SIGNAL_RUN_LAST,
@@ -342,16 +327,14 @@ gcal_month_cell_class_init (GcalMonthCellClass *klass)
 static void
 gcal_month_cell_init (GcalMonthCell *self)
 {
+  GtkDropTarget *drop_target;
+
   gtk_widget_init_template (GTK_WIDGET (self));
 
-#if 0 // TODO: DND
-  /* Setup the month cell as a drag n' drop destination */
-  gtk_drag_dest_set (GTK_WIDGET (self),
-                     0,
-                     NULL,
-                     0,
-                     GDK_ACTION_MOVE);
-#endif
+  drop_target = gtk_drop_target_new (GCAL_TYPE_EVENT_WIDGET, GDK_ACTION_COPY);
+  g_signal_connect (drop_target, "accept", G_CALLBACK (on_drop_target_accept_cb), self);
+  g_signal_connect (drop_target, "drop", G_CALLBACK (on_drop_target_drop_cb), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
 }
 
 GtkWidget*
diff --git a/src/gui/views/gcal-week-grid.c b/src/gui/views/gcal-week-grid.c
index 3c9236a3..47f77f93 100644
--- a/src/gui/views/gcal-week-grid.c
+++ b/src/gui/views/gcal-week-grid.c
@@ -35,6 +35,13 @@
 #include <string.h>
 #include <math.h>
 
+typedef struct
+{
+  GcalWeekGrid       *self;
+  GcalEvent          *event;
+  gint                drop_cell;
+} DropData;
+
 typedef struct
 {
   GtkWidget          *widget;
@@ -322,6 +329,156 @@ on_click_gesture_released_cb (GtkGestureClick *click_gesture,
   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)
 {
@@ -689,160 +846,6 @@ gcal_week_grid_size_allocate (GtkWidget *widget,
     }
 }
 
-#if 0 // TODO: DND
-static gint
-get_dnd_cell (GtkWidget *widget,
-              gint       x,
-              gint       y)
-{
-  GtkAllocation alloc;
-  gdouble column_width, cell_height;
-  gint column, row;
-
-  gtk_widget_get_allocation (widget, &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 gboolean
-gcal_week_grid_drag_motion (GtkWidget      *widget,
-                            GdkDragContext *context,
-                            gint            x,
-                            gint            y,
-                            guint           time)
-{
-  GcalWeekGrid *self;
-
-  self = GCAL_WEEK_GRID (widget);
-  self->dnd_cell = get_dnd_cell (widget, x, y);
-
-  /* Setup the drag highlight */
-  if (self->dnd_cell != -1)
-    gtk_drag_highlight (widget);
-  else
-    gtk_drag_unhighlight (widget);
-
-  /*
-   * Sets the status of the drag - if it fails, sets the action to 0 and
-   * aborts the drag with FALSE.
-   */
-  gdk_drag_status (context,
-                   self->dnd_cell == -1 ? 0 : GDK_ACTION_MOVE,
-                   time);
-
-  gtk_widget_queue_draw (widget);
-
-  return self->dnd_cell != -1;
-}
-
-static gboolean
-gcal_week_grid_drag_drop (GtkWidget      *widget,
-                          GdkDragContext *context,
-                          gint            x,
-                          gint            y,
-                          guint           time)
-{
-  g_autoptr (GDateTime) week_start = NULL;
-  g_autoptr (GDateTime) dnd_date = NULL;
-  g_autoptr (GDateTime) new_end = NULL;
-  g_autoptr (GcalEvent) changed_event = NULL;
-  GcalRecurrenceModType mod;
-  GcalWeekGrid *self;
-  GcalCalendar *calendar;
-  GtkWidget *event_widget;
-  GcalEvent *event;
-  GTimeSpan timespan = 0;
-  gboolean ltr;
-  gint drop_cell;
-
-  self = GCAL_WEEK_GRID (widget);
-  ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
-  drop_cell = get_dnd_cell (widget, x, y);
-  event_widget = gtk_drag_get_source_widget (context);
-
-  mod = GCAL_RECURRENCE_MOD_THIS_ONLY;
-
-  if (!GCAL_IS_EVENT_WIDGET (event_widget))
-    return FALSE;
-
-  /* RTL languages swap the drop cell column */
-  if (!ltr)
-    {
-      gint column, row;
-
-      column = drop_cell / (MINUTES_PER_DAY / 30);
-      row = drop_cell - column * 48;
-
-      drop_cell = (6 - column) * 48 + row;
-    }
-
-  event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
-  changed_event = gcal_event_new_from_event (event);
-  calendar = gcal_event_get_calendar (changed_event);
-
-  if (gcal_event_has_recurrence (changed_event) &&
-      !ask_recurrence_modification_type (widget, &mod, calendar))
-    {
-      goto out;
-    }
-
-  week_start = gcal_date_time_get_start_of_week (self->active_date);
-  dnd_date = g_date_time_add_minutes (week_start, drop_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);
-
-out:
-  /* Cancel the DnD */
-  self->dnd_cell = -1;
-  gtk_drag_unhighlight (widget);
-
-  gtk_drag_finish (context, TRUE, FALSE, time);
-
-  gtk_widget_queue_draw (widget);
-
-  return TRUE;
-}
-
-static void
-gcal_week_grid_drag_leave (GtkWidget      *widget,
-                           GdkDragContext *context,
-                           guint           time)
-{
-  GcalWeekGrid *self = GCAL_WEEK_GRID (widget);
-
-  /* Cancel the drag */
-  self->dnd_cell = -1;
-  gtk_drag_unhighlight (widget);
-
-  gtk_widget_queue_draw (widget);
-}
-#endif
-
 static void
 gcal_week_grid_class_init (GcalWeekGridClass *klass)
 {
@@ -872,6 +875,7 @@ gcal_week_grid_class_init (GcalWeekGridClass *klass)
 static void
 gcal_week_grid_init (GcalWeekGrid *self)
 {
+  GtkDropTarget *drop_target;
   GtkGesture *click_gesture;
 
   self->selection_start = -1;
@@ -896,14 +900,11 @@ gcal_week_grid_init (GcalWeekGrid *self)
   gtk_event_controller_set_propagation_phase (self->motion_controller, GTK_PHASE_NONE);
   gtk_widget_add_controller (GTK_WIDGET (self), self->motion_controller);
 
-#if 0 // TODO: DND
-  /* Setup the week view as a drag n' drop destination */
-  gtk_drag_dest_set (GTK_WIDGET (self),
-                     0,
-                     NULL,
-                     0,
-                     GDK_ACTION_MOVE);
-#endif
+  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 */
diff --git a/src/gui/views/gcal-week-header.c b/src/gui/views/gcal-week-header.c
index e026b736..571f2342 100644
--- a/src/gui/views/gcal-week-header.c
+++ b/src/gui/views/gcal-week-header.c
@@ -60,6 +60,13 @@ typedef struct
   GtkWidget          *weekday_name_label;
 } WeekdayHeader;
 
+typedef struct
+{
+  GcalWeekHeader     *self;
+  GcalEvent          *event;
+  gint                drop_cell;
+} DropData;
+
 struct _GcalWeekHeader
 {
   GtkBox              parent;
@@ -1144,6 +1151,171 @@ on_expand_action_activated (GcalWeekHeader *self,
     header_expand (self);
 }
 
+static gint
+get_dnd_cell (GcalWeekHeader *self,
+              gint            x,
+              gint            y)
+{
+  gdouble column_width;
+
+  column_width = gtk_widget_get_allocated_width (GTK_WIDGET (self)) / 7.0;
+
+  return x / column_width;
+}
+
+static void
+move_event_to_cell (GcalWeekHeader        *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 (GDateTime) tmp_dt = NULL;
+  g_autoptr (GcalEvent) changed_event = NULL;
+  GDateTime *start_date;
+  GDateTime *end_date;
+  GTimeSpan difference;
+  gboolean turn_all_day;
+
+  GCAL_ENTRY;
+
+  /* RTL languages swap the drop cell column */
+  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+    cell = 6 - cell;
+
+  changed_event = gcal_event_new_from_event (event);
+  start_date = gcal_event_get_date_start (changed_event);
+  end_date = gcal_event_get_date_end (changed_event);
+  week_start = gcal_date_time_get_start_of_week (self->active_date);
+
+  turn_all_day = !gcal_event_is_multiday (changed_event) || gcal_event_get_all_day (changed_event);
+
+  if (!turn_all_day)
+    {
+      /*
+       * The only case where we don't touch the timezone is for
+       * timed, multiday events.
+       */
+      tmp_dt = g_date_time_new (g_date_time_get_timezone (start_date),
+                                g_date_time_get_year (week_start),
+                                g_date_time_get_month (week_start),
+                                g_date_time_get_day_of_month (week_start),
+                                g_date_time_get_hour (start_date),
+                                g_date_time_get_minute (start_date),
+                                0);
+    }
+  else
+    {
+      tmp_dt = g_date_time_new_utc (g_date_time_get_year (week_start),
+                                    g_date_time_get_month (week_start),
+                                    g_date_time_get_day_of_month (week_start),
+                                    0, 0, 0);
+    }
+  dnd_date = g_date_time_add_days (tmp_dt, cell);
+
+  /* End date */
+  difference = turn_all_day ? 24 : g_date_time_difference (end_date, start_date) / G_TIME_SPAN_HOUR;
+
+  new_end = g_date_time_add_hours (dnd_date, difference);
+  gcal_event_set_date_end (changed_event, new_end);
+
+  /*
+   * Set the start date ~after~ the end date, so we can compare
+   * the event's start and end dates above
+   */
+  gcal_event_set_date_start (changed_event, dnd_date);
+
+  if (turn_all_day)
+    gcal_event_set_all_day (changed_event, TRUE);
+
+  /* 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,
+                        GcalWeekHeader *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,
+                         GcalWeekHeader *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,
+                          GcalWeekHeader *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);
+}
+
 /* Drawing area content and size */
 
 static void
@@ -1264,154 +1436,6 @@ gcal_week_header_snapshot (GtkWidget   *widget,
   g_clear_pointer (&week_end, g_date_time_unref);
 }
 
-#if 0 // TODO: DND
-static gint
-get_dnd_cell (GtkWidget *widget,
-              gint       x,
-              gint       y)
-{
-  gdouble column_width;
-
-  column_width = gtk_widget_get_allocated_width (widget) / 7.0;
-
-  return x / column_width;
-}
-
-static gboolean
-gcal_week_header_drag_motion (GtkWidget      *widget,
-                              GdkDragContext *context,
-                              gint            x,
-                              gint            y,
-                              guint           time)
-{
-  GcalWeekHeader *self;
-
-  self = GCAL_WEEK_HEADER (widget);
-  self->dnd_cell = get_dnd_cell (widget, x, y);
-
-  /*
-   * Sets the status of the drag - if it fails, sets the action to 0 and
-   * aborts the drag with FALSE.
-   */
-  gdk_drag_status (context,
-                   self->dnd_cell == -1 ? 0 : GDK_ACTION_MOVE,
-                   time);
-
-  gtk_widget_queue_draw (widget);
-
-  return self->dnd_cell != -1;
-}
-
-static gboolean
-gcal_week_header_drag_drop (GtkWidget      *widget,
-                            GdkDragContext *context,
-                            gint            x,
-                            gint            y,
-                            guint           time)
-{
-  g_autoptr (GDateTime) week_start = NULL;
-  g_autoptr (GDateTime) dnd_date = NULL;
-  g_autoptr (GDateTime) new_end = NULL;
-  g_autoptr (GDateTime) tmp_dt = NULL;
-  g_autoptr (GcalEvent) changed_event = NULL;
-  GcalWeekHeader *self;
-  GDateTime *start_date;
-  GDateTime *end_date;
-  GTimeSpan difference;
-  GtkWidget *event_widget;
-  GcalEvent *event;
-  gboolean turn_all_day;
-  gboolean ltr;
-  gint drop_cell;
-
-  GCAL_ENTRY;
-
-  self = GCAL_WEEK_HEADER (widget);
-  ltr = gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL;
-  drop_cell = get_dnd_cell (widget, x, y);
-  event_widget = gtk_drag_get_source_widget (context);
-
-  if (!GCAL_IS_EVENT_WIDGET (event_widget))
-    return FALSE;
-
-  /* RTL languages swap the drop cell column */
-  if (!ltr)
-    drop_cell = 6 - drop_cell;
-
-  event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (event_widget));
-  changed_event = gcal_event_new_from_event (event);
-  start_date = gcal_event_get_date_start (changed_event);
-  end_date = gcal_event_get_date_end (changed_event);
-  week_start = gcal_date_time_get_start_of_week (self->active_date);
-
-  turn_all_day = !gcal_event_is_multiday (changed_event) || gcal_event_get_all_day (changed_event);
-
-  if (!turn_all_day)
-    {
-      /*
-       * The only case where we don't touch the timezone is for
-       * timed, multiday events.
-       */
-      tmp_dt = g_date_time_new (g_date_time_get_timezone (start_date),
-                                g_date_time_get_year (week_start),
-                                g_date_time_get_month (week_start),
-                                g_date_time_get_day_of_month (week_start),
-                                g_date_time_get_hour (start_date),
-                                g_date_time_get_minute (start_date),
-                                0);
-    }
-  else
-    {
-      tmp_dt = g_date_time_new_utc (g_date_time_get_year (week_start),
-                                    g_date_time_get_month (week_start),
-                                    g_date_time_get_day_of_month (week_start),
-                                    0, 0, 0);
-    }
-  dnd_date = g_date_time_add_days (tmp_dt, drop_cell);
-
-  /* End date */
-  difference = turn_all_day ? 24 : g_date_time_difference (end_date, start_date) / G_TIME_SPAN_HOUR;
-
-  new_end = g_date_time_add_hours (dnd_date, difference);
-  gcal_event_set_date_end (changed_event, new_end);
-
-  /*
-   * Set the start date ~after~ the end date, so we can compare
-   * the event's start and end dates above
-   */
-  gcal_event_set_date_start (changed_event, dnd_date);
-
-  if (turn_all_day)
-    gcal_event_set_all_day (changed_event, TRUE);
-
-  /* Commit the changes */
-  gcal_manager_update_event (gcal_context_get_manager (self->context),
-                             changed_event,
-                             GCAL_RECURRENCE_MOD_THIS_ONLY);
-
-  /* Cancel the DnD */
-  self->dnd_cell = -1;
-
-  gtk_drag_finish (context, TRUE, FALSE, time);
-
-  gtk_widget_queue_draw (widget);
-
-  GCAL_RETURN (TRUE);
-}
-
-static void
-gcal_week_header_drag_leave (GtkWidget      *widget,
-                             GdkDragContext *context,
-                             guint           time)
-{
-  GcalWeekHeader *self = GCAL_WEEK_HEADER (widget);
-
-  /* Cancel the drag */
-  self->dnd_cell = -1;
-
-  gtk_widget_queue_draw (widget);
-}
-#endif
 
 /*
  * GObject overrides
@@ -1477,6 +1501,7 @@ gcal_week_header_class_init (GcalWeekHeaderClass *kclass)
 static void
 gcal_week_header_init (GcalWeekHeader *self)
 {
+  GtkDropTarget *drop_target;
   gint i;
 
   self->expanded = FALSE;
@@ -1515,14 +1540,11 @@ gcal_week_header_init (GcalWeekHeader *self)
       gtk_grid_attach (self->grid, gtk_box_new (GTK_ORIENTATION_VERTICAL, 0), i, 0, 1, 1);
     }
 
-#if 0 // TODO: DND
-  /* Setup the week header as a drag n' drop destination */
-  gtk_drag_dest_set (GTK_WIDGET (self),
-                     0,
-                     NULL,
-                     0,
-                     GDK_ACTION_MOVE);
-#endif
+  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));
 }
 
 void
diff --git a/src/theme/Adwaita.css b/src/theme/Adwaita.css
index 7090d83e..0e3abe67 100644
--- a/src/theme/Adwaita.css
+++ b/src/theme/Adwaita.css
@@ -328,6 +328,9 @@ weekgrid:selected,
     background-color: alpha(@accent_bg_color, 0.25);
 }
 
+weekgrid.dnd, .week-header.dnd {
+    background-color: alpha(@accent_bg_color, 0.25);
+}
 
 /*
  * Month cell
diff --git a/src/utils/gcal-utils.c b/src/utils/gcal-utils.c
index 6cff7aeb..576cef06 100644
--- a/src/utils/gcal-utils.c
+++ b/src/utils/gcal-utils.c
@@ -1332,3 +1332,86 @@ out:
   if (out_meeting_url)
     *out_meeting_url = g_steal_pointer (&meeting_url);
 }
+
+typedef struct
+{
+  GcalEvent                 *event;
+  GcalAskRecurrenceCallback  callback;
+  gpointer                   user_data;
+} AskRecurrenceData;
+
+static void
+on_message_dialog_response_cb (GtkDialog         *dialog,
+                               gint               response,
+                               AskRecurrenceData *data)
+{
+  GcalRecurrenceModType mod_type;
+
+  switch (response)
+    {
+      case GTK_RESPONSE_CANCEL:
+        mod_type = GCAL_RECURRENCE_MOD_NONE;
+        break;
+      case GTK_RESPONSE_ACCEPT:
+        mod_type = GCAL_RECURRENCE_MOD_THIS_ONLY;
+        break;
+      case GTK_RESPONSE_OK:
+        mod_type = GCAL_RECURRENCE_MOD_THIS_AND_FUTURE;
+        break;
+      case GTK_RESPONSE_YES:
+        mod_type = GCAL_RECURRENCE_MOD_ALL;
+        break;
+      default:
+        mod_type = GCAL_RECURRENCE_MOD_NONE;
+        break;
+    }
+
+  gtk_window_destroy (GTK_WINDOW (dialog));
+
+  data->callback (data->event, mod_type, data->user_data);
+  g_clear_object (&data->event);
+  g_clear_pointer (&data, g_free);
+}
+
+void
+gcal_utils_ask_recurrence_modification_type (GtkWidget                 *parent,
+                                             GcalEvent                 *event,
+                                             GcalAskRecurrenceCallback  callback,
+                                             gpointer                   user_data)
+{
+  AskRecurrenceData *data;
+  GtkDialogFlags flags;
+  ECalClient *client;
+  GtkWidget *dialog;
+
+  data = g_new0 (AskRecurrenceData, 1);
+  data->event = g_object_ref (event);
+  data->callback = callback;
+  data->user_data = user_data;
+
+  flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
+
+  dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_native (parent)),
+                                   flags,
+                                   GTK_MESSAGE_QUESTION,
+                                   GTK_BUTTONS_NONE,
+                                   _("The event you are trying to modify is recurring. The changes you have 
selected should be applied to:"));
+
+  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+                          _("_Cancel"),
+                          GTK_RESPONSE_CANCEL,
+                          _("_Only This Event"),
+                          GTK_RESPONSE_ACCEPT,
+                          NULL);
+
+  client = gcal_calendar_get_client (gcal_event_get_calendar (event));
+
+  if (!e_client_check_capability (E_CLIENT (client), E_CAL_STATIC_CAPABILITY_NO_THISANDFUTURE))
+    gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Subsequent events"), GTK_RESPONSE_OK);
+
+  gtk_dialog_add_button (GTK_DIALOG (dialog), _("_All events"), GTK_RESPONSE_YES);
+  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (gtk_widget_get_native (parent)));
+  g_signal_connect (dialog, "response", G_CALLBACK (on_message_dialog_response_cb), data);
+
+  gtk_window_present (GTK_WINDOW (dialog));
+}
diff --git a/src/utils/gcal-utils.h b/src/utils/gcal-utils.h
index 46f50ab7..0985b8c2 100644
--- a/src/utils/gcal-utils.h
+++ b/src/utils/gcal-utils.h
@@ -43,6 +43,10 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (ECalComponent, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (GWeatherLocation, gweather_location_unref)
 #endif
 
+typedef void (*GcalAskRecurrenceCallback) (GcalEvent             *event,
+                                           GcalRecurrenceModType  modtype,
+                                           gpointer               user_data);
+
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (ICalTime, g_object_unref)
 
 gchar*               gcal_get_weekday                            (gint                i);
@@ -130,4 +134,9 @@ void                 gcal_utils_extract_google_section           (const gchar
                                                                   gchar             **out_description,
                                                                   gchar             **out_meeting_url);
 
+void                 gcal_utils_ask_recurrence_modification_type (GtkWidget                 *parent,
+                                                                  GcalEvent                 *event,
+                                                                  GcalAskRecurrenceCallback  callback,
+                                                                  gpointer                   user_data);
+
 #endif /* __GCAL_UTILS_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]