[evolution] Calendar: Implement event drag&drop for the Year view
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution] Calendar: Implement event drag&drop for the Year view
- Date: Thu, 9 Jun 2022 12:04:19 +0000 (UTC)
commit 3e9d38d1faf6c324e6637af3b42fcbcfb485ef36
Author: Milan Crha <mcrha redhat com>
Date: Thu Jun 9 14:01:53 2022 +0200
Calendar: Implement event drag&drop for the Year view
To be able to copy/move an event to a different day.
src/calendar/calendar.error.xml | 7 ++
src/calendar/gui/comp-util.c | 112 +++++++++++++++++++++
src/calendar/gui/comp-util.h | 9 +-
src/calendar/gui/e-cal-dialogs.c | 32 ++++++
src/calendar/gui/e-cal-dialogs.h | 2 +
src/calendar/gui/e-year-view.c | 210 +++++++++++++++++++++++++++++++++++++++
6 files changed, 371 insertions(+), 1 deletion(-)
---
diff --git a/src/calendar/calendar.error.xml b/src/calendar/calendar.error.xml
index 03f71fa705..9a1b2cf6ef 100644
--- a/src/calendar/calendar.error.xml
+++ b/src/calendar/calendar.error.xml
@@ -582,4 +582,11 @@
<_secondary>A WebKitWebProcess crashed when displaying the task. You can try again by moving to another
task and back. If the issue persists, please file a bug report in GNOME Gitlab.</_secondary>
</error>
+ <error id="prompt-detach-copy-event" type="question" default="GTK_RESPONSE_NO">
+ <_primary>Do you want to detach event from the series?</_primary>
+ <_secondary>The event “{0}” will be detached from its series and saved as a new standalone
event.</_secondary>
+ <button _label="_Cancel" response="GTK_RESPONSE_NO"/>
+ <button _label="_Detach and Create Copy" response="GTK_RESPONSE_YES"/>
+ </error>
+
</error-list>
diff --git a/src/calendar/gui/comp-util.c b/src/calendar/gui/comp-util.c
index d0630d744e..0708e96dbd 100644
--- a/src/calendar/gui/comp-util.c
+++ b/src/calendar/gui/comp-util.c
@@ -31,6 +31,8 @@
#include "calendar-config.h"
#include "comp-util.h"
#include "e-calendar-view.h"
+#include "e-cal-dialogs.h"
+#include "e-cal-ops.h"
#include "itip-utils.h"
#include "shell/e-shell-window.h"
@@ -2532,3 +2534,113 @@ cal_comp_util_get_attendee_email (const ECalComponentAttendee *attendee)
e_cal_component_attendee_get_value (attendee),
e_cal_component_attendee_get_parameter_bag (attendee));
}
+
+/* moves the @comp by @days days, preserving its time and duration */
+gboolean
+cal_comp_util_move_component_by_days (GtkWindow *parent,
+ ECalModel *model,
+ ECalClient *client,
+ ECalComponent *in_comp,
+ gint days,
+ gboolean is_move)
+{
+ ECalComponentDateTime *datetime;
+ ECalComponent *comp_copy;
+ ESourceRegistry *registry;
+ ICalTime *itt;
+ GtkResponseType send = GTK_RESPONSE_NO;
+ gboolean only_new_attendees = FALSE;
+ gboolean strip_alarms = TRUE;
+
+ g_return_val_if_fail (E_IS_CAL_MODEL (model), FALSE);
+ g_return_val_if_fail (E_IS_CAL_CLIENT (client), FALSE);
+ g_return_val_if_fail (E_IS_CAL_COMPONENT (in_comp), FALSE);
+ g_return_val_if_fail (days != 0, FALSE);
+
+ registry = e_cal_model_get_registry (model);
+
+ if (e_cal_component_has_attendees (in_comp) &&
+ !itip_organizer_is_user (registry, in_comp, client)) {
+ /* Can continue with the next component */
+ return TRUE;
+ }
+
+ if (itip_has_any_attendees (in_comp) &&
+ (itip_organizer_is_user (registry, in_comp, client) ||
+ itip_sentby_is_user (registry, in_comp, client)))
+ send = e_cal_dialogs_send_dragged_or_resized_component (parent, client, in_comp,
&strip_alarms, &only_new_attendees);
+
+ if (send == GTK_RESPONSE_CANCEL)
+ return FALSE;
+
+ comp_copy = e_cal_component_clone (in_comp);
+
+ datetime = e_cal_component_get_dtstart (comp_copy);
+ itt = e_cal_component_datetime_get_value (datetime);
+ i_cal_time_adjust (itt, days, 0, 0, 0);
+ cal_comp_set_dtstart_with_oldzone (client, comp_copy, datetime);
+ e_cal_component_datetime_free (datetime);
+
+ datetime = e_cal_component_get_dtend (comp_copy);
+ itt = e_cal_component_datetime_get_value (datetime);
+ i_cal_time_adjust (itt, days, 0, 0, 0);
+ cal_comp_set_dtend_with_oldzone (client, comp_copy, datetime);
+ e_cal_component_datetime_free (datetime);
+
+ e_cal_component_commit_sequence (comp_copy);
+
+ if (is_move) {
+ ECalObjModType mod = E_CAL_OBJ_MOD_ALL;
+
+ if (e_cal_component_has_recurrences (comp_copy)) {
+ if (!e_cal_dialogs_recur_component (client, comp_copy, &mod, NULL, FALSE)) {
+ g_clear_object (&comp_copy);
+ return FALSE;
+ }
+
+ if (mod == E_CAL_OBJ_MOD_THIS) {
+ e_cal_component_set_rdates (comp_copy, NULL);
+ e_cal_component_set_rrules (comp_copy, NULL);
+ e_cal_component_set_exdates (comp_copy, NULL);
+ e_cal_component_set_exrules (comp_copy, NULL);
+ }
+ } else if (e_cal_component_is_instance (comp_copy)) {
+ mod = E_CAL_OBJ_MOD_THIS;
+ }
+
+ e_cal_component_commit_sequence (comp_copy);
+
+ e_cal_ops_modify_component (model, client, e_cal_component_get_icalcomponent (comp_copy), mod,
+ (send == GTK_RESPONSE_YES ? E_CAL_OPS_SEND_FLAG_SEND : E_CAL_OPS_SEND_FLAG_DONT_SEND)
|
+ (strip_alarms ? E_CAL_OPS_SEND_FLAG_STRIP_ALARMS : 0) |
+ (only_new_attendees ? E_CAL_OPS_SEND_FLAG_ONLY_NEW_ATTENDEES : 0));
+ } else {
+ gchar *new_uid;
+
+ if ((e_cal_component_has_recurrences (comp_copy) ||
+ e_cal_component_is_instance (comp_copy)) &&
+ !e_cal_dialogs_detach_and_copy (parent, e_cal_component_get_icalcomponent (comp_copy))) {
+ g_clear_object (&comp_copy);
+ return FALSE;
+ }
+
+ new_uid = e_util_generate_uid ();
+ e_cal_component_set_uid (comp_copy, new_uid);
+ g_free (new_uid);
+
+ /* Detach the instance from the series and make a new independent component for it */
+ e_cal_component_set_recurid (comp_copy, NULL);
+ e_cal_component_set_rdates (comp_copy, NULL);
+ e_cal_component_set_rrules (comp_copy, NULL);
+ e_cal_component_set_exdates (comp_copy, NULL);
+ e_cal_component_set_exrules (comp_copy, NULL);
+ e_cal_component_commit_sequence (comp_copy);
+
+ e_cal_ops_create_component (model, client, e_cal_component_get_icalcomponent (comp_copy),
+ NULL, NULL, NULL);
+ }
+
+ g_clear_object (&comp_copy);
+
+ return TRUE;
+}
diff --git a/src/calendar/gui/comp-util.h b/src/calendar/gui/comp-util.h
index 322f2da6cd..3ec120a0a9 100644
--- a/src/calendar/gui/comp-util.h
+++ b/src/calendar/gui/comp-util.h
@@ -29,6 +29,7 @@
#include <e-util/e-util.h>
#include <calendar/gui/e-cal-data-model.h>
+#include <calendar/gui/e-cal-model.h>
struct _EShell;
@@ -218,5 +219,11 @@ const gchar * cal_comp_util_get_organizer_email
(const ECalComponentOrganizer *organizer);
const gchar * cal_comp_util_get_attendee_email
(const ECalComponentAttendee *attendee);
-
+gboolean cal_comp_util_move_component_by_days
+ (GtkWindow *parent,
+ ECalModel *model,
+ ECalClient *client,
+ ECalComponent *in_comp,
+ gint days,
+ gboolean is_move);
#endif
diff --git a/src/calendar/gui/e-cal-dialogs.c b/src/calendar/gui/e-cal-dialogs.c
index 08a5f11f40..b176be537e 100644
--- a/src/calendar/gui/e-cal-dialogs.c
+++ b/src/calendar/gui/e-cal-dialogs.c
@@ -1304,3 +1304,35 @@ e_cal_dialogs_send_component_prompt_subject (GtkWindow *parent,
else
return FALSE;
}
+
+gboolean
+e_cal_dialogs_detach_and_copy (GtkWindow *parent,
+ ICalComponent *component)
+{
+ ICalComponentKind kind;
+ gchar *summary;
+ const gchar *id;
+ gboolean res;
+
+ kind = i_cal_component_isa (component);
+
+ switch (kind) {
+ case I_CAL_VEVENT_COMPONENT:
+ id = "calendar:prompt-detach-copy-event";
+ break;
+
+ case I_CAL_VTODO_COMPONENT:
+ case I_CAL_VJOURNAL_COMPONENT:
+ return TRUE;
+
+ default:
+ g_message ("%s: Cannot handle object of type %d", G_STRFUNC, kind);
+ return FALSE;
+ }
+
+ summary = e_calendar_view_dup_component_summary (component);
+ res = e_alert_run_dialog_for_args (parent, id, summary, NULL) == GTK_RESPONSE_YES;
+ g_free (summary);
+
+ return res;
+}
diff --git a/src/calendar/gui/e-cal-dialogs.h b/src/calendar/gui/e-cal-dialogs.h
index 348d1e6a45..2575d6545e 100644
--- a/src/calendar/gui/e-cal-dialogs.h
+++ b/src/calendar/gui/e-cal-dialogs.h
@@ -72,5 +72,7 @@ GtkResponseType e_cal_dialogs_send_dragged_or_resized_component
gboolean e_cal_dialogs_send_component_prompt_subject
(GtkWindow *parent,
ICalComponent *icomp);
+gboolean e_cal_dialogs_detach_and_copy (GtkWindow *parent,
+ ICalComponent *icomp);
#endif /* E_CAL_DIALOGS_H */
diff --git a/src/calendar/gui/e-year-view.c b/src/calendar/gui/e-year-view.c
index 38b3faa43d..27b099f01e 100644
--- a/src/calendar/gui/e-year-view.c
+++ b/src/calendar/gui/e-year-view.c
@@ -18,6 +18,10 @@
/* #define WITH_PREV_NEXT_BUTTONS 1 */
+static GtkTargetEntry target_table[] = {
+ { (gchar *) "application/x-e-calendar-event", 0, 0 }
+};
+
typedef struct _ComponentData {
ECalClient *client;
ECalComponent *comp;
@@ -37,6 +41,11 @@ typedef struct _DayData {
GSList *comps_data; /* ComponentData * */
} DayData;
+typedef struct _DragData {
+ ECalClient *client;
+ ECalComponent *comp;
+} DragData;
+
struct _EYearViewPrivate {
ESourceRegistry *registry;
GHashTable *client_colors; /* ESource * ~> GdkRGBA * */
@@ -62,6 +71,11 @@ struct _EYearViewPrivate {
guint current_month;
guint current_year;
+ GSList *drag_data; /* DragData * */
+ guint drag_day;
+ guint drag_month;
+ guint drag_year;
+
/* Track today */
gboolean highlight_today;
gboolean today_fix_timeout;
@@ -159,6 +173,31 @@ component_data_equal (gconstpointer ptr1,
g_strcmp0 (cd1->rid, cd2->rid) == 0;
}
+static DragData *
+drag_data_new (ECalClient *client,
+ ECalComponent *comp)
+{
+ DragData *dd;
+
+ dd = g_slice_new (DragData);
+ dd->client = g_object_ref (client);
+ dd->comp = g_object_ref (comp);
+
+ return dd;
+}
+
+static void
+drag_data_free (gpointer ptr)
+{
+ DragData *dd = ptr;
+
+ if (dd) {
+ g_clear_object (&dd->client);
+ g_clear_object (&dd->comp);
+ g_slice_free (DragData, dd);
+ }
+}
+
static void
year_view_calc_component_data (EYearView *self,
ComponentData *cd,
@@ -1317,6 +1356,155 @@ year_view_tree_view_row_activated_cb (GtkTreeView *tree_view,
}
}
+static void
+year_view_tree_view_drag_begin_cb (GtkWidget *tree_view,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ EYearView *self = user_data;
+ GtkTreeSelection *selection;
+ cairo_surface_t *surface = NULL;
+ GList *selected, *link;
+ GtkTreeModel *model = NULL;
+ GtkTreeIter iter;
+
+ g_slist_free_full (self->priv->drag_data, drag_data_free);
+ self->priv->drag_data = NULL;
+
+ selection = gtk_tree_view_get_selection (self->priv->tree_view);
+ selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (link = selected; link; link = g_list_next (link)) {
+ if (gtk_tree_model_get_iter (model, &iter, link->data)) {
+ ComponentData *cd = NULL;
+
+ gtk_tree_model_get (model, &iter,
+ COLUMN_COMPONENT_DATA, &cd,
+ -1);
+
+ self->priv->drag_data = g_slist_prepend (self->priv->drag_data,
+ drag_data_new (cd->client, cd->comp));
+
+ if (!surface)
+ surface = gtk_tree_view_create_row_drag_icon (self->priv->tree_view,
link->data);
+ }
+ }
+
+ g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
+
+ self->priv->drag_data = g_slist_reverse (self->priv->drag_data);
+ self->priv->drag_day = self->priv->current_day;
+ self->priv->drag_month = self->priv->current_month;
+ self->priv->drag_year = self->priv->current_year;
+
+ if (surface) {
+ gtk_drag_set_icon_surface (context, surface);
+ cairo_surface_destroy (surface);
+ }
+}
+
+static void
+year_view_tree_view_drag_end_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ EYearView *self = user_data;
+
+ g_slist_free_full (self->priv->drag_data, drag_data_free);
+ self->priv->drag_data = NULL;
+ self->priv->drag_day = 0;
+ self->priv->drag_month = 0;
+ self->priv->drag_year = 0;
+}
+
+static gboolean
+year_view_month_drag_motion_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer user_data)
+{
+ EYearView *self = user_data;
+ guint day, year = 0;
+ GDateMonth month = 0;
+ gboolean can_drop;
+
+ day = e_month_widget_get_day_at_position (E_MONTH_WIDGET (widget), x, y);
+ e_month_widget_get_month (E_MONTH_WIDGET (widget), &month, &year);
+
+ can_drop = day != 0 && self->priv->drag_data && (
+ day != self->priv->drag_day ||
+ month != self->priv->drag_month ||
+ year != self->priv->drag_year);
+
+ gdk_drag_status (context,
+ can_drop ? gdk_drag_context_get_selected_action (context) : 0, time);
+
+ return TRUE;
+}
+
+static gboolean
+year_view_month_drag_drop_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer user_data)
+{
+ EYearView *self = user_data;
+ guint day, year = 0;
+ GDateMonth month = 0;
+ gboolean can_drop;
+
+ day = e_month_widget_get_day_at_position (E_MONTH_WIDGET (widget), x, y);
+ e_month_widget_get_month (E_MONTH_WIDGET (widget), &month, &year);
+
+ can_drop = day != 0 && self->priv->drag_data && (
+ day != self->priv->drag_day ||
+ month != self->priv->drag_month ||
+ year != self->priv->drag_year);
+
+ if (can_drop) {
+ GDate *from, *to;
+ gint diff_days;
+
+ from = g_date_new_dmy (self->priv->drag_day, self->priv->drag_month, self->priv->drag_year);
+ to = g_date_new_dmy (day, month, year);
+
+ diff_days = g_date_days_between (from, to);
+
+ if (diff_days != 0) {
+ ECalModel *model = e_calendar_view_get_model (E_CALENDAR_VIEW (self));
+ GtkWidget *toplevel;
+ GtkWindow *parent;
+ GSList *drag_data, *link;
+ gboolean is_move;
+
+ drag_data = g_steal_pointer (&self->priv->drag_data);
+ toplevel = gtk_widget_get_toplevel (widget);
+ parent = GTK_IS_WINDOW (toplevel) ? GTK_WINDOW (toplevel) : NULL;
+ is_move = gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE;
+
+ for (link = drag_data; link; link = g_slist_next (link)) {
+ DragData *dd = link->data;
+ if (!cal_comp_util_move_component_by_days (parent, model,
+ dd->client, dd->comp, diff_days, is_move))
+ break;
+ }
+
+ g_slist_free_full (drag_data, drag_data_free);
+ }
+
+ g_date_free (from);
+ g_date_free (to);
+ }
+
+ gdk_drag_status (context, 0, time);
+
+ return FALSE;
+}
+
static void
year_view_timezone_changed_cb (GObject *object,
GParamSpec *param,
@@ -1569,6 +1757,17 @@ year_view_construct_year_widget (EYearView *self)
e_month_widget_set_month (E_MONTH_WIDGET (widget), ii + 1, self->priv->current_year);
+ gtk_drag_dest_set (
+ widget, GTK_DEST_DEFAULT_ALL,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
+ g_signal_connect_object (widget, "drag-motion",
+ G_CALLBACK (year_view_month_drag_motion_cb), self, 0);
+
+ g_signal_connect_object (widget, "drag-drop",
+ G_CALLBACK (year_view_month_drag_drop_cb), self, 0);
+
gtk_container_add (GTK_CONTAINER (container), vbox);
child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (container), ii);
@@ -1819,6 +2018,10 @@ year_view_constructed (GObject *object)
gtk_tree_view_append_column (self->priv->tree_view, column);
+ gtk_drag_source_set (GTK_WIDGET (self->priv->tree_view), GDK_BUTTON1_MASK,
+ target_table, G_N_ELEMENTS (target_table),
+ GDK_ACTION_COPY | GDK_ACTION_MOVE);
+
selection = gtk_tree_view_get_selection (self->priv->tree_view);
g_signal_connect_object (selection, "changed",
@@ -1833,6 +2036,12 @@ year_view_constructed (GObject *object)
g_signal_connect_object (self->priv->tree_view, "row-activated",
G_CALLBACK (year_view_tree_view_row_activated_cb), self, 0);
+ g_signal_connect_object (self->priv->tree_view, "drag-begin",
+ G_CALLBACK (year_view_tree_view_drag_begin_cb), self, 0);
+
+ g_signal_connect_object (self->priv->tree_view, "drag-end",
+ G_CALLBACK (year_view_tree_view_drag_end_cb), self, 0);
+
g_signal_connect_object (self->priv->data_model, "notify::timezone",
G_CALLBACK (year_view_timezone_changed_cb), self, 0);
@@ -1902,6 +2111,7 @@ year_view_finalize (GObject *object)
year_view_clear_comps (self);
+ g_slist_free_full (self->priv->drag_data, drag_data_free);
g_hash_table_destroy (self->priv->client_colors);
g_hash_table_destroy (self->priv->comps);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]