1 /*
2 A system to permit user selection of a block and rotation axis
3 of a magic cube.
4 Copyright (C) 1998, 2003, 2011 John Darrington
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <config.h>
21
22 #include "control.h"
23 #include <gdk/gdkkeysyms.h>
24
25 /* This library provides a means of picking a block using the mouse cursor.
26
27 Two mutually co-operative mechanisms are used in this library. There is a
28 timer callback, which occurs at regular intervals. There is also the mouse
29 motion callback, which occurs whenever the mouse cursor is moving. If two
30 consecutive timer callbacks occur, without and intervening mouse motion callback,
31 then the cursor is assumed to be stationary.
32
33 If a stationary mouse is detected, the program goes on to determine which block
34 in the cube (if any) the cursor is located upon.
35 */
36
37 #include "select.h"
38 #include <float.h>
39 #include <stdio.h>
40
41 #include <stdlib.h>
42
43 #include <gtk/gtk.h>
44 #include <assert.h>
45
46 #include "drwBlock.h"
47 #include "cubeview.h"
48 #include "glarea.h"
49
50 static gboolean detect_motion (GtkWidget * w,
51 GdkEventMotion * event, gpointer user_data);
52
53
54 static gboolean UnsetMotion (gpointer data);
55
56
57 struct cublet_selection
58 {
59 guint timer;
60 select_func *action;
61 gpointer data;
62 gint idle_threshold;
63 double granularity;
64 GtkWidget *w;
65
66 gboolean motion;
67 gboolean stop_detected;
68
69 /* A copy of the last selection taken */
70 struct facet_selection current_selection;
71
72 gint mouse_x;
73 gint mouse_y;
74 };
75
76
77
78 static gboolean
key_press(GtkWidget * w,GdkEventKey * e,gpointer data)79 key_press (GtkWidget *w, GdkEventKey *e, gpointer data)
80 {
81 struct cublet_selection *cs = data;
82
83 if ( e->keyval != GDK_KEY_Shift_L && e->keyval != GDK_KEY_Shift_R )
84 return FALSE;
85
86 selection_func (cs, w);
87
88 return FALSE;
89 }
90
91
92 static void pickPolygons (GbkCubeview * cv, struct cublet_selection *cs,
93 struct facet_selection *sel);
94
95 /* Initialise the selection mechanism. Holdoff is the time for which
96 the mouse must stay still, for anything to happen. Precision is the
97 minimum distance it must have moved. DO_THIS is a pointer to a function
98 to be called when a new block is selected. DATA is a data to be passed to DO_THIS*/
99 struct cublet_selection *
select_create(GtkWidget * w,int holdoff,double precision,select_func * do_this,gpointer data)100 select_create (GtkWidget * w, int holdoff,
101 double precision, select_func * do_this, gpointer data)
102 {
103 struct cublet_selection *cs = g_malloc (sizeof *cs);
104
105 cs->idle_threshold = holdoff;
106 cs->granularity = precision;
107
108 g_signal_connect (w, "motion-notify-event", G_CALLBACK (detect_motion), cs);
109
110 g_signal_connect (w, "key-press-event", G_CALLBACK (key_press), cs);
111 g_signal_connect (w, "key-release-event", G_CALLBACK (key_press), cs);
112
113 cs->action = do_this;
114 cs->data = data;
115 cs->stop_detected = FALSE;
116 cs->motion = FALSE;
117
118 cs->timer = g_timeout_add (cs->idle_threshold, UnsetMotion, cs);
119
120 cs->w = w;
121
122 cs->current_selection.block = -1;
123 cs->current_selection.face = -1;
124 cs->current_selection.quadrant = -1;
125
126 return cs;
127 }
128
129
130 void
select_destroy(struct cublet_selection * cs)131 select_destroy (struct cublet_selection *cs)
132 {
133 select_disable (cs);
134 g_free (cs);
135 }
136
137
138
139 void
select_disable(struct cublet_selection * cs)140 select_disable (struct cublet_selection *cs)
141 {
142 if (cs->timer)
143 g_source_remove (cs->timer);
144 cs->timer = 0;
145 }
146
147 void
select_enable(struct cublet_selection * cs)148 select_enable (struct cublet_selection *cs)
149 {
150 if (0 == cs->timer)
151 cs->timer = g_timeout_add (cs->idle_threshold, UnsetMotion, cs);
152 }
153
154 /* This callback occurs whenever the mouse is moving */
155 static gboolean
detect_motion(GtkWidget * w,GdkEventMotion * event,gpointer data)156 detect_motion (GtkWidget * w, GdkEventMotion * event, gpointer data)
157 {
158 struct cublet_selection *cs = data;
159
160 if (event->type != GDK_MOTION_NOTIFY)
161 return FALSE;
162
163 cs->mouse_x = event->x;
164 cs->mouse_y = event->y;
165
166 cs->motion = TRUE;
167 cs->stop_detected = FALSE;
168
169 return FALSE;
170 }
171
172
173
174
175
176
177 /* This callback occurs at regular intervals. The period is determined by
178 cs->idle_threshold. It checks to see if the mouse has moved, since the last
179 call of this function.
180 Post-condition: motion is FALSE.
181 */
182 static gboolean
UnsetMotion(gpointer data)183 UnsetMotion (gpointer data)
184 {
185 struct cublet_selection *cs = data;
186
187 GbkCubeview *cv = GBK_CUBEVIEW (cs->data);
188
189 if (cs->motion == FALSE)
190 { /* if not moved since last time */
191
192 if (!cs->stop_detected)
193 {
194 /* in here, things happen upon the mouse stopping */
195 cs->stop_detected = TRUE;
196 select_update (cv, cs);
197 }
198 }
199
200 cs->motion = FALSE;
201
202 return TRUE;
203 } /* end UnsetMotion */
204
205
206
207 static int
get_widget_height(GtkWidget * w)208 get_widget_height (GtkWidget * w)
209 {
210 GtkAllocation allocation;
211 gtk_widget_get_allocation (w, &allocation);
212 return allocation.height;
213 }
214
215
216
217 #include "select.h"
218 #include <float.h>
219 #include <stdio.h>
220
221 #include <assert.h>
222 #include <string.h>
223 #include <GL/glu.h>
224
225
226 #define BUFSIZE 512
227
228
229
230
231 static void choose_items (GLint hits, GLuint buffer[],
232 struct facet_selection *);
233
234
235
236 /* Identify the block at screen co-ordinates x, y . This func determines all
237 candidate blocks. That is, all blocks which orthogonally project to x, y. It
238 then calls choose_items, to determine which of them is closest to the screen.
239 */
240 static void
pickPolygons(GbkCubeview * cv,struct cublet_selection * cs,struct facet_selection * sel)241 pickPolygons (GbkCubeview * cv, struct cublet_selection *cs,
242 struct facet_selection *sel)
243 {
244 GLint height;
245
246 GLint viewport[4];
247 GLuint selectBuf[BUFSIZE];
248 GLint hits;
249
250 GtkWidget *w;
251
252 if (!gdk_gl_drawable_make_current (cv->gldrawable, cv->glcontext))
253 {
254 g_critical ("Cannot set gl drawable current\n");
255 return;
256 }
257 assert (cs->granularity > 0);
258 w = cs->w;
259
260 height = get_widget_height (w);
261
262 glSelectBuffer (BUFSIZE, selectBuf);
263
264 glRenderMode (GL_SELECT);
265
266 glInitNames ();
267 glPushName (0xFFFF);
268
269 glMatrixMode (GL_PROJECTION);
270 glPushMatrix ();
271 glLoadIdentity ();
272
273 glGetIntegerv (GL_VIEWPORT, viewport);
274
275 gluPickMatrix ((GLdouble) cs->mouse_x, (GLdouble) (height - cs->mouse_y),
276 cs->granularity, cs->granularity, viewport);
277
278 perspectiveSet (&cv->scene);
279 gbk_cubeview_model_view_init (cv);
280 drawCube (cv->cube, TRUE, cv);
281 glMatrixMode (GL_PROJECTION);
282 glPopMatrix ();
283
284 ERR_CHECK ("");
285 hits = glRenderMode (GL_RENDER);
286
287 choose_items (hits, selectBuf, sel);
288 }
289
290
291
292
293
294 /* Find out which of all the objects in buffer is the one with the
295 lowest Z value. ie. closer in the depth buffer */
296 static void
choose_items(GLint hits,GLuint buffer[],struct facet_selection * selection)297 choose_items (GLint hits, GLuint buffer[], struct facet_selection *selection)
298 {
299 unsigned int i, j;
300 GLuint names, *ptr;
301
302 GLint closest[3] = { -1, -1, -1 };
303
304 float zvalue = FLT_MAX;
305 float z1;
306
307 #define SEL_BLOCK 0
308 #define SEL_FACE 1
309 #define SEL_QUAD 2
310
311 g_return_if_fail (hits >= 0);
312
313 ptr = (GLuint *) buffer;
314
315 for (i = 0; i < hits; i++)
316 {
317 names = *ptr++;
318
319 z1 = (float) *ptr++ / 0x7fffffff;
320
321 ptr++; /* we're not interested in the minimum zvalue */
322
323 if (z1 < zvalue)
324 {
325 zvalue = z1;
326 for (j = 0; j < names; j++)
327 {
328 closest[j] = *ptr++;
329 }
330
331 }
332 else
333 {
334 ptr += names;
335 }
336 }
337
338 selection->block = closest[SEL_BLOCK];
339 selection->face = closest[SEL_FACE];
340 selection->quadrant = closest[SEL_QUAD];
341
342 #if DEBUG
343 fprintf (stderr, "Selected block %d, face %d, quadrant %d\n",
344 selection->block, selection->face, selection->quadrant);
345 #endif
346
347 }
348
349 /* an accessor func to get the value of the currently selected items */
350 const struct facet_selection *
select_get(const struct cublet_selection * cs)351 select_get (const struct cublet_selection *cs)
352 {
353 return &cs->current_selection;
354 }
355
356
357 /* This func, determines which block the mouse is pointing at, and if it
358 has changed, calls the function ptr "cs->action" */
359 void
select_update(GbkCubeview * cv,struct cublet_selection * cs)360 select_update (GbkCubeview * cv, struct cublet_selection *cs)
361 {
362 pickPolygons (cv, cs, &cs->current_selection);
363 if (cs->action)
364 cs->action (cs, cs->data);
365 }
366
367 gboolean
select_is_selected(const struct cublet_selection * cs)368 select_is_selected (const struct cublet_selection *cs)
369 {
370 return cs->current_selection.quadrant != -1;
371 }
372
373
374 GtkWidget *
cublet_selection_get_widget(const struct cublet_selection * cs)375 cublet_selection_get_widget (const struct cublet_selection * cs)
376 {
377 return cs->w;
378 }
379