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