chiark / gitweb /
Unref dead pixmaps.
[disorder] / disobedience / multidrag.c
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
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  *
25  * Disobedience needs more.  To implement this it intercepts button-press-event
26  * and button-release event and for clicks that might be the start of drags,
27  * suppresses changes to the selection.  A consequence of this is that it needs
28  * to intercept button-release-event too, to restore the effect of the click,
29  * if it turns out not to be drag after all.
30  *
31  * The location of the initial click is stored in object data called @c
32  * multidrag-where.
33  *
34  * Inspired by similar code in <a
35  * href="http://code.google.com/p/quodlibet/">Quodlibet</a> (another software
36  * jukebox, albeit as far as I can see a single-user one).
37  */
38 #include "disobedience.h"
39
40 static gboolean multidrag_selection_block(GtkTreeSelection attribute((unused)) *selection,
41                                           GtkTreeModel attribute((unused)) *model,
42                                           GtkTreePath attribute((unused)) *path,
43                                           gboolean attribute((unused)) path_currently_selected,
44                                           gpointer data) {
45   return *(const gboolean *)data;
46 }
47
48 static void block_selection(GtkWidget *w, gboolean block,
49                             int x, int y) {
50   static const gboolean which[] = { FALSE, TRUE };
51   GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
52   gtk_tree_selection_set_select_function(s,
53                                          multidrag_selection_block,
54                                          (gboolean *)&which[!!block],
55                                          NULL);
56   // Remember the pointer location
57   int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
58   if(!where) {
59     where = g_malloc(2 * sizeof (int));
60     g_object_set_data(G_OBJECT(w), "multidrag-where", where);
61   }
62   where[0] = x;
63   where[1] = y;
64   // TODO release 'where' when object is destroyed
65 }
66
67 static gboolean multidrag_button_press_event(GtkWidget *w,
68                                              GdkEventButton *event,
69                                              gpointer attribute((unused)) user_data) {
70   /* By default we assume that anything this button press does should
71    * act as normal */
72   block_selection(w, TRUE, -1, -1);
73   /* We are only interested in left-button behavior */
74   if(event->button != 1)
75     return FALSE;
76   /* We are only interested in unmodified clicks (not SHIFT etc) */
77   if(event->state & GDK_MODIFIER_MASK)
78     return FALSE;
79   /* We are only interested if a well-defined path is clicked */
80   GtkTreePath *path;
81   if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
82                                     event->x, event->y,
83                                     &path,
84                                     NULL,
85                                     NULL, NULL))
86     return FALSE;
87   //gtk_widget_grab_focus(w);    // TODO why??
88   /* We are only interested if a selected row is clicked */
89   GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
90   if(!gtk_tree_selection_path_is_selected(s, path))
91     return FALSE;
92   /* We block subsequent selection changes and remember where the
93    * click was */
94   block_selection(w, FALSE, event->x, event->y);
95   return FALSE;                 /* propagate */
96 }
97
98 static gboolean multidrag_button_release_event(GtkWidget *w,
99                                                GdkEventButton *event,
100                                                gpointer attribute((unused)) user_data) {
101   int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
102
103   /* Did button-press-event do anything?  We just check the outcome rather than
104    * going through all the conditions it tests. */
105   if(where && where[0] != -1) {
106     // Remember where the down-click was
107     const int x = where[0], y = where[1];
108     // Re-allow selections
109     block_selection(w, TRUE, -1, -1);
110     if(x == event->x && y == event->y) {
111       // If the up-click is at the same location as the down-click,
112       // it's not a drag.
113       GtkTreePath *path;
114       GtkTreeViewColumn *col;
115       if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
116                                        event->x, event->y,
117                                        &path,
118                                        &col,
119                                        NULL, NULL)) {
120         gtk_tree_view_set_cursor(GTK_TREE_VIEW(w), path, col, FALSE);
121       }
122     }
123   }
124   return FALSE;                 /* propagate */
125 }
126
127 /** @brief Allow multi-row drag for @p w
128  * @param w A GtkTreeView widget
129  *
130  * Suppresses the restriction of selections when a drag is started.
131  */
132 void make_treeview_multidrag(GtkWidget *w) {
133   g_signal_connect(w, "button-press-event",
134                    G_CALLBACK(multidrag_button_press_event), NULL);
135   g_signal_connect(w, "button-release-event",
136                    G_CALLBACK(multidrag_button_release_event), NULL);
137 }
138
139 /*
140 Local Variables:
141 c-basic-offset:2
142 comment-column:40
143 fill-column:79
144 indent-tabs-mode:nil
145 End:
146 */