chiark / gitweb /
Update copyright notices
[disorder] / disobedience / multidrag.c
CommitLineData
6a7eb118
RK
1/*
2 * This file is part of DisOrder
3 * Copyright (C) 2009 Richard Kettlewell
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18/** @file disobedience/multidrag.c
19 * @brief Drag multiple rows of a GtkTreeView
30b358a3
RK
20 *
21 * Normally when you start a drag, GtkTreeView sets the selection to just row
22 * you dragged from (because it can't cope with dragging more than one row at a
23 * time).
24 *
846b01c2
RK
25 * Disobedience needs more.
26 *
27 * Firstly it intercepts button-press-event and button-release event and for
28 * clicks that might be the start of drags, suppresses changes to the
29 * selection. A consequence of this is that it needs to intercept
30 * button-release-event too, to restore the effect of the click, if it turns
31 * out not to be drag after all.
30b358a3
RK
32 *
33 * The location of the initial click is stored in object data called @c
34 * multidrag-where.
35 *
846b01c2
RK
36 * Secondly it intercepts drag-begin and constructs an icon from the rows to be
37 * dragged.
38 *
30b358a3
RK
39 * Inspired by similar code in <a
40 * href="http://code.google.com/p/quodlibet/">Quodlibet</a> (another software
41 * jukebox, albeit as far as I can see a single-user one).
6a7eb118 42 */
3ae36d41 43#include <config.h>
846b01c2
RK
44
45#include <string.h>
3ae36d41
RK
46#include <glib.h>
47#include <gtk/gtk.h>
48
49#include "multidrag.h"
6a7eb118
RK
50
51static gboolean multidrag_selection_block(GtkTreeSelection attribute((unused)) *selection,
52 GtkTreeModel attribute((unused)) *model,
53 GtkTreePath attribute((unused)) *path,
54 gboolean attribute((unused)) path_currently_selected,
55 gpointer data) {
30b358a3 56 return *(const gboolean *)data;
6a7eb118
RK
57}
58
59static void block_selection(GtkWidget *w, gboolean block,
60 int x, int y) {
30b358a3 61 static const gboolean which[] = { FALSE, TRUE };
6a7eb118
RK
62 GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
63 gtk_tree_selection_set_select_function(s,
64 multidrag_selection_block,
30b358a3 65 (gboolean *)&which[!!block],
6a7eb118
RK
66 NULL);
67 // Remember the pointer location
68 int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
69 if(!where) {
70 where = g_malloc(2 * sizeof (int));
71 g_object_set_data(G_OBJECT(w), "multidrag-where", where);
72 }
73 where[0] = x;
74 where[1] = y;
30b358a3 75 // TODO release 'where' when object is destroyed
6a7eb118
RK
76}
77
78static gboolean multidrag_button_press_event(GtkWidget *w,
79 GdkEventButton *event,
80 gpointer attribute((unused)) user_data) {
81 /* By default we assume that anything this button press does should
82 * act as normal */
83 block_selection(w, TRUE, -1, -1);
84 /* We are only interested in left-button behavior */
85 if(event->button != 1)
86 return FALSE;
87 /* We are only interested in unmodified clicks (not SHIFT etc) */
88 if(event->state & GDK_MODIFIER_MASK)
89 return FALSE;
90 /* We are only interested if a well-defined path is clicked */
91 GtkTreePath *path;
92 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
93 event->x, event->y,
94 &path,
95 NULL,
96 NULL, NULL))
97 return FALSE;
98 //gtk_widget_grab_focus(w); // TODO why??
99 /* We are only interested if a selected row is clicked */
100 GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
101 if(!gtk_tree_selection_path_is_selected(s, path))
102 return FALSE;
103 /* We block subsequent selection changes and remember where the
104 * click was */
105 block_selection(w, FALSE, event->x, event->y);
106 return FALSE; /* propagate */
107}
108
109static gboolean multidrag_button_release_event(GtkWidget *w,
110 GdkEventButton *event,
111 gpointer attribute((unused)) user_data) {
112 int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
113
30b358a3
RK
114 /* Did button-press-event do anything? We just check the outcome rather than
115 * going through all the conditions it tests. */
6a7eb118
RK
116 if(where && where[0] != -1) {
117 // Remember where the down-click was
118 const int x = where[0], y = where[1];
119 // Re-allow selections
120 block_selection(w, TRUE, -1, -1);
121 if(x == event->x && y == event->y) {
122 // If the up-click is at the same location as the down-click,
123 // it's not a drag.
124 GtkTreePath *path;
125 GtkTreeViewColumn *col;
126 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
127 event->x, event->y,
128 &path,
129 &col,
130 NULL, NULL)) {
131 gtk_tree_view_set_cursor(GTK_TREE_VIEW(w), path, col, FALSE);
132 }
133 }
134 }
135 return FALSE; /* propagate */
136}
137
846b01c2
RK
138/** @brief State for multidrag_begin() and its callbacks */
139struct multidrag_begin_state {
140 GtkTreeView *view;
ff18efce 141 multidrag_row_predicate *predicate;
846b01c2
RK
142 int rows;
143 int index;
144 GdkPixmap **pixmaps;
145};
146
147/** @brief Callback to construct a row pixmap */
148static void multidrag_make_row_pixmaps(GtkTreeModel attribute((unused)) *model,
149 GtkTreePath *path,
ff18efce 150 GtkTreeIter *iter,
846b01c2
RK
151 gpointer data) {
152 struct multidrag_begin_state *qdbs = data;
153
ff18efce
RK
154 if(qdbs->predicate(path, iter)) {
155 qdbs->pixmaps[qdbs->index++]
156 = gtk_tree_view_create_row_drag_icon(qdbs->view, path);
157 }
846b01c2
RK
158}
159
160/** @brief Called when a drag operation starts
161 * @param w Source widget (the tree view)
162 * @param dc Drag context
ff18efce 163 * @param user_data Row predicate
846b01c2
RK
164 */
165static void multidrag_drag_begin(GtkWidget *w,
166 GdkDragContext attribute((unused)) *dc,
ff18efce 167 gpointer user_data) {
846b01c2
RK
168 struct multidrag_begin_state qdbs[1];
169 GdkPixmap *icon;
170 GtkTreeSelection *sel;
171
172 //fprintf(stderr, "drag-begin\n");
173 memset(qdbs, 0, sizeof *qdbs);
174 qdbs->view = GTK_TREE_VIEW(w);
ff18efce 175 qdbs->predicate = (multidrag_row_predicate *)user_data;
846b01c2
RK
176 sel = gtk_tree_view_get_selection(qdbs->view);
177 /* Find out how many rows there are */
178 if(!(qdbs->rows = gtk_tree_selection_count_selected_rows(sel)))
179 return; /* doesn't make sense */
180 /* Generate a pixmap for each row */
181 qdbs->pixmaps = g_new(GdkPixmap *, qdbs->rows);
182 gtk_tree_selection_selected_foreach(sel,
183 multidrag_make_row_pixmaps,
184 qdbs);
ff18efce
RK
185 /* Might not have used all rows */
186 qdbs->rows = qdbs->index;
846b01c2
RK
187 /* Determine the size of the final icon */
188 int height = 0, width = 0;
189 for(int n = 0; n < qdbs->rows; ++n) {
190 int pxw, pxh;
191 gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
192 if(pxw > width)
193 width = pxw;
194 height += pxh;
195 }
196 if(!width || !height)
197 return; /* doesn't make sense */
198 /* Construct the icon */
199 icon = gdk_pixmap_new(qdbs->pixmaps[0], width, height, -1);
200 GdkGC *gc = gdk_gc_new(icon);
201 gdk_gc_set_colormap(gc, gtk_widget_get_colormap(w));
202 int y = 0;
203 for(int n = 0; n < qdbs->rows; ++n) {
204 int pxw, pxh;
205 gdk_drawable_get_size(qdbs->pixmaps[n], &pxw, &pxh);
206 gdk_draw_drawable(icon,
207 gc,
208 qdbs->pixmaps[n],
209 0, 0, /* source coords */
210 0, y, /* dest coords */
211 pxw, pxh); /* size */
212 y += pxh;
213 gdk_drawable_unref(qdbs->pixmaps[n]);
214 qdbs->pixmaps[n] = NULL;
215 }
216 g_free(qdbs->pixmaps);
217 qdbs->pixmaps = NULL;
218 // TODO scale down a bit, the resulting icons are currently a bit on the
219 // large side.
220 gtk_drag_source_set_icon(w,
221 gtk_widget_get_colormap(w),
222 icon,
223 NULL);
224}
225
ff18efce
RK
226static gboolean multidrag_default_predicate(GtkTreePath attribute((unused)) *path,
227 GtkTreeIter attribute((unused)) *iter) {
228 return TRUE;
229}
230
30b358a3
RK
231/** @brief Allow multi-row drag for @p w
232 * @param w A GtkTreeView widget
ff18efce
RK
233 * @param predicate Function called to test rows for draggability, or NULL
234 *
235 * Suppresses the restriction of selections when a drag is started, and
236 * intercepts drag-begin to construct an icon.
30b358a3 237 *
ff18efce
RK
238 * @p predicate should return TRUE for draggable rows and FALSE otherwise, to
239 * control what goes in the icon. If NULL, equivalent to a function that
240 * always returns TRUE.
30b358a3 241 */
ff18efce
RK
242void make_treeview_multidrag(GtkWidget *w,
243 multidrag_row_predicate *predicate) {
244 if(!predicate)
245 predicate = multidrag_default_predicate;
6a7eb118
RK
246 g_signal_connect(w, "button-press-event",
247 G_CALLBACK(multidrag_button_press_event), NULL);
248 g_signal_connect(w, "button-release-event",
249 G_CALLBACK(multidrag_button_release_event), NULL);
846b01c2 250 g_signal_connect(w, "drag-begin",
ff18efce 251 G_CALLBACK(multidrag_drag_begin), predicate);
6a7eb118
RK
252}
253
254/*
255Local Variables:
256c-basic-offset:2
257comment-column:40
258fill-column:79
259indent-tabs-mode:nil
260End:
261*/