From 3b85eeec8db436bb492b3a00476e9fbea0175bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sun, 7 Oct 2012 08:34:32 +0200 Subject: [PATCH] Add drag and drop support for symbols. --- liblogdiag/ld-category-symbol-view.c | 159 ++++++++++++++++++++++++--- liblogdiag/ld-diagram-view.c | 138 +++++++++++++++++++++++ 2 files changed, 280 insertions(+), 17 deletions(-) diff --git a/liblogdiag/ld-category-symbol-view.c b/liblogdiag/ld-category-symbol-view.c index e9e9e6c..365be62 100644 --- a/liblogdiag/ld-category-symbol-view.c +++ b/liblogdiag/ld-category-symbol-view.c @@ -30,21 +30,6 @@ #define SYMBOL_HEIGHT 40 /* Height of a symbol. */ #define SYMBOL_SPACING 10 /* Spacing between symbols, and also borders. */ -/* - * LdCategorySymbolViewPrivate: - * @category: a category object assigned as a model. - * @path: path to the category within the library. - * @layout: (element-type SymbolData *): current layout of symbols. - * @height_negotiation: whether we are negotiating height right now. - */ -struct _LdCategorySymbolViewPrivate -{ - LdCategory *category; - gchar *path; - GSList *layout; - guint height_negotiation : 1; -}; - typedef struct { LdSymbol *symbol; /* The associated symbol, ref'ed. */ @@ -56,6 +41,23 @@ typedef struct } SymbolData; +/* + * LdCategorySymbolViewPrivate: + * @category: a category object assigned as a model. + * @path: path to the category within the library. + * @layout: (element-type SymbolData *): current layout of symbols. + * @preselected: currently preselected symbol. + * @height_negotiation: whether we are negotiating height right now. + */ +struct _LdCategorySymbolViewPrivate +{ + LdCategory *category; + gchar *path; + GSList *layout; + SymbolData *preselected; + guint height_negotiation : 1; +}; + enum { PROP_0, @@ -103,6 +105,112 @@ ld_category_symbol_view_class_init (LdCategorySymbolViewClass *klass) g_type_class_add_private (klass, sizeof (LdCategorySymbolViewPrivate)); } +static void +symbol_redraw (LdCategorySymbolView *self, SymbolData *symbol) +{ + gtk_widget_queue_draw_area (GTK_WIDGET (self), + symbol->rect.x, + symbol->rect.y, + symbol->rect.width, + symbol->rect.height); +} + +static void +symbol_deselect (LdCategorySymbolView *self) +{ + if (!self->priv->preselected) + return; + + symbol_redraw (self, self->priv->preselected); + self->priv->preselected = NULL; + gtk_drag_source_unset (GTK_WIDGET (self)); +} + +static gboolean +on_leave_notify (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + symbol_deselect (LD_CATEGORY_SYMBOL_VIEW (widget)); + return FALSE; +} + +static gboolean +on_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer user_data) +{ + LdCategorySymbolView *self; + GSList *iter; + + if (event->state & GDK_BUTTON1_MASK) + return FALSE; + + self = LD_CATEGORY_SYMBOL_VIEW (widget); + for (iter = self->priv->layout; iter; iter = iter->next) + { + SymbolData *data; + + data = iter->data; + if (event->x < data->rect.x + || event->y < data->rect.y + || event->x >= data->rect.x + data->rect.width + || event->y >= data->rect.y + data->rect.height) + continue; + + if (data != self->priv->preselected) + { + GtkTargetEntry target = {"ld-symbol", GTK_TARGET_SAME_APP, 0}; + + symbol_deselect (self); + self->priv->preselected = data; + symbol_redraw (self, data); + + gtk_drag_source_set (widget, + GDK_BUTTON1_MASK, &target, 1, GDK_ACTION_COPY); + } + return FALSE; + } + + symbol_deselect (self); + return FALSE; +} + +static void +on_drag_data_get +(GtkWidget *widget, GdkDragContext *ctx, GtkSelectionData *selection_data, + guint target_type, guint time, gpointer user_data) +{ + LdCategorySymbolView *self; + + self = LD_CATEGORY_SYMBOL_VIEW (widget); + g_return_if_fail (self->priv->preselected != NULL); + + gtk_selection_data_set (selection_data, + gtk_selection_data_get_target (selection_data), + 8, (guchar *) self->priv->preselected->path, + strlen (self->priv->preselected->path)); +} + +static void +on_drag_begin (GtkWidget *widget, GdkDragContext *ctx, gpointer user_data) +{ + LdCategorySymbolView *self; + GdkPixbuf *pbuf; + + self = LD_CATEGORY_SYMBOL_VIEW (widget); + g_return_if_fail (self->priv->preselected != NULL); + + /* Some of the larger previews didn't work, and we have to get rid of + * the icon later when we're hovering above LdDiagramView anyway. */ + pbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1); + gdk_pixbuf_fill (pbuf, 0x00000000); + gtk_drag_set_icon_pixbuf (ctx, pbuf, 0, 0); + g_object_unref (pbuf); +} + +static void +on_drag_end (GtkWidget *widget, GdkDragContext *ctx, gpointer user_data) +{ + symbol_deselect (LD_CATEGORY_SYMBOL_VIEW (widget)); +} + static void ld_category_symbol_view_init (LdCategorySymbolView *self) { @@ -116,6 +224,18 @@ ld_category_symbol_view_init (LdCategorySymbolView *self) g_signal_connect (self, "expose-event", G_CALLBACK (on_expose_event), NULL); + g_signal_connect (self, "motion-notify-event", + G_CALLBACK (on_motion_notify), NULL); + g_signal_connect (self, "leave-notify-event", + G_CALLBACK (on_leave_notify), NULL); + + g_signal_connect (self, "drag-begin", + G_CALLBACK (on_drag_begin), NULL); + g_signal_connect (self, "drag-data-get", + G_CALLBACK (on_drag_data_get), NULL); + g_signal_connect (self, "drag-end", + G_CALLBACK (on_drag_end), NULL); + gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK @@ -136,6 +256,7 @@ layout_destroy (LdCategorySymbolView *self) g_slist_foreach (self->priv->layout, (GFunc) symbol_data_free, NULL); g_slist_free (self->priv->layout); self->priv->layout = NULL; + self->priv->preselected = NULL; } static void @@ -331,11 +452,15 @@ on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) gdk_cairo_rectangle (cr, &data->rect); cairo_clip (cr); + gdk_cairo_set_source_color (cr, + >k_widget_get_style (widget)->text[GTK_STATE_NORMAL]); + + if (data == self->priv->preselected) + cairo_paint_with_alpha (cr, 0.1); + cairo_translate (cr, data->rect.x + data->dx, data->rect.y + data->dy); cairo_scale (cr, data->scale, data->scale); - gdk_cairo_set_source_color (cr, - >k_widget_get_style (widget)->text[GTK_STATE_NORMAL]); cairo_set_line_width (cr, 1 / data->scale); ld_symbol_draw (data->symbol, cr); diff --git a/liblogdiag/ld-diagram-view.c b/liblogdiag/ld-diagram-view.c index 1d2764c..55a60c0 100644 --- a/liblogdiag/ld-diagram-view.c +++ b/liblogdiag/ld-diagram-view.c @@ -129,6 +129,9 @@ Color; * @y: the Y coordinate of the center of view. * @zoom: the current zoom. * @show_grid: whether to show the grid. + * @dnd_symbol: currently dragged symbol. + * @dnd_last_position: last cursor movement position. + * @dnd_left: whether the user has stopped dragging. * @terminal: position of the highlighted terminal. * @terminal_hovered: whether a terminal is hovered. * @drag_start_pos: position of the mouse pointer when dragging started. @@ -152,6 +155,10 @@ struct _LdDiagramViewPrivate gboolean show_grid; + LdDiagramObject *dnd_symbol; + LdPoint dnd_last_position; + guint dnd_left : 1; + LdPoint terminal; gboolean terminal_hovered; @@ -343,6 +350,16 @@ static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, static gboolean on_scroll (GtkWidget *widget, GdkEventScroll *event, gpointer user_data); +static gboolean on_drag_motion (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, guint time, gpointer user_data); +static gboolean on_drag_drop (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, guint time, gpointer user_data); +static void on_drag_data_received (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, GtkSelectionData *data, guint info, + guint time, gpointer user_data); +static void on_drag_leave (GtkWidget *widget, GdkDragContext *drag_ctx, + guint time, gpointer user_data); + static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data); static void draw_grid (GtkWidget *widget, DrawData *data); @@ -486,6 +503,8 @@ ld_diagram_view_class_init (LdDiagramViewClass *klass) static void ld_diagram_view_init (LdDiagramView *self) { + GtkTargetEntry target = {"ld-symbol", GTK_TARGET_SAME_APP, 0}; + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, LD_TYPE_DIAGRAM_VIEW, LdDiagramViewPrivate); @@ -517,12 +536,23 @@ ld_diagram_view_init (LdDiagramView *self) g_signal_connect (self, "scroll-event", G_CALLBACK (on_scroll), NULL); + g_signal_connect (self, "drag-motion", + G_CALLBACK (on_drag_motion), NULL); + g_signal_connect (self, "drag-leave", + G_CALLBACK (on_drag_leave), NULL); + g_signal_connect (self, "drag-drop", + G_CALLBACK (on_drag_drop), NULL); + g_signal_connect (self, "drag-data-received", + G_CALLBACK (on_drag_data_received), NULL); + g_object_set (self, "can-focus", TRUE, NULL); gtk_widget_add_events (GTK_WIDGET (self), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK); + + gtk_drag_dest_set (GTK_WIDGET (self), 0, &target, 1, GDK_ACTION_COPY); } static void @@ -541,6 +571,8 @@ ld_diagram_view_finalize (GObject *gobject) } if (self->priv->library) g_object_unref (self->priv->library); + if (self->priv->dnd_symbol) + g_object_unref (self->priv->dnd_symbol); /* Chain up to the parent class. */ G_OBJECT_CLASS (ld_diagram_view_parent_class)->finalize (gobject); @@ -2424,6 +2456,109 @@ on_scroll (GtkWidget *widget, GdkEventScroll *event, gpointer user_data) return TRUE; } +static gboolean +on_drag_motion (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, guint time, gpointer user_data) +{ + LdDiagramView *self; + GdkAtom target; + + self = LD_DIAGRAM_VIEW (widget); + target = gtk_drag_dest_find_target (widget, drag_ctx, NULL); + if (target == GDK_NONE) + { + gdk_drag_status (drag_ctx, 0, time); + return TRUE; + } + + gdk_drag_status (drag_ctx, + gdk_drag_context_get_suggested_action (drag_ctx), time); + + /* Discard leftovers from any previous unsuccessful drag. */ + if (self->priv->dnd_left) + { + g_object_unref (self->priv->dnd_symbol); + self->priv->dnd_symbol = NULL; + self->priv->dnd_left = FALSE; + } + + self->priv->dnd_last_position.x = x; + self->priv->dnd_last_position.y = y; + + if (!self->priv->dnd_symbol) + gtk_drag_get_data (widget, drag_ctx, target, time); + else + { + queue_object_draw (self, self->priv->dnd_symbol); + move_object_to_point (self, self->priv->dnd_symbol, + &self->priv->dnd_last_position); + queue_object_draw (self, self->priv->dnd_symbol); + } + + return TRUE; +} + +static void +on_drag_data_received (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, GtkSelectionData *data, guint info, guint time, + gpointer user_data) +{ + LdDiagramView *self; + LdDiagramSymbol *symbol; + const gchar *klass; + + self = LD_DIAGRAM_VIEW (widget); + + g_return_if_fail (gtk_selection_data_get_length (data) >= 0); + g_return_if_fail (self->priv->dnd_symbol == NULL); + + klass = (const gchar *) gtk_selection_data_get_data (data); + symbol = ld_diagram_symbol_new (NULL); + ld_diagram_symbol_set_class (symbol, klass); + self->priv->dnd_symbol = LD_DIAGRAM_OBJECT (symbol); + + move_object_to_point (self, self->priv->dnd_symbol, + &self->priv->dnd_last_position); + queue_object_draw (self, self->priv->dnd_symbol); +} + +static void +on_drag_leave (GtkWidget *widget, GdkDragContext *drag_ctx, + guint time, gpointer user_data) +{ + LdDiagramView *self; + + self = LD_DIAGRAM_VIEW (widget); + self->priv->dnd_left = TRUE; + queue_object_draw (self, self->priv->dnd_symbol); +} + +static gboolean +on_drag_drop (GtkWidget *widget, GdkDragContext *drag_ctx, + gint x, gint y, guint time, gpointer user_data) +{ + LdDiagramView *self; + gboolean del; + + del = gdk_drag_context_get_suggested_action (drag_ctx) + == GDK_ACTION_MOVE; + + self = LD_DIAGRAM_VIEW (widget); + if (!self->priv->dnd_symbol) + gtk_drag_finish (drag_ctx, FALSE, del, time); + else + { + gtk_drag_finish (drag_ctx, TRUE, del, time); + + ld_diagram_insert_object (self->priv->diagram, + self->priv->dnd_symbol, -1); + g_object_unref (self->priv->dnd_symbol); + self->priv->dnd_symbol = NULL; + self->priv->dnd_left = FALSE; + } + return TRUE; +} + static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { @@ -2572,6 +2707,9 @@ draw_diagram (GtkWidget *widget, DrawData *data) break; } + if (data->self->priv->dnd_symbol && !data->self->priv->dnd_left) + draw_object (data->self->priv->dnd_symbol, data); + cairo_restore (data->cr); }