/* * Engrampa * * Copyright (C) 2005 Free Software Foundation, Inc. * Author: Paolo Bacchilega */ /* eggtreemultidnd.c * Copyright (C) 2001 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include "eggtreemultidnd.h" #define EGG_TREE_MULTI_DND_STRING "EggTreeMultiDndString" static GtkTargetEntry target_table[] = { { "XdndDirectSave0", 0, 0 }, { "XdndEngrampa0", 0, 1 } }; typedef struct { guint pressed_button; gint x; gint y; guint motion_notify_handler; guint button_release_handler; guint drag_data_get_handler; GSList *event_list; gboolean pending_event; } EggTreeMultiDndData; GType egg_tree_multi_drag_source_get_type (void) { static GType our_type = 0; if (!our_type) { static const GTypeInfo our_info = { sizeof (EggTreeMultiDragSourceIface), /* class_size */ NULL, /* base_init */ NULL, /* base_finalize */ NULL, NULL, /* class_finalize */ NULL, /* class_data */ 0, 0, /* n_preallocs */ NULL }; our_type = g_type_register_static (G_TYPE_INTERFACE, "EggTreeMultiDragSource", &our_info, 0); } return our_type; } /** * egg_tree_multi_drag_source_row_draggable: * @drag_source: a #EggTreeMultiDragSource * @path: row on which user is initiating a drag * * Asks the #EggTreeMultiDragSource whether a particular row can be used as * the source of a DND operation. If the source doesn't implement * this interface, the row is assumed draggable. * * Return value: %TRUE if the row can be dragged **/ gboolean egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source, GList *path_list) { EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); g_return_val_if_fail (iface->row_draggable != NULL, FALSE); g_return_val_if_fail (path_list != NULL, FALSE); if (iface->row_draggable) return (* iface->row_draggable) (drag_source, path_list); else return TRUE; } /** * egg_tree_multi_drag_source_drag_data_delete: * @drag_source: a #EggTreeMultiDragSource * @path: row that was being dragged * * Asks the #EggTreeMultiDragSource to delete the row at @path, because * it was moved somewhere else via drag-and-drop. Returns %FALSE * if the deletion fails because @path no longer exists, or for * some model-specific reason. Should robustly handle a @path no * longer found in the model! * * Return value: %TRUE if the row was successfully deleted **/ gboolean egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source, GList *path_list) { EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); g_return_val_if_fail (iface->drag_data_delete != NULL, FALSE); g_return_val_if_fail (path_list != NULL, FALSE); return (* iface->drag_data_delete) (drag_source, path_list); } /** * egg_tree_multi_drag_source_drag_data_get: * @drag_source: a #EggTreeMultiDragSource * @path: row that was dragged * @selection_data: a #EggSelectionData to fill with data from the dragged row * * Asks the #EggTreeMultiDragSource to fill in @selection_data with a * representation of the row at @path. @selection_data->target gives * the required type of the data. Should robustly handle a @path no * longer found in the model! * * Return value: %TRUE if data of the required type was provided **/ gboolean egg_tree_multi_drag_source_drag_data_get (EggTreeMultiDragSource *drag_source, GdkDragContext *context, GtkSelectionData *selection_data, GList *path_list) { EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source); g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE); g_return_val_if_fail (iface->drag_data_get != NULL, FALSE); g_return_val_if_fail (path_list != NULL, FALSE); g_return_val_if_fail (selection_data != NULL, FALSE); return (* iface->drag_data_get) (drag_source, context, selection_data, path_list); } static void stop_drag_check (GtkWidget *widget) { EggTreeMultiDndData *priv_data; GSList *l; priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); for (l = priv_data->event_list; l != NULL; l = l->next) gdk_event_free (l->data); g_slist_free (priv_data->event_list); priv_data->event_list = NULL; priv_data->pending_event = FALSE; if (priv_data->motion_notify_handler) { g_signal_handler_disconnect (widget, priv_data->motion_notify_handler); priv_data->motion_notify_handler = 0; } if (priv_data->button_release_handler) { g_signal_handler_disconnect (widget, priv_data->button_release_handler); priv_data->button_release_handler = 0; } } static gboolean egg_tree_multi_drag_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data) { EggTreeMultiDndData *priv_data; GSList *l; priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); for (l = priv_data->event_list; l != NULL; l = l->next) gtk_propagate_event (widget, l->data); stop_drag_check (widget); return FALSE; } static void selection_foreach (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) { GList **list_ptr; list_ptr = (GList **) data; *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path)); } static void path_list_free (GList *path_list) { g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL); g_list_free (path_list); } static void set_context_data (GdkDragContext *context, GList *path_list) { g_object_set_data_full (G_OBJECT (context), "egg-tree-view-multi-source-row", path_list, (GDestroyNotify) path_list_free); } static GList * get_context_data (GdkDragContext *context) { return g_object_get_data (G_OBJECT (context), "egg-tree-view-multi-source-row"); } static gboolean egg_tree_multi_drag_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time) { GtkTreeView *tree_view; GtkTreeModel *model; GList *path_list; tree_view = GTK_TREE_VIEW (widget); model = gtk_tree_view_get_model (tree_view); if (model == NULL) return FALSE; path_list = get_context_data (context); if (path_list == NULL) return FALSE; /* We can implement the GTK_TREE_MODEL_ROW target generically for * any model; for DragSource models there are some other targets * we also support. */ if (! EGG_IS_TREE_MULTI_DRAG_SOURCE (model)) return FALSE; return egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model), context, selection_data, path_list); } static gboolean egg_tree_multi_drag_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data) { EggTreeMultiDndData *priv_data; priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING); if (gtk_drag_check_threshold (widget, priv_data->x, priv_data->y, event->x, event->y)) { GList *path_list = NULL; GtkTreeSelection *selection; GtkTreeModel *model; GdkDragContext *context; stop_drag_check (widget); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list); if (path_list == NULL) return FALSE; path_list = g_list_reverse (path_list); model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); if (egg_tree_multi_drag_source_row_draggable (EGG_TREE_MULTI_DRAG_SOURCE (model), path_list)) { GtkTargetList *target_list; GtkTreePath *tree_path; int cell_x; int cell_y; target_list = gtk_target_list_new (target_table, G_N_ELEMENTS (target_table)); context = gtk_drag_begin_with_coordinates (widget, target_list, GDK_ACTION_COPY, priv_data->pressed_button, (GdkEvent*) event, event->x, event->y); set_context_data (context, path_list); if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), priv_data->x, priv_data->y, &tree_path, NULL, &cell_x, &cell_y)) { cairo_surface_t *drag_icon; drag_icon = gtk_tree_view_create_row_drag_icon (GTK_TREE_VIEW (widget), tree_path); cairo_surface_set_device_offset (drag_icon, -cell_x, -cell_y); gtk_drag_set_icon_surface (context, drag_icon); cairo_surface_destroy (drag_icon); gtk_tree_path_free (tree_path); } else gtk_drag_set_icon_default (context); gtk_target_list_unref (target_list); } else { path_list_free (path_list); } } return TRUE; } static gboolean egg_tree_multi_drag_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data) { GtkTreeView *tree_view; GtkTreePath *path = NULL; GtkTreeViewColumn *column = NULL; gint cell_x, cell_y; GtkTreeSelection *selection; EggTreeMultiDndData *priv_data; if (event->window != gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget))) return FALSE; if (event->button == 3) return FALSE; tree_view = GTK_TREE_VIEW (widget); priv_data = g_object_get_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING); if (priv_data == NULL) { priv_data = g_new0 (EggTreeMultiDndData, 1); priv_data->pending_event = FALSE; g_object_set_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING, priv_data); } if (g_slist_find (priv_data->event_list, event)) return FALSE; if (priv_data->pending_event) { /* save the event to be propagated in order */ priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event)); return TRUE; } if (event->type == GDK_2BUTTON_PRESS) return FALSE; gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, &column, &cell_x, &cell_y); selection = gtk_tree_view_get_selection (tree_view); if (path) { gboolean call_parent = (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK) || !gtk_tree_selection_path_is_selected (selection, path) || event->button != 1); if (call_parent) (GTK_WIDGET_GET_CLASS (tree_view))->button_press_event (widget, event); if (gtk_tree_selection_path_is_selected (selection, path)) { priv_data->pressed_button = event->button; priv_data->x = event->x; priv_data->y = event->y; priv_data->pending_event = TRUE; if (!call_parent) priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event)); if (priv_data->motion_notify_handler == 0) { priv_data->motion_notify_handler = g_signal_connect (G_OBJECT (tree_view), "motion_notify_event", G_CALLBACK (egg_tree_multi_drag_motion_event), NULL); } if (priv_data->button_release_handler == 0) { priv_data->button_release_handler = g_signal_connect (G_OBJECT (tree_view), "button_release_event", G_CALLBACK (egg_tree_multi_drag_button_release_event), NULL); } if (priv_data->drag_data_get_handler == 0) { priv_data->drag_data_get_handler = g_signal_connect (G_OBJECT (tree_view), "drag_data_get", G_CALLBACK (egg_tree_multi_drag_drag_data_get), NULL); } } gtk_tree_path_free (path); /* We called the default handler so we don't let the default handler run */ return TRUE; } return FALSE; } void egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view) { g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); g_signal_connect (G_OBJECT (tree_view), "button_press_event", G_CALLBACK (egg_tree_multi_drag_button_press_event), NULL); }