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