X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/30b358a302ee2607752457c18a4b160d5e54e764..a59bf4da6cdf7b4c5f3ddf359cb69676554ee222:/disobedience/multidrag.c diff --git a/disobedience/multidrag.c b/disobedience/multidrag.c index 650d1c4..8b28c94 100644 --- a/disobedience/multidrag.c +++ b/disobedience/multidrag.c @@ -1,19 +1,27 @@ /* - * This file is part of DisOrder * Copyright (C) 2009 Richard Kettlewell * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * Note that this license ONLY applies to multidrag.c and multidrag.h, not to + * the rest of DisOrder. * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE + * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** @file disobedience/multidrag.c * @brief Drag multiple rows of a GtkTreeView @@ -22,20 +30,31 @@ * you dragged from (because it can't cope with dragging more than one row at a * time). * - * Disobedience needs more. To implement this it intercepts button-press-event - * and button-release event and for clicks that might be the start of drags, - * suppresses changes to the selection. A consequence of this is that it needs - * to intercept button-release-event too, to restore the effect of the click, - * if it turns out not to be drag after all. + * Disobedience needs more. + * + * Firstly it intercepts button-press-event and button-release event and for + * clicks that might be the start of drags, suppresses changes to the + * selection. A consequence of this is that it needs to intercept + * button-release-event too, to restore the effect of the click, if it turns + * out not to be drag after all. * * The location of the initial click is stored in object data called @c * multidrag-where. * + * Secondly it intercepts drag-begin and constructs an icon from the rows to be + * dragged. + * * Inspired by similar code in Quodlibet (another software * jukebox, albeit as far as I can see a single-user one). */ -#include "disobedience.h" +#include + +#include +#include +#include + +#include "multidrag.h" static gboolean multidrag_selection_block(GtkTreeSelection attribute((unused)) *selection, GtkTreeModel attribute((unused)) *model, @@ -57,11 +76,11 @@ static void block_selection(GtkWidget *w, gboolean block, int *where = g_object_get_data(G_OBJECT(w), "multidrag-where"); if(!where) { where = g_malloc(2 * sizeof (int)); - g_object_set_data(G_OBJECT(w), "multidrag-where", where); + g_object_set_data_full(G_OBJECT(w), "multidrag-where", where, + g_free); } where[0] = x; where[1] = y; - // TODO release 'where' when object is destroyed } static gboolean multidrag_button_press_event(GtkWidget *w, @@ -77,21 +96,22 @@ static gboolean multidrag_button_press_event(GtkWidget *w, if(event->state & GDK_MODIFIER_MASK) return FALSE; /* We are only interested if a well-defined path is clicked */ - GtkTreePath *path; + GtkTreePath *path = NULL; if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w), event->x, event->y, &path, NULL, NULL, NULL)) return FALSE; - //gtk_widget_grab_focus(w); // TODO why?? /* We are only interested if a selected row is clicked */ GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w)); - if(!gtk_tree_selection_path_is_selected(s, path)) - return FALSE; - /* We block subsequent selection changes and remember where the - * click was */ - block_selection(w, FALSE, event->x, event->y); + if(gtk_tree_selection_path_is_selected(s, path)) { + /* We block subsequent selection changes and remember where the + * click was */ + block_selection(w, FALSE, event->x, event->y); + } + if(path) + gtk_tree_path_free(path); return FALSE; /* propagate */ } @@ -110,7 +130,7 @@ static gboolean multidrag_button_release_event(GtkWidget *w, if(x == event->x && y == event->y) { // If the up-click is at the same location as the down-click, // it's not a drag. - GtkTreePath *path; + GtkTreePath *path = NULL; GtkTreeViewColumn *col; if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w), event->x, event->y, @@ -119,21 +139,127 @@ static gboolean multidrag_button_release_event(GtkWidget *w, NULL, NULL)) { gtk_tree_view_set_cursor(GTK_TREE_VIEW(w), path, col, FALSE); } + if(path) + gtk_tree_path_free(path); } } return FALSE; /* propagate */ } +/** @brief State for multidrag_begin() and its callbacks */ +struct multidrag_begin_state { + GtkTreeView *view; + multidrag_row_predicate *predicate; + int rows; + int index; + GdkPixmap **pixmaps; +}; + +/** @brief Callback to construct a row pixmap */ +static void multidrag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) { + struct multidrag_begin_state *qdbs = data; + + if(qdbs->predicate(path, iter)) { + qdbs->pixmaps[qdbs->index++] + = gtk_tree_view_create_row_drag_icon(qdbs->view, path); + } +} + +/** @brief Called when a drag operation starts + * @param w Source widget (the tree view) + * @param dc Drag context + * @param user_data Row predicate + */ +static void multidrag_drag_begin(GtkWidget *w, + GdkDragContext attribute((unused)) *dc, + gpointer user_data) { + struct multidrag_begin_state qdbs[1]; + GdkPixmap *icon; + GtkTreeSelection *sel; + + //fprintf(stderr, "drag-begin\n"); + memset(qdbs, 0, sizeof *qdbs); + qdbs->view = GTK_TREE_VIEW(w); + qdbs->predicate = (multidrag_row_predicate *)user_data; + sel = gtk_tree_view_get_selection(qdbs->view); + /* Find out how many rows there are */ + if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(sel))) + return; /* doesn't make sense */ + /* Generate a pixmap for each row */ + qdbs->pixmaps = g_new(GdkPixmap *, qdbs->rows); + gtk_tree_selection_selected_foreach(sel, + multidrag_make_row_pixmaps, + qdbs); + /* Might not have used all rows */ + qdbs->rows = qdbs->index; + /* Determine the size of the final icon */ + int height = 0, width = 0; + for(int n = 0; n < qdbs->rows; ++n) { + int pxw, pxh; + gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh); + if(pxw > width) + width = pxw; + height += pxh; + } + if(!width || !height) + return; /* doesn't make sense */ + /* Construct the icon */ + icon = gdk_pixmap_new(qdbs->pixmaps[0], width, height, -1); + GdkGC *gc = gdk_gc_new(icon); + gdk_gc_set_colormap(gc, gtk_widget_get_colormap(w)); + int y = 0; + for(int n = 0; n < qdbs->rows; ++n) { + int pxw, pxh; + gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh); + gdk_draw_drawable(icon, + gc, + qdbs->pixmaps[n], + 0, 0, /* source coords */ + 0, y, /* dest coords */ + pxw, pxh); /* size */ + y += pxh; + gdk_drawable_unref(qdbs->pixmaps[n]); + qdbs->pixmaps[n] = NULL; + } + g_free(qdbs->pixmaps); + qdbs->pixmaps = NULL; + // TODO scale down a bit, the resulting icons are currently a bit on the + // large side. + gtk_drag_source_set_icon(w, + gtk_widget_get_colormap(w), + icon, + NULL); +} + +static gboolean multidrag_default_predicate(GtkTreePath attribute((unused)) *path, + GtkTreeIter attribute((unused)) *iter) { + return TRUE; +} + /** @brief Allow multi-row drag for @p w * @param w A GtkTreeView widget + * @param predicate Function called to test rows for draggability, or NULL + * + * Suppresses the restriction of selections when a drag is started, and + * intercepts drag-begin to construct an icon. * - * Suppresses the restriction of selections when a drag is started. + * @p predicate should return TRUE for draggable rows and FALSE otherwise, to + * control what goes in the icon. If NULL, equivalent to a function that + * always returns TRUE. */ -void make_treeview_multidrag(GtkWidget *w) { +void make_treeview_multidrag(GtkWidget *w, + multidrag_row_predicate *predicate) { + if(!predicate) + predicate = multidrag_default_predicate; g_signal_connect(w, "button-press-event", G_CALLBACK(multidrag_button_press_event), NULL); g_signal_connect(w, "button-release-event", G_CALLBACK(multidrag_button_release_event), NULL); + g_signal_connect(w, "drag-begin", + G_CALLBACK(multidrag_drag_begin), predicate); } /*