2 * Copyright (C) 2009 Richard Kettlewell
4 * Note that this license ONLY applies to multidrag.c and multidrag.h, not to
5 * the rest of DisOrder.
7 * Permission is hereby granted, free of charge, to any person
8 * obtaining a copy of this software and associated documentation files
9 * (the "Software"), to deal in the Software without restriction,
10 * including without limitation the rights to use, copy, modify, merge,
11 * publish, distribute, sublicense, and/or sell copies of the Software,
12 * and to permit persons to whom the Software is furnished to do so,
13 * subject to the following conditions:
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
22 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 /** @file disobedience/multidrag.c
27 * @brief Drag multiple rows of a GtkTreeView
29 * Normally when you start a drag, GtkTreeView sets the selection to just row
30 * you dragged from (because it can't cope with dragging more than one row at a
33 * Disobedience needs more.
35 * Firstly it intercepts button-press-event and button-release event and for
36 * clicks that might be the start of drags, suppresses changes to the
37 * selection. A consequence of this is that it needs to intercept
38 * button-release-event too, to restore the effect of the click, if it turns
39 * out not to be drag after all.
41 * The location of the initial click is stored in object data called @c
44 * Secondly it intercepts drag-begin and constructs an icon from the rows to be
47 * Inspired by similar code in <a
48 * href="http://code.google.com/p/quodlibet/">Quodlibet</a> (another software
49 * jukebox, albeit as far as I can see a single-user one).
57 #include "multidrag.h"
59 static gboolean
multidrag_selection_block(GtkTreeSelection
attribute((unused
)) *selection
,
60 GtkTreeModel
attribute((unused
)) *model
,
61 GtkTreePath
attribute((unused
)) *path
,
62 gboolean
attribute((unused
)) path_currently_selected
,
64 return *(const gboolean
*)data
;
67 static void block_selection(GtkWidget
*w
, gboolean block
,
69 static const gboolean which
[] = { FALSE
, TRUE
};
70 GtkTreeSelection
*s
= gtk_tree_view_get_selection(GTK_TREE_VIEW(w
));
71 gtk_tree_selection_set_select_function(s
,
72 multidrag_selection_block
,
73 (gboolean
*)&which
[!!block
],
75 // Remember the pointer location
76 int *where
= g_object_get_data(G_OBJECT(w
), "multidrag-where");
78 where
= g_malloc(2 * sizeof (int));
79 g_object_set_data_full(G_OBJECT(w
), "multidrag-where", where
,
86 static gboolean
multidrag_button_press_event(GtkWidget
*w
,
87 GdkEventButton
*event
,
88 gpointer
attribute((unused
)) user_data
) {
89 /* By default we assume that anything this button press does should
91 block_selection(w
, TRUE
, -1, -1);
92 /* We are only interested in left-button behavior */
93 if(event
->button
!= 1)
95 /* We are only interested in unmodified clicks (not SHIFT etc) */
96 if(event
->state
& GDK_MODIFIER_MASK
)
98 /* We are only interested if a well-defined path is clicked */
99 GtkTreePath
*path
= NULL
;
100 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w
),
106 /* We are only interested if a selected row is clicked */
107 GtkTreeSelection
*s
= gtk_tree_view_get_selection(GTK_TREE_VIEW(w
));
108 if(gtk_tree_selection_path_is_selected(s
, path
)) {
109 /* We block subsequent selection changes and remember where the
111 block_selection(w
, FALSE
, event
->x
, event
->y
);
114 gtk_tree_path_free(path
);
115 return FALSE
; /* propagate */
118 static gboolean
multidrag_button_release_event(GtkWidget
*w
,
119 GdkEventButton
*event
,
120 gpointer
attribute((unused
)) user_data
) {
121 int *where
= g_object_get_data(G_OBJECT(w
), "multidrag-where");
123 /* Did button-press-event do anything? We just check the outcome rather than
124 * going through all the conditions it tests. */
125 if(where
&& where
[0] != -1) {
126 // Remember where the down-click was
127 const int x
= where
[0], y
= where
[1];
128 // Re-allow selections
129 block_selection(w
, TRUE
, -1, -1);
130 if(x
== event
->x
&& y
== event
->y
) {
131 // If the up-click is at the same location as the down-click,
133 GtkTreePath
*path
= NULL
;
134 GtkTreeViewColumn
*col
;
135 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w
),
140 gtk_tree_view_set_cursor(GTK_TREE_VIEW(w
), path
, col
, FALSE
);
143 gtk_tree_path_free(path
);
146 return FALSE
; /* propagate */
149 /** @brief State for multidrag_begin() and its callbacks */
150 struct multidrag_begin_state
{
152 multidrag_row_predicate
*predicate
;
158 /** @brief Callback to construct a row pixmap */
159 static void multidrag_make_row_pixmaps(GtkTreeModel
attribute((unused
)) *model
,
163 struct multidrag_begin_state
*qdbs
= data
;
165 if(qdbs
->predicate(path
, iter
)) {
166 qdbs
->pixmaps
[qdbs
->index
++]
167 = gtk_tree_view_create_row_drag_icon(qdbs
->view
, path
);
171 /** @brief Called when a drag operation starts
172 * @param w Source widget (the tree view)
173 * @param dc Drag context
174 * @param user_data Row predicate
176 static void multidrag_drag_begin(GtkWidget
*w
,
177 GdkDragContext
attribute((unused
)) *dc
,
178 gpointer user_data
) {
179 struct multidrag_begin_state qdbs
[1];
181 GtkTreeSelection
*sel
;
183 //fprintf(stderr, "drag-begin\n");
184 memset(qdbs
, 0, sizeof *qdbs
);
185 qdbs
->view
= GTK_TREE_VIEW(w
);
186 qdbs
->predicate
= (multidrag_row_predicate
*)user_data
;
187 sel
= gtk_tree_view_get_selection(qdbs
->view
);
188 /* Find out how many rows there are */
189 if(!(qdbs
->rows
= gtk_tree_selection_count_selected_rows(sel
)))
190 return; /* doesn't make sense */
191 /* Generate a pixmap for each row */
192 qdbs
->pixmaps
= g_new(GdkPixmap
*, qdbs
->rows
);
193 gtk_tree_selection_selected_foreach(sel
,
194 multidrag_make_row_pixmaps
,
196 /* Might not have used all rows */
197 qdbs
->rows
= qdbs
->index
;
198 /* Determine the size of the final icon */
199 int height
= 0, width
= 0;
200 for(int n
= 0; n
< qdbs
->rows
; ++n
) {
202 gdk_drawable_get_size(qdbs
->pixmaps
[n
], &pxw
, &pxh
);
207 if(!width
|| !height
)
208 return; /* doesn't make sense */
209 /* Construct the icon */
210 icon
= gdk_pixmap_new(qdbs
->pixmaps
[0], width
, height
, -1);
211 GdkGC
*gc
= gdk_gc_new(icon
);
212 gdk_gc_set_colormap(gc
, gtk_widget_get_colormap(w
));
214 for(int n
= 0; n
< qdbs
->rows
; ++n
) {
216 gdk_drawable_get_size(qdbs
->pixmaps
[n
], &pxw
, &pxh
);
217 gdk_draw_drawable(icon
,
220 0, 0, /* source coords */
221 0, y
, /* dest coords */
222 pxw
, pxh
); /* size */
224 gdk_drawable_unref(qdbs
->pixmaps
[n
]);
225 qdbs
->pixmaps
[n
] = NULL
;
227 g_free(qdbs
->pixmaps
);
228 qdbs
->pixmaps
= NULL
;
229 // TODO scale down a bit, the resulting icons are currently a bit on the
231 gtk_drag_source_set_icon(w
,
232 gtk_widget_get_colormap(w
),
237 static gboolean
multidrag_default_predicate(GtkTreePath
attribute((unused
)) *path
,
238 GtkTreeIter
attribute((unused
)) *iter
) {
242 /** @brief Allow multi-row drag for @p w
243 * @param w A GtkTreeView widget
244 * @param predicate Function called to test rows for draggability, or NULL
246 * Suppresses the restriction of selections when a drag is started, and
247 * intercepts drag-begin to construct an icon.
249 * @p predicate should return TRUE for draggable rows and FALSE otherwise, to
250 * control what goes in the icon. If NULL, equivalent to a function that
251 * always returns TRUE.
253 void make_treeview_multidrag(GtkWidget
*w
,
254 multidrag_row_predicate
*predicate
) {
256 predicate
= multidrag_default_predicate
;
257 g_signal_connect(w
, "button-press-event",
258 G_CALLBACK(multidrag_button_press_event
), NULL
);
259 g_signal_connect(w
, "button-release-event",
260 G_CALLBACK(multidrag_button_release_event
), NULL
);
261 g_signal_connect(w
, "drag-begin",
262 G_CALLBACK(multidrag_drag_begin
), predicate
);