1 /**************************************************************************\
2 * Copyright (c) Kongsberg Oil & Gas Technologies AS
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of the copyright holder nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32
33 /*!
34 \class SoEventCallback SoEventCallback.h Inventor/nodes/SoEventCallback.h
35 \brief The SoEventCallback class provides functionality for catching events.
36
37 \ingroup nodes
38
39 Use SoEventCallback nodes in the scenegraph for catching user
40 interaction events with the scenegraph's render canvas.
41
42
43 This is how event handling works in Coin: when the user interacts
44 with the render canvas, for instance by using the mouse pointer or
45 by hitting the keyboard, the GUI interface toolkit (ie SoQt, SoWin,
46 SoXt, Sc21 ...) will catch the event and translate it from a
47 windowsystem-specific event to a generic Coin event. (For the types
48 of generic Coin events, see the classes derived from SoEvent.) This
49 event will then be wrapped inside a SoHandleEventAction and applied
50 to the scenegraph. All this happens within the So[Qt|Xt|Win|...]
51 toolkit.
52
53 The SoHandleEventAction then traverses the scenegraph, delivering
54 the event to any node type which "is interested" in it. The
55 SoEventCallback nodetype catches the action and forwards the event
56 to a callback function set up by the application programmer.
57
58 Be careful about which position in the scenegraph you insert
59 SoEventCallback nodes if you are also using any of the built-in Coin
60 library elements which are interested in user interaction events
61 (like for instance the dragger and manipulator classes and the
62 SoSelection nodes). These Coin elements might catch the event for
63 themselves, short-circuiting the SoHandleEventAction traversal so
64 the event will never reach the SoEventCallback node(s) you insert.
65
66 <b>FILE FORMAT/DEFAULTS:</b>
67 \code
68 EventCallback {
69 }
70 \endcode
71 */
72
73 // *************************************************************************
74
75 /*! \file SoEventCallback.h */
76 #include <Inventor/nodes/SoEventCallback.h>
77
78 #include <Inventor/SoPath.h>
79 #include <Inventor/SoPickedPoint.h>
80 #include <Inventor/actions/SoHandleEventAction.h>
81 #include <Inventor/errors/SoDebugError.h>
82 #include <Inventor/events/SoEvent.h>
83
84 #include "nodes/SoSubNodeP.h"
85
86 // *************************************************************************
87
88 /*!
89 \typedef void SoEventCallbackCB(void * userdata, SoEventCallback * node)
90
91 Callback functions for SoEventCallback::addEventCallback() must be
92 of this type. \a userdata is the last argument to
93 SoEventCallback::addEventCallback(), and \a node is of course the
94 SoEventCallback node in the scenegraph which caused the invocation
95 of the callback.
96 */
97
98 // *************************************************************************
99
100 SO_NODE_SOURCE(SoEventCallback);
101
102 // *************************************************************************
103
104 /*!
105 Constructor.
106 */
SoEventCallback(void)107 SoEventCallback::SoEventCallback(void)
108 {
109 SO_NODE_INTERNAL_CONSTRUCTOR(SoEventCallback);
110
111 this->heaction = NULL;
112 this->path = NULL;
113 }
114
115 /*!
116 Destructor.
117 */
~SoEventCallback()118 SoEventCallback::~SoEventCallback()
119 {
120 if (this->path) this->path->unref();
121 }
122
123 // Doc from superclass.
124 void
initClass(void)125 SoEventCallback::initClass(void)
126 {
127 SO_NODE_INTERNAL_INIT_CLASS(SoEventCallback, SO_FROM_INVENTOR_1);
128 }
129
130 // *************************************************************************
131
132 /*!
133 Sets the path that must be picked before the registered callbacks
134 are invoked. If \c NULL, callbacks will be invoked for every event
135 that matches the callback event type.
136
137 \sa getPath()
138 */
139 void
setPath(SoPath * pathptr)140 SoEventCallback::setPath(SoPath * pathptr)
141 {
142 if (this->path) {
143 this->path->unref();
144 this->path = NULL;
145 }
146 if (pathptr) {
147 #if COIN_DEBUG
148 if (pathptr->getRefCount() == 0) {
149 SoDebugError::postWarning("SoEventCallback::setPath",
150 "input path has reference count equal to zero");
151 }
152 #endif // COIN_DEBUG
153 this->path = pathptr->copy();
154 this->path->ref();
155 }
156 }
157
158 /*!
159 Returns the path that must be picked before callbacks are invoked.
160
161 \sa setPath()
162 */
163 const SoPath *
getPath(void)164 SoEventCallback::getPath(void)
165 {
166 return this->path;
167 }
168
169
170 /*!
171 Set up a callback function \a f which will be invoked for the given
172 \a eventtype. \a userdata will be given as the first argument to the
173 function.
174 */
175 void
addEventCallback(SoType eventtype,SoEventCallbackCB * f,void * userdata)176 SoEventCallback::addEventCallback(SoType eventtype, SoEventCallbackCB * f,
177 void * userdata)
178 {
179 struct CallbackInfo cb;
180 cb.func = f;
181 cb.eventtype = eventtype;
182 cb.userdata = userdata;
183
184 this->callbacks.append(cb);
185 }
186
187 /*!
188 Unregister the given callback function \a f.
189 */
190 void
removeEventCallback(SoType eventtype,SoEventCallbackCB * f,void * userdata)191 SoEventCallback::removeEventCallback(SoType eventtype, SoEventCallbackCB * f,
192 void * userdata)
193 {
194 for (int i = 0; i < this->callbacks.getLength(); i++) {
195 if (this->callbacks[i].func == f &&
196 this->callbacks[i].eventtype == eventtype &&
197 this->callbacks[i].userdata == userdata) {
198 this->callbacks.remove(i);
199 return;
200 }
201 }
202
203 #if COIN_DEBUG
204 SoDebugError::postWarning("SoEventCallback::removeEventCallback",
205 "tried to remove non-existent callback function");
206 #endif // COIN_DEBUG
207 }
208
209
210 /*!
211 Returns the SoHandleEventAction instance currently traversing the
212 scene graph with the SoEvent-derived event object.
213 */
214 SoHandleEventAction *
getAction(void) const215 SoEventCallback::getAction(void) const
216 {
217 return this->heaction;
218 }
219
220 /*!
221 Returns a pointer to the event object which is currently being sent
222 through the scenegraph.
223
224 If your application code handles the event, you probably want to
225 call SoEventCallback::setHandled() to notify the SoHandleEventAction
226 that it should stop traversing the scenegraph with the event.
227 */
228 const SoEvent *
getEvent(void) const229 SoEventCallback::getEvent(void) const
230 {
231 return (this->heaction ? this->heaction->getEvent() : NULL);
232 }
233
234 /*!
235 Returns the picked point for the current handle event traversal.
236
237 This is obviously only related to events which can be considered
238 "pick-style" events, like mousebutton presses.
239 */
240 const SoPickedPoint *
getPickedPoint(void) const241 SoEventCallback::getPickedPoint(void) const
242 {
243 return this->heaction ? this->heaction->getPickedPoint() : NULL;
244 }
245
246
247 /*!
248 Use this method from a callback function to notify the node that the
249 event has been handled.
250
251 The rest of the callbacks registered with the node will still be
252 called, but further SoEventCallback nodes in the scene will not be
253 notified about the event, neither will any other Coin elements in
254 the scenegraph (like for instance SoDragger objects, SoSelection
255 nodes or manipulators).
256
257 Since callbacks registered within the same SoEventCallback node will
258 still be invoked after the event has been handled, it is likely that
259 you should use SoEventCallback::isHandled() to check for this
260 condition from your callback functions.
261 */
262 void
setHandled(void)263 SoEventCallback::setHandled(void)
264 {
265 #if COIN_DEBUG
266 if (!this->heaction) {
267 SoDebugError::postWarning("SoEventCallback::setHandled",
268 "should only be called from event callbacks");
269 return;
270 }
271 #endif // COIN_DEBUG
272
273 this->heaction->setHandled();
274 }
275
276 /*!
277 Check whether or not the event from the SoHandleEventAction has been
278 handled.
279 */
280 SbBool
isHandled(void) const281 SoEventCallback::isHandled(void) const
282 {
283 #if COIN_DEBUG
284 if (!this->heaction) {
285 SoDebugError::postWarning("SoEventCallback::isHandled",
286 "should only be called from event callbacks");
287 return TRUE;
288 }
289 #endif // COIN_DEBUG
290
291 return this->heaction->isHandled();
292 }
293
294
295 /*!
296 Set up the node so all future events (until releaseEvents() is
297 called) in Coin will be directly forwarded to this node.
298 */
299 void
grabEvents(void)300 SoEventCallback::grabEvents(void)
301 {
302 #if COIN_DEBUG
303 if (!this->heaction) {
304 SoDebugError::postWarning("SoEventCallback::grabEvents",
305 "should only be called from event callbacks");
306 return;
307 }
308 #endif // COIN_DEBUG
309
310 this->heaction->setGrabber(this);
311 }
312
313 /*!
314 Do not grab event handling any more.
315
316 \sa grabEvents()
317 */
318 void
releaseEvents(void)319 SoEventCallback::releaseEvents(void)
320 {
321 #if COIN_DEBUG
322 if (!this->heaction) {
323 SoDebugError::postWarning("SoEventCallback::releaseEvents",
324 "should only be called from event callbacks");
325 return;
326 }
327 #endif // COIN_DEBUG
328
329 this->heaction->releaseGrabber();
330 }
331
332 /*!
333 Invokes the registered callback functions.
334 */
335 void
handleEvent(SoHandleEventAction * action)336 SoEventCallback::handleEvent(SoHandleEventAction * action)
337 {
338 // check if correct path is picked
339 if (this->path) {
340 const SoPickedPoint * pp = action->getPickedPoint();
341 if (!pp || !pp->getPath()->containsPath(this->path)) return;
342 }
343
344 // Make it possible to access the action object from the callback
345 // code.
346 SoHandleEventAction * tmphandle = this->heaction = action;
347
348 SoType eventtype = this->heaction->getEvent()->getTypeId();
349
350 // Invoke callbacks.
351 for (int i = 0; i < this->callbacks.getLength(); i++) {
352 if (eventtype.isDerivedFrom(this->callbacks[i].eventtype)) {
353 SoEventCallbackCB * cb = this->callbacks[i].func;
354 cb(this->callbacks[i].userdata, this);
355 }
356 }
357
358 // Reset.
359 //
360 // FIXME: there is a fundamental flaw with this technique; if a new
361 // SoHandleEventAction is applied to the scene graph while the
362 // callbacks above are still processing, a new pointer will
363 // overwrite the previous one (which is a problem that may cause
364 // hard-to-find errors), and then it will be overwritten with a NULL
365 // pointer below, which causes getAction(), getEvent() etc to return
366 // NULL values.
367 //
368 // The fundamental design flaw here, as I see it, is that the
369 // callback invocations should pass on the SoHandleEventAction
370 // pointer, instead of temporarily storing it as a member of this
371 // node. We can't really change this without breaking backward API
372 // compatibility, though.
373 //
374 // I've added some detection code below to at least catch and warn
375 // about the problem.
376 //
377 // 20041114 mortene.
378
379 if (tmphandle != this->heaction) {
380 SoDebugError::postWarning("SoEventCallback::handleEvent",
381 "detected additional SoHandleEventAction scene "
382 "graph traversal while still handling a previous "
383 "traversal -- this can cause hard-to-find bugs, "
384 "and should be avoided");
385 }
386
387 this->heaction = NULL;
388 }
389