1 /* ip: display VASARI format files.
2 *
3 * doubleclick.c: separate single and double clicks on a widget
4 */
5
6 /*
7
8 Copyright (C) 1991-2003 The National Gallery
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License along
21 with this program; if not, write to the Free Software Foundation, Inc.,
22 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23
24 */
25
26 /*
27
28 These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
29
30 */
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif /*HAVE_UNISTD_H*/
37 #include <string.h>
38
39 #include <gtk/gtk.h>
40 #include <gdk/gdkkeysyms.h>
41
42 #include <vips/vips.h>
43 #include <vips/vips7compat.h>
44 #include <vips/util.h>
45
46 #include "doubleclick.h"
47
48 #define FREEFI( F, S ) { if( S ) { (void) F( S ); (S) = 0; } }
49
50 /* For debugging.
51 #define DEBUG
52 */
53
54 /* The struct we hold our private stuff inside.
55 */
56 typedef struct doubleclick_info {
57 GtkWidget *wid; /* Widget we are attached to */
58 guint click; /* Timer for click determination */
59 gboolean dsingle; /* Do single click on first click of double */
60 GdkEvent event; /* Copy of last event for clients */
61
62 DoubleclickFunc single; /* Callback for single click */
63 void *clients; /* Client data for single */
64 DoubleclickFunc dub; /* Callback for double click */
65 void *clientd; /* Client data for double */
66 } Doubleclick;
67
68 /* Allocate and free clicks.
69 */
70 static Doubleclick *
doubleclick_new()71 doubleclick_new()
72 {
73 Doubleclick *click;
74
75 if( !(click = IM_NEW( NULL, Doubleclick )) )
76 return( NULL );
77
78 click->wid = NULL;
79 click->click = 0;
80 click->dsingle = FALSE;
81 click->single = NULL;
82 click->dub = NULL;
83
84 return( click );
85 }
86
87 void
doubleclick_free(Doubleclick * click)88 doubleclick_free( Doubleclick *click )
89 {
90 im_free( click );
91 }
92
93 /* Timer callback for multiclick detection.
94 */
95 static gboolean
doubleclick_time_cb(Doubleclick * click)96 doubleclick_time_cb( Doubleclick *click )
97 {
98 #ifdef DEBUG
99 g_message( "doubleclick: timeout" );
100 #endif /*DEBUG*/
101
102 click->click = 0;
103
104 /* There has been no second click before the timeout: we do a single
105 * click. If st->dsingle is set though, we have already delivered the
106 * single-click event, so don't bother.
107 */
108 if( !click->dsingle && click->single ) {
109 #ifdef DEBUG
110 g_message( "doubleclick: timeout - calling single" );
111 #endif /*DEBUG*/
112 click->single( click->wid, &click->event, click->clients );
113 }
114
115 /* Stop timer.
116 */
117 return( FALSE );
118 }
119
120 /* There has been an event. Is it single or double?
121 */
122 static gboolean
doubleclick_trigger_cb(GtkWidget * wid,GdkEvent * ev,Doubleclick * click)123 doubleclick_trigger_cb( GtkWidget *wid, GdkEvent *ev, Doubleclick *click )
124 {
125 gboolean handled = FALSE;
126
127 /* Make sure we have a button 1 press.
128 */
129 if( ev->type != GDK_BUTTON_PRESS || ev->button.button != 1 )
130 return( handled );
131
132 /* Note event for client.
133 */
134 click->event = *ev;
135
136 if( click->click ) {
137 /* There is a timeout pending - ie. there was a click
138 * recently. This must be the second part of a double click.
139 * Cancel the timeout and do a double click action.
140 */
141 FREEFI( g_source_remove, click->click );
142 if( click->dub ) {
143 #ifdef DEBUG
144 g_message( "doubleclick: seen double" );
145 #endif /*DEBUG*/
146 click->dub( click->wid, &click->event, click->clientd );
147 handled = TRUE;
148 }
149 }
150 else {
151 #ifdef DEBUG
152 g_message( "doubleclick: starting timer" );
153 #endif /*DEBUG*/
154 /* No previous click. This may be either. Start a timeout to
155 * help us decide.
156 *
157 * We aren't supposed to look at double_click_time, but
158 * there's no access method, I think.
159 */
160 click->click = g_timeout_add(
161 gtk_widget_get_display( wid )->double_click_time,
162 (GSourceFunc) doubleclick_time_cb, click );
163
164 /* If do-single-on-double is set, we can trigger a
165 * single-click now.
166 */
167 if( click->dsingle && click->single ) {
168 click->single( click->wid, &click->event,
169 click->clients );
170 handled = TRUE;
171 }
172 }
173
174 return( handled );
175 }
176
177 /* Destroy a doubleclick_info.
178 */
179 /*ARGSUSED*/
180 static void
doubleclick_destroy_cb(GtkWidget * wid,Doubleclick * click)181 doubleclick_destroy_cb( GtkWidget *wid, Doubleclick *click )
182 {
183 #ifdef DEBUG
184 g_message( "doubleclick: destroyed" );
185 #endif /*DEBUG*/
186
187 if( click->click ) {
188 /* Don't trigger a single-click, even though there was one
189 * recently, since our widget is being destroyed.
190 */
191 FREEFI( g_source_remove, click->click );
192 }
193
194 doubleclick_free( click );
195 }
196
197 static void
doubleclick_realize_cb(GtkWidget * wid)198 doubleclick_realize_cb( GtkWidget *wid )
199 {
200 gtk_widget_add_events( wid, GDK_BUTTON_PRESS_MASK );
201 }
202
203 /* Attach callbacks to a widget.
204 */
205 void
doubleclick_add(GtkWidget * wid,gboolean dsingle,DoubleclickFunc single,void * clients,DoubleclickFunc dub,void * clientd)206 doubleclick_add( GtkWidget *wid, gboolean dsingle,
207 DoubleclickFunc single, void *clients,
208 DoubleclickFunc dub, void *clientd )
209 {
210 Doubleclick *click = doubleclick_new();
211
212 /* Complete fields.
213 */
214 click->wid = wid;
215 click->dsingle = dsingle;
216
217 click->single = single;
218 click->dub = dub;
219 click->clients = clients;
220 click->clientd = clientd;
221
222 /* Add callbacks.
223 */
224 gtk_signal_connect( GTK_OBJECT( wid ), "destroy",
225 GTK_SIGNAL_FUNC( doubleclick_destroy_cb ), click );
226 gtk_signal_connect( GTK_OBJECT( wid ), "event",
227 GTK_SIGNAL_FUNC( doubleclick_trigger_cb ), click );
228 gtk_signal_connect( GTK_OBJECT( wid ), "realize",
229 GTK_SIGNAL_FUNC( doubleclick_realize_cb ), NULL );
230 }
231