]> Pileus Git - grits/blob - src/objects/grits-object.c
Add motion threshold when clicking objects
[grits] / src / objects / grits-object.c
1 /*
2  * Copyright (C) 2009-2011 Andy Spencer <andy753421@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 /**
19  * SECTION:grits-object
20  * @short_description: Base class for drawing operations
21  *
22  * Objects in grits are things which can be added to the viewer and will be
23  * displayed to the user. Each object has information such as it's location and
24  * level of detail which are used by the viewer to determine which objects
25  * should be drawn.
26  *
27  * Each #GritsObject is also a #GObject, but not every GObject in grits is a
28  * GritsObject. The "Object" part of the name is just coincidence.
29  */
30
31 #include <config.h>
32 #include <math.h>
33
34 #include "gtkgl.h"
35 #include "grits-object.h"
36 #include "grits-marshal.h"
37
38 /* Constants */
39 enum {
40         SIG_ENTER,
41         SIG_LEAVE,
42         SIG_CLICKED,
43         SIG_BUTTON_PRESS,
44         SIG_BUTTON_RELEASE,
45         SIG_KEY_PRESS,
46         SIG_KEY_RELEASE,
47         SIG_MOTION,
48         NUM_SIGNALS,
49 };
50 static guint signals[NUM_SIGNALS];
51
52 void grits_object_pickdraw(GritsObject *object, GritsOpenGL *opengl, gboolean pick)
53 {
54         GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
55
56         if (!klass->draw) {
57                 g_warning("GritsObject: draw - Unimplemented");
58                 return;
59         }
60
61         /* Skip hidden objects */
62         if (object->hidden)
63                 return;
64
65         /* Skip object with no signals when picking */
66         for (int i = 0; pick; i++) {
67                 if (i == NUM_SIGNALS)
68                         return;
69                 if (g_signal_has_handler_pending(object, signals[i], 0, FALSE))
70                         break;
71         }
72
73         /* Support GritsTester */
74         if (!GRITS_IS_OPENGL(opengl)) {
75                 g_debug("GritsObject: draw - drawing raw object");
76                 klass->draw(object, opengl);
77                 return;
78         }
79
80         /* Calculate distance for LOD and horizon tests */
81         GritsPoint *center = &object->center;
82         if ((!(object->skip & GRITS_SKIP_LOD) ||
83              !(object->skip & GRITS_SKIP_HORIZON)) &&
84             (center->elev != -EARTH_R)) {
85                 /* LOD test */
86                 gdouble eye[3], obj[3];
87                 grits_viewer_get_location(GRITS_VIEWER(opengl),
88                                 &eye[0], &eye[1], &eye[2]);
89                 gdouble elev = eye[2];
90                 lle2xyz(eye[0], eye[1], eye[2],
91                                 &eye[0], &eye[1], &eye[2]);
92                 lle2xyz(center->lat, center->lon, center->elev,
93                                 &obj[0], &obj[1], &obj[2]);
94                 gdouble dist = distd(obj, eye);
95
96                 /* Level of detail test */
97                 if (!(object->skip & GRITS_SKIP_LOD)
98                                 && object->lod > 0) {
99                         if (object->lod < dist)
100                                 return;
101                 }
102
103                 /* Horizon test */
104                 if (!(object->skip & GRITS_SKIP_HORIZON)) {
105                         gdouble c = EARTH_R+elev;
106                         gdouble a = EARTH_R;
107                         gdouble horizon = sqrt(c*c - a*a);
108                         if (dist > horizon)
109                                 return;
110                 }
111         }
112
113         /* Save state, draw, restore state */
114         g_mutex_lock(&opengl->sphere_lock);
115         if (!(object->skip & GRITS_SKIP_STATE)) {
116                 glPushAttrib(GL_ALL_ATTRIB_BITS);
117                 glMatrixMode(GL_PROJECTION); glPushMatrix();
118                 glMatrixMode(GL_MODELVIEW);  glPushMatrix();
119         }
120
121         if (!(object->skip & GRITS_SKIP_CENTER))
122                 grits_viewer_center_position(GRITS_VIEWER(opengl),
123                                 object->center.lat,
124                                 object->center.lon,
125                                 object->center.elev);
126
127         if (pick && klass->pick)
128                 klass->pick(object, opengl);
129         else
130                 klass->draw(object, opengl);
131
132         if (!(object->skip & GRITS_SKIP_STATE)) {
133                 glPopAttrib();
134                 glMatrixMode(GL_PROJECTION); glPopMatrix();
135                 glMatrixMode(GL_MODELVIEW);  glPopMatrix();
136         }
137         g_mutex_unlock(&opengl->sphere_lock);
138 }
139
140 /**
141  * grits_object_draw:
142  * @object: the object
143  * @opengl: the viewer the object is being displayed in
144  *
145  * Perform any OpenGL commands necessasairy to draw the object.
146  *
147  * The GL_PROJECTION and GL_MODELVIEW matricies and GL_ALL_ATTRIB_BITS will be
148  * restored to the default state after the call to draw.
149  */
150 void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
151 {
152         grits_object_pickdraw(object, opengl, FALSE);
153 }
154
155 void grits_object_hide(GritsObject *object, gboolean hidden)
156 {
157         GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
158         object->hidden = hidden;
159         if (klass->hide)
160                 klass->hide(object, hidden);
161 }
162
163 void grits_object_queue_draw(GritsObject *object)
164 {
165         if (object->viewer)
166                 grits_viewer_queue_draw(object->viewer);
167 }
168
169 void grits_object_set_cursor(GritsObject *object, GdkCursorType cursor)
170 {
171         // Used by grits OpenGL
172         object->cursor = gdk_cursor_new(cursor);
173 }
174
175 /* Event handling */
176 void grits_object_pick(GritsObject *object, GritsOpenGL *opengl)
177 {
178         grits_object_pickdraw(object, opengl, TRUE);
179 }
180
181 gboolean grits_object_set_pointer(GritsObject *object, GdkEvent *event, gboolean selected)
182 {
183         gboolean rval = FALSE;
184         if (selected) {
185                 if (!object->state.selected)
186                         g_signal_emit(object, signals[SIG_ENTER], 0, event, &rval);
187                 object->state.selected = TRUE;
188         } else {
189                 if (object->state.selected)
190                         g_signal_emit(object, signals[SIG_LEAVE], 0, event, &rval);
191                 object->state.selected = FALSE;
192         }
193         return rval;
194 }
195
196 gboolean grits_object_event(GritsObject *object, GdkEvent *event)
197 {
198         const int map[GDK_EVENT_LAST] = {
199                 [GDK_BUTTON_PRESS  ] SIG_BUTTON_PRESS,
200                 [GDK_2BUTTON_PRESS ] SIG_BUTTON_PRESS,
201                 [GDK_3BUTTON_PRESS ] SIG_BUTTON_PRESS,
202                 [GDK_BUTTON_RELEASE] SIG_BUTTON_RELEASE,
203                 [GDK_KEY_PRESS     ] SIG_KEY_PRESS,
204                 [GDK_KEY_RELEASE   ] SIG_KEY_RELEASE,
205                 [GDK_MOTION_NOTIFY ] SIG_MOTION,
206         };
207         if (!object->state.selected)
208                 return FALSE;
209         guint sig = map[event->type];
210         gboolean rval = FALSE;
211
212         /* Handle button click */
213         if (sig == SIG_BUTTON_PRESS)
214                 object->state.clicking  = GRITS_CLICK_THRESHOLD;
215         if (sig == SIG_MOTION && object->state.clicking)
216                 object->state.clicking -= 1;
217         if (sig == SIG_BUTTON_RELEASE && object->state.clicking)
218                 g_signal_emit(object, signals[SIG_CLICKED], 0, event, &rval);
219         if (sig == SIG_BUTTON_RELEASE)
220                 object->state.clicking  = 0;
221
222         /* Emit this signal */
223         if (rval == FALSE) {
224                 if (!g_signal_has_handler_pending(object, signals[sig], 0, FALSE))
225                         return FALSE;
226                 g_signal_emit(object, signals[sig], 0, event, &rval);
227         }
228
229         if (rval == TRUE)
230                 g_debug("GritsObject: breaking chained event");
231         return rval;
232 }
233
234 /* GObject stuff */
235 G_DEFINE_ABSTRACT_TYPE(GritsObject, grits_object, G_TYPE_OBJECT);
236 static void grits_object_init(GritsObject *object)
237 {
238         object->center.lat  =  0;
239         object->center.lon  =  0;
240         object->center.elev = -EARTH_R;
241 }
242
243 static void grits_object_class_init(GritsObjectClass *klass)
244 {
245         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
246
247         /**
248          * GritsObject::enter:
249          * @object: the object.
250          *
251          * The ::enter signal is emitted when the pointer moves over the object
252          */
253         signals[SIG_ENTER] = g_signal_new(
254                         "enter",
255                         G_TYPE_FROM_CLASS(gobject_class),
256                         G_SIGNAL_RUN_LAST,
257                         0,
258                         NULL,
259                         NULL,
260                         grits_cclosure_marshal_BOOLEAN__POINTER,
261                         G_TYPE_BOOLEAN,
262                         1,
263                         G_TYPE_POINTER);
264
265         /**
266          * GritsViewer::leave:
267          * @object: the object.
268          *
269          * The ::leave signal is emitted when the pointer moves away from the
270          * object
271          */
272         signals[SIG_LEAVE] = g_signal_new(
273                         "leave",
274                         G_TYPE_FROM_CLASS(gobject_class),
275                         G_SIGNAL_RUN_LAST,
276                         0,
277                         NULL,
278                         NULL,
279                         grits_cclosure_marshal_BOOLEAN__POINTER,
280                         G_TYPE_BOOLEAN,
281                         1,
282                         G_TYPE_POINTER);
283
284         /**
285          * GritsViewer::clicked:
286          * @object: the object.
287          *
288          * The ::clicked signal is emitted when the user clicks on the object
289          */
290         signals[SIG_CLICKED] = g_signal_new(
291                         "clicked",
292                         G_TYPE_FROM_CLASS(gobject_class),
293                         G_SIGNAL_RUN_LAST,
294                         0,
295                         NULL,
296                         NULL,
297                         grits_cclosure_marshal_BOOLEAN__POINTER,
298                         G_TYPE_BOOLEAN,
299                         1,
300                         G_TYPE_POINTER);
301
302         /**
303          * GritsViewer::button-press:
304          * @object: the object.
305          * @event:  the GdkEventButton which triggered this signal
306          *
307          * The ::button-press signal is emitted when a button (typically from a
308          * mouse) is pressed.
309          */
310         signals[SIG_BUTTON_PRESS] = g_signal_new(
311                         "button-press",
312                         G_TYPE_FROM_CLASS(gobject_class),
313                         G_SIGNAL_RUN_LAST,
314                         0,
315                         NULL,
316                         NULL,
317                         grits_cclosure_marshal_BOOLEAN__POINTER,
318                         G_TYPE_BOOLEAN,
319                         1,
320                         G_TYPE_POINTER);
321
322         /**
323          * GritsViewer::button-release:
324          * @object: the object.
325          * @event:  the GdkEventButton which triggered this signal
326          *
327          * The ::button-release signal is emitted when a button (typically from
328          * a mouse) is released.
329          */
330         signals[SIG_BUTTON_RELEASE] = g_signal_new(
331                         "button-release",
332                         G_TYPE_FROM_CLASS(gobject_class),
333                         G_SIGNAL_RUN_LAST,
334                         0,
335                         NULL,
336                         NULL,
337                         grits_cclosure_marshal_BOOLEAN__POINTER,
338                         G_TYPE_BOOLEAN,
339                         1,
340                         G_TYPE_POINTER);
341
342         /**
343          * GritsViewer::key-press:
344          * @object: the object.
345          * @event:  the GdkEventKey which triggered this signal
346          *
347          * The ::key-press signal is emitted when a key is pressed.
348          */
349         signals[SIG_KEY_PRESS] = g_signal_new(
350                         "key-press",
351                         G_TYPE_FROM_CLASS(gobject_class),
352                         G_SIGNAL_RUN_LAST,
353                         0,
354                         NULL,
355                         NULL,
356                         grits_cclosure_marshal_BOOLEAN__POINTER,
357                         G_TYPE_BOOLEAN,
358                         1,
359                         G_TYPE_POINTER);
360
361         /**
362          * GritsViewer::key-release:
363          * @object: the object.
364          * @event:  the GdkEventKey which triggered this signal
365          *
366          * The ::key-release signal is emitted when a key is released.
367          */
368         signals[SIG_KEY_RELEASE] = g_signal_new(
369                         "key-release",
370                         G_TYPE_FROM_CLASS(gobject_class),
371                         G_SIGNAL_RUN_LAST,
372                         0,
373                         NULL,
374                         NULL,
375                         grits_cclosure_marshal_BOOLEAN__POINTER,
376                         G_TYPE_BOOLEAN,
377                         1,
378                         G_TYPE_POINTER);
379
380         /**
381          * GritsViewer::motion:
382          * @object: the object.
383          * @event:  the GdkEventMotion which triggered this signal
384          *
385          * The ::motion signal is emitted the pointer moves over the object
386          */
387         signals[SIG_MOTION] = g_signal_new(
388                         "motion",
389                         G_TYPE_FROM_CLASS(gobject_class),
390                         G_SIGNAL_RUN_LAST,
391                         0,
392                         NULL,
393                         NULL,
394                         grits_cclosure_marshal_BOOLEAN__POINTER,
395                         G_TYPE_BOOLEAN,
396                         1,
397                         G_TYPE_POINTER);
398 }