Reset: guard against picks during re-init
[fg:hoorays-flightgear.git] / src / Input / FGMouseInput.cxx
1 // FGMouseInput.cxx -- handle user input from mouse devices
2 //
3 // Written by Torsten Dreyer, started August 2009
4 // Based on work from David Megginson, started May 2001.
5 //
6 // Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
7 // Copyright (C) 2001 David Megginson, david@megginson.com
8 //
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
13 //
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22 //
23 // $Id$
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #endif
28
29 #include "FGMouseInput.hxx"
30
31 #include <boost/foreach.hpp>
32 #include <osgGA/GUIEventAdapter>
33
34 #include <simgear/scene/util/SGPickCallback.hxx>
35 #include <simgear/timing/timestamp.hxx>
36 #include <simgear/scene/model/SGPickAnimation.hxx>
37
38 #include "FGButton.hxx"
39 #include "Main/globals.hxx"
40 #include <Viewer/renderer.hxx>
41 #include <plib/pu.h>
42 #include <Model/panelnode.hxx>
43 #include <Cockpit/panel.hxx>
44 #include <Viewer/FGEventHandler.hxx>
45 #include <GUI/MouseCursor.hxx>
46
47 using std::ios_base;
48
49 const int MAX_MICE = 1;
50 const int MAX_MOUSE_BUTTONS = 8;
51
52 typedef std::vector<SGSceneryPick> SGSceneryPicks;
53 typedef SGSharedPtr<SGPickCallback> SGPickCallbackPtr;
54 typedef std::list<SGPickCallbackPtr> SGPickCallbackList;
55
56 ////////////////////////////////////////////////////////////////////////
57
58 /**
59  * List of currently pressed mouse button events
60  */
61 class ActivePickCallbacks:
62   public std::map<int, SGPickCallbackList>
63 {
64   public:
65     void update( double dt, unsigned int keyModState );
66     void init( int button, const osgGA::GUIEventAdapter* ea );
67 };
68
69
70 void ActivePickCallbacks::init( int button, const osgGA::GUIEventAdapter* ea )
71 {
72   osg::Vec2d windowPos;
73   flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
74     
75   // Get the list of hit callbacks. Take the first callback that
76   // accepts the mouse button press and ignore the rest of them
77   // That is they get sorted by distance and by scenegraph depth.
78   // The nearest one is the first one and the deepest
79   // (the most specialized one in the scenegraph) is the first.
80   SGSceneryPicks pickList;
81   if (!globals->get_renderer()->pick(pickList, windowPos)) {
82     return;
83   }
84
85   SGSceneryPicks::const_iterator i;
86   for (i = pickList.begin(); i != pickList.end(); ++i) {
87     if (i->callback->buttonPressed(button, *ea, i->info)) {
88         (*this)[button].push_back(i->callback);
89         return;
90     }
91   }
92 }
93
94 void ActivePickCallbacks::update( double dt, unsigned int keyModState )
95 {
96   // handle repeatable mouse press events
97   for( iterator mi = begin(); mi != end(); ++mi ) {
98     SGPickCallbackList::iterator li;
99     for (li = mi->second.begin(); li != mi->second.end(); ++li) {
100       (*li)->update(dt, keyModState);
101     }
102   }
103 }
104
105 ////////////////////////////////////////////////////////////////////////
106
107
108 /**
109  * Settings for a mouse mode.
110  */
111 struct mouse_mode {
112     mouse_mode ();
113     virtual ~mouse_mode ();
114     FGMouseCursor::Cursor cursor;
115     bool constrained;
116     bool pass_through;
117     FGButton * buttons;
118     SGBindingList x_bindings[KEYMOD_MAX];
119     SGBindingList y_bindings[KEYMOD_MAX];
120 };
121
122
123 /**
124  * Settings for a mouse.
125  */
126 struct mouse {
127     mouse ();
128     virtual ~mouse ();
129     int x, y;
130     SGPropertyNode_ptr mode_node;
131     SGPropertyNode_ptr mouse_button_nodes[MAX_MOUSE_BUTTONS];
132     int nModes;
133     int current_mode;
134     
135     SGTimeStamp timeSinceLastMove;
136     mouse_mode * modes;
137 };
138
139 static
140 const SGSceneryPick*
141 getPick( const SGSceneryPicks& pick_list,
142          const SGPickCallback* cb )
143 {
144   for(size_t i = 0; i < pick_list.size(); ++i)
145     if( pick_list[i].callback == cb )
146       return &pick_list[i];
147
148   return 0;
149 }
150
151 ////////////////////////////////////////////////////////////////////////
152
153 class FGMouseInput::FGMouseInputPrivate : public SGPropertyChangeListener
154 {
155 public:
156     FGMouseInputPrivate() :
157         initialized(false),
158         haveWarped(false),
159         xSizeNode(fgGetNode("/sim/startup/xsize", false ) ),
160         ySizeNode(fgGetNode("/sim/startup/ysize", false ) ),
161         xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ),
162         yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ),
163         mouseXNode(fgGetNode("/devices/status/mice/mouse/x", true)),
164         mouseYNode(fgGetNode("/devices/status/mice/mouse/y", true))
165     {
166         tooltipTimeoutDone = false;
167         hoverPickScheduled = false;
168         tooltipsEnabled = false;
169         
170         fgGetNode("/sim/mouse/hide-cursor", true )->addChangeListener(this, true);
171         fgGetNode("/sim/mouse/cursor-timeout-sec", true )->addChangeListener(this, true);
172         fgGetNode("/sim/mouse/right-button-mode-cycle-enabled", true)->addChangeListener(this, true);
173         fgGetNode("/sim/mouse/tooltip-delay-msec", true)->addChangeListener(this, true);
174         fgGetNode("/sim/mouse/click-shows-tooltip", true)->addChangeListener(this, true);
175         fgGetNode("/sim/mouse/tooltips-enabled", true)->addChangeListener(this, true);
176         fgGetNode("/sim/mouse/drag-sensitivity", true)->addChangeListener(this, true);
177         fgGetNode("/sim/mouse/invert-mouse-wheel", true)->addChangeListener(this, true);
178     }
179   
180     void centerMouseCursor(mouse& m)
181     {    
182       // center the cursor
183       m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2;
184       m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2;
185       fgWarpMouse(m.x, m.y);
186       haveWarped = true;
187     }
188   
189     void constrainMouse(int x, int y)
190     {
191         int new_x=x,new_y=y;
192         int xsize = xSizeNode ? xSizeNode->getIntValue() : 800;
193         int ysize = ySizeNode ? ySizeNode->getIntValue() : 600;
194         
195         bool need_warp = false;
196         if (x <= (xsize * .25) || x >= (xsize * .75)) {
197           new_x = int(xsize * .5);
198           need_warp = true;
199         }
200
201         if (y <= (ysize * .25) || y >= (ysize * .75)) {
202           new_y = int(ysize * .5);
203           need_warp = true;
204         }
205
206         if (need_warp)
207         {
208           fgWarpMouse(new_x, new_y);
209           haveWarped = true;
210         }
211     }
212
213     void scheduleHoverPick(const osg::Vec2d& windowPos)
214     {
215       hoverPickScheduled = true;
216       hoverPos = windowPos;
217     }
218   
219     void doHoverPick(const osg::Vec2d& windowPos)
220     {
221         FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_ARROW;
222         bool explicitCursor = false;
223         bool didPick = false;
224
225         SGPickCallback::Priority priority = SGPickCallback::PriorityScenery;
226         SGSceneryPicks pickList;
227         globals->get_renderer()->pick(pickList, windowPos);
228
229         SGSceneryPicks::const_iterator i;
230         for( i = pickList.begin(); i != pickList.end(); ++i )
231         {
232             bool done = i->callback->hover(windowPos, i->info);
233             std::string curName(i->callback->getCursor());
234             if (!curName.empty()) {
235                 explicitCursor = true;
236                 cur = FGMouseCursor::cursorFromString(curName.c_str());
237             }
238             
239             // if the callback is of higher prioirty (lower enum index),
240             // record that.
241             if (i->callback->getPriority() < priority) {
242                 priority = i->callback->getPriority();
243             }
244
245             if (done) {
246                 didPick = true;
247                 break;
248             }
249         } // of picks iteration
250
251         // Check if any pick from the previous iteration has disappeared. If so
252         // notify the callback that the mouse has left its element.
253         for( i = _previous_picks.begin(); i != _previous_picks.end(); ++i )
254         {
255           if( !getPick(pickList, i->callback) )
256             i->callback->mouseLeave(windowPos);
257         }
258         _previous_picks = pickList;
259       
260         if (!explicitCursor && (priority == SGPickCallback::PriorityPanel)) {
261             cur = FGMouseCursor::CURSOR_HAND;
262         }
263         
264         FGMouseCursor::instance()->setCursor(cur);
265         if (!didPick) {
266           SGPropertyNode_ptr args(new SGPropertyNode);
267           globals->get_commands()->execute("update-hover", args);
268
269         }
270     }
271     
272     void doMouseMoveWithCallbacks(const osgGA::GUIEventAdapter* ea)
273     {
274         FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_CLOSED_HAND;
275         
276         osg::Vec2d windowPos;
277         flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
278
279         SGSceneryPicks pickList;
280         if( !globals->get_renderer()->pick(pickList, windowPos) )
281           return;
282
283         for( ActivePickCallbacks::iterator mi = activePickCallbacks.begin();
284                                            mi != activePickCallbacks.end();
285                                          ++mi )
286         {
287           SGPickCallbackList::iterator li;
288           for( li = mi->second.begin(); li != mi->second.end(); ++li )
289           {
290             const SGSceneryPick* pick = getPick(pickList, *li);
291             (*li)->mouseMoved(*ea, pick ? &pick->info : 0);
292
293             std::string curName((*li)->getCursor());
294             if( !curName.empty() )
295               cur = FGMouseCursor::cursorFromString(curName.c_str());
296           }
297         }
298
299         FGMouseCursor::instance()->setCursor(cur);
300     }
301
302     // implement the property-change-listener interfacee
303     virtual void valueChanged( SGPropertyNode * node )
304     {
305         if (node->getNameString() == "drag-sensitivity") {
306             SGKnobAnimation::setDragSensitivity(node->getDoubleValue());
307         } else if (node->getNameString() == "invert-mouse-wheel") {
308             SGKnobAnimation::setAlternateMouseWheelDirection(node->getBoolValue());
309         } else if (node->getNameString() == "hide-cursor") {
310             hideCursor = node->getBoolValue();
311         } else if (node->getNameString() == "cursor-timeout-sec") {
312             cursorTimeoutMsec = node->getDoubleValue() * 1000;
313         } else if (node->getNameString() == "tooltip-delay-msec") {
314             tooltipDelayMsec = node->getIntValue();
315         } else if (node->getNameString() == "right-button-mode-cycle-enabled") {
316             rightClickModeCycle = node->getBoolValue();
317         } else if (node->getNameString() == "click-shows-tooltip") {
318             clickTriggersTooltip = node->getBoolValue();
319         } else if (node->getNameString() == "tooltips-enabled") {
320             tooltipsEnabled = node->getBoolValue();
321         }
322     }
323     
324     ActivePickCallbacks activePickCallbacks;
325     SGSceneryPicks _previous_picks;
326
327     mouse mice[MAX_MICE];
328     
329     bool initialized;
330     bool hideCursor, haveWarped;
331     bool tooltipTimeoutDone;
332     bool clickTriggersTooltip;
333     int tooltipDelayMsec, cursorTimeoutMsec;
334     bool rightClickModeCycle;
335     bool tooltipsEnabled;
336     
337     SGPropertyNode_ptr xSizeNode;
338     SGPropertyNode_ptr ySizeNode;
339     SGPropertyNode_ptr xAccelNode;
340     SGPropertyNode_ptr yAccelNode;
341     SGPropertyNode_ptr mouseXNode, mouseYNode;
342   
343     bool hoverPickScheduled;
344     osg::Vec2d hoverPos;
345 };
346
347
348 ////////////////////////////////////////////////////////////////////////
349 // The Mouse Input Implementation
350 ////////////////////////////////////////////////////////////////////////
351
352 static FGMouseInput* global_mouseInput = NULL;
353
354 static void mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
355 {
356     if(global_mouseInput)
357         global_mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea);
358 }
359
360 static void mouseMotionHandler(int x, int y, const osgGA::GUIEventAdapter* ea)
361 {
362     if (global_mouseInput != 0)
363         global_mouseInput->doMouseMotion(x, y, ea);
364 }
365
366
367
368 FGMouseInput::FGMouseInput() :
369   d(new FGMouseInputPrivate)
370 {
371     global_mouseInput = this;
372 }
373
374 FGMouseInput::~FGMouseInput()
375 {
376     global_mouseInput = NULL;
377 }
378
379 void FGMouseInput::init()
380 {
381     if (d->initialized) {
382         SG_LOG(SG_INPUT, SG_WARN, "Duplicate init of FGMouseInput");
383
384         return;
385     }
386     
387     d->initialized = true;
388     
389   SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse bindings");
390   std::string module = "";
391
392   SGPropertyNode * mouse_nodes = fgGetNode("/input/mice");
393   if (mouse_nodes == 0) {
394     SG_LOG(SG_INPUT, SG_WARN, "No mouse bindings (/input/mice)!!");
395     mouse_nodes = fgGetNode("/input/mice", true);
396   }
397
398   int j;
399   for (int i = 0; i < MAX_MICE; i++) {
400     SGPropertyNode * mouse_node = mouse_nodes->getChild("mouse", i, true);
401     mouse &m = d->mice[i];
402
403                                 // Grab node pointers
404     std::ostringstream buf;
405     buf <<  "/devices/status/mice/mouse[" << i << "]/mode";
406     m.mode_node = fgGetNode(buf.str().c_str());
407     if (m.mode_node == NULL) {
408       m.mode_node = fgGetNode(buf.str().c_str(), true);
409       m.mode_node->setIntValue(0);
410     }
411     for (j = 0; j < MAX_MOUSE_BUTTONS; j++) {
412       buf.seekp(ios_base::beg);
413       buf << "/devices/status/mice/mouse["<< i << "]/button[" << j << "]";
414       m.mouse_button_nodes[j] = fgGetNode(buf.str().c_str(), true);
415       m.mouse_button_nodes[j]->setBoolValue(false);
416     }
417
418    // Read all the modes
419     m.nModes = mouse_node->getIntValue("mode-count", 1);
420     m.modes = new mouse_mode[m.nModes];
421
422     for (int j = 0; j < m.nModes; j++) {
423       int k;
424       SGPropertyNode * mode_node = mouse_node->getChild("mode", j, true);
425
426     // Read the mouse cursor for this mode
427       m.modes[j].cursor = FGMouseCursor::cursorFromString(mode_node->getStringValue("cursor", "inherit"));
428         
429       // Read other properties for this mode
430       m.modes[j].constrained = mode_node->getBoolValue("constrained", false);
431       m.modes[j].pass_through = mode_node->getBoolValue("pass-through", false);
432
433       // Read the button bindings for this mode
434       m.modes[j].buttons = new FGButton[MAX_MOUSE_BUTTONS];
435       std::ostringstream buf;
436       for (k = 0; k < MAX_MOUSE_BUTTONS; k++) {
437         buf.seekp(ios_base::beg);
438         buf << "mouse button " << k;
439         m.modes[j].buttons[k].init( mode_node->getChild("button", k), buf.str(), module );
440       }
441
442       // Read the axis bindings for this mode
443       read_bindings(mode_node->getChild("x-axis", 0, true), m.modes[j].x_bindings, KEYMOD_NONE, module );
444       read_bindings(mode_node->getChild("y-axis", 0, true), m.modes[j].y_bindings, KEYMOD_NONE, module );
445       
446       if (mode_node->hasChild("x-axis-ctrl")) {
447         read_bindings(mode_node->getChild("x-axis-ctrl"), m.modes[j].x_bindings, KEYMOD_CTRL, module );
448       }
449       if (mode_node->hasChild("x-axis-shift")) {
450         read_bindings(mode_node->getChild("x-axis-shift"), m.modes[j].x_bindings, KEYMOD_SHIFT, module );
451       }
452       if (mode_node->hasChild("x-axis-ctrl-shift")) {
453         read_bindings(mode_node->getChild("x-axis-ctrl-shift"), m.modes[j].x_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
454       }
455       
456       if (mode_node->hasChild("y-axis-ctrl")) {
457         read_bindings(mode_node->getChild("y-axis-ctrl"), m.modes[j].y_bindings, KEYMOD_CTRL, module );
458       }
459       if (mode_node->hasChild("y-axis-shift")) {
460         read_bindings(mode_node->getChild("y-axis-shift"), m.modes[j].y_bindings, KEYMOD_SHIFT, module );
461       }
462       if (mode_node->hasChild("y-axis-ctrl-shift")) {
463         read_bindings(mode_node->getChild("y-axis-ctrl-shift"), m.modes[j].y_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
464       }
465     } // of modes iteration
466   }
467
468   fgRegisterMouseClickHandler(mouseClickHandler);
469   fgRegisterMouseMotionHandler(mouseMotionHandler);
470 }
471
472 void FGMouseInput::update ( double dt )
473 {
474     if (!d->initialized) {
475         SG_LOG(SG_INPUT, SG_WARN, "update of mouse before init");
476     }
477     
478   mouse &m = d->mice[0];
479   int mode =  m.mode_node->getIntValue();
480   if (mode != m.current_mode) {
481     // current mode has changed
482     m.current_mode = mode;
483     m.timeSinceLastMove.stamp();
484       
485     if (mode >= 0 && mode < m.nModes) {
486       FGMouseCursor::instance()->setCursor(m.modes[mode].cursor);
487       d->centerMouseCursor(m);
488     } else {
489       SG_LOG(SG_INPUT, SG_WARN, "Mouse mode " << mode << " out of range");
490       FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_ARROW);
491     }
492   }
493
494   if ((mode == 0) && d->hoverPickScheduled) {
495     d->doHoverPick(d->hoverPos);
496     d->hoverPickScheduled = false;
497   }
498   
499   if ( !d->tooltipTimeoutDone &&
500       d->tooltipsEnabled &&
501       (m.timeSinceLastMove.elapsedMSec() > d->tooltipDelayMsec))
502   {
503       d->tooltipTimeoutDone = true;
504       SGPropertyNode_ptr arg(new SGPropertyNode);
505       globals->get_commands()->execute("tooltip-timeout", arg);
506   }
507   
508   if ( d->hideCursor ) {
509       if ( m.timeSinceLastMove.elapsedMSec() > d->cursorTimeoutMsec) {
510           FGMouseCursor::instance()->hideCursorUntilMouseMove();
511           m.timeSinceLastMove.stamp();
512       }
513   }
514     
515   d->activePickCallbacks.update( dt, fgGetKeyModifiers() );
516 }
517
518 mouse::mouse ()
519   : x(-1),
520     y(-1),
521     nModes(1),
522     current_mode(0),
523     modes(NULL)
524 {
525 }
526
527 mouse::~mouse ()
528 {
529   delete [] modes;
530 }
531
532 mouse_mode::mouse_mode ()
533   : cursor(FGMouseCursor::CURSOR_ARROW),
534     constrained(false),
535     pass_through(false),
536     buttons(NULL)
537 {
538 }
539
540 mouse_mode::~mouse_mode ()
541 {
542                                 // FIXME: memory leak
543 //   for (int i = 0; i < KEYMOD_MAX; i++) {
544 //     int j;
545 //     for (j = 0; i < x_bindings[i].size(); j++)
546 //       delete bindings[i][j];
547 //     for (j = 0; j < y_bindings[i].size(); j++)
548 //       delete bindings[i][j];
549 //   }
550   if (buttons) {
551     delete [] buttons;
552   }
553 }
554
555 void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
556 {
557     if (!d->initialized) {
558         // can occur during reset
559         return;
560     }
561     
562   int modifiers = fgGetKeyModifiers();
563
564   mouse &m = d->mice[0];
565   mouse_mode &mode = m.modes[m.current_mode];
566                                 // Let the property manager know.
567   if (b >= 0 && b < MAX_MOUSE_BUTTONS)
568     m.mouse_button_nodes[b]->setBoolValue(updown == MOUSE_BUTTON_DOWN);
569
570   if (!d->rightClickModeCycle && (b == 2)) {
571     // in spring-loaded look mode, ignore right clicks entirely here
572     return;
573   }
574   
575   // Pass on to PUI and the panel if
576   // requested, and return if one of
577   // them consumes the event.
578
579   osg::Vec2d windowPos;
580   flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
581
582   SGSceneryPicks pickList;
583   globals->get_renderer()->pick(pickList, windowPos);
584
585   if( updown != MOUSE_BUTTON_DOWN )
586   {
587     // Execute the mouse up event in any case, may be we should
588     // stop processing here?
589
590     SGPickCallbackList& callbacks = d->activePickCallbacks[b];
591
592     while( !callbacks.empty() )
593     {
594       SGPickCallbackPtr& cb = callbacks.front();
595       const SGSceneryPick* pick = getPick(pickList, cb);
596       cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : 0);
597
598       callbacks.pop_front();
599     }
600   }
601
602   if (mode.pass_through) {
603     // remove once PUI uses standard picking mechanism
604     if (0 <= x && 0 <= y && puMouse(b, updown, x, y))
605       return; // pui handled it
606
607     // pui didn't want the click event so compute a
608     // scenegraph intersection point corresponding to the mouse click
609     if (updown == MOUSE_BUTTON_DOWN) {
610       d->activePickCallbacks.init( b, ea );
611         
612       if (d->clickTriggersTooltip) {
613             SGPropertyNode_ptr args(new SGPropertyNode);
614             args->setStringValue("reason", "click");
615             globals->get_commands()->execute("tooltip-timeout", args);
616             d->tooltipTimeoutDone = true;
617       }
618     } else {
619       // do a hover pick now, to fix up cursor
620       d->doHoverPick(windowPos);
621     } // mouse button was released
622   } // of pass-through mode
623
624   // OK, PUI and the panel didn't want the click
625   if (b >= MAX_MOUSE_BUTTONS) {
626     SG_LOG(SG_INPUT, SG_ALERT, "Mouse button " << b
627            << " where only " << MAX_MOUSE_BUTTONS << " expected");
628     return;
629   }
630
631   m.modes[m.current_mode].buttons[b].update( modifiers, 0 != updown, x, y);  
632 }
633
634 void FGMouseInput::processMotion(int x, int y, const osgGA::GUIEventAdapter* ea)
635 {
636   if (!d->activePickCallbacks[0].empty()) {
637     d->doMouseMoveWithCallbacks(ea);
638     return;
639   }
640   
641   mouse &m = d->mice[0];
642   int modeIndex = m.current_mode;
643   // are we in spring-loaded look mode?
644   if (!d->rightClickModeCycle) {
645     if (m.mouse_button_nodes[2]->getBoolValue()) {
646       // right mouse is down, force look mode
647       modeIndex = 3;
648     }
649   }
650
651   if (modeIndex == 0) {
652     osg::Vec2d windowPos;
653     flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
654     d->scheduleHoverPick(windowPos);
655     // mouse has moved, so we may need to issue tooltip-timeout command again
656     d->tooltipTimeoutDone = false;
657   }
658   
659   mouse_mode &mode = m.modes[modeIndex];
660   
661   // Pass on to PUI if requested, and return
662   // if PUI consumed the event.
663   if (mode.pass_through && puMouse(x, y)) {
664     return;
665   }
666
667   if (d->haveWarped)
668   {
669     // don't fire mouse-movement events at the first update after warping the mouse,
670     // just remember the new mouse position
671     d->haveWarped = false;
672   }
673   else
674   {
675     int modifiers = fgGetKeyModifiers();
676     int xsize = d->xSizeNode ? d->xSizeNode->getIntValue() : 800;
677     int ysize = d->ySizeNode ? d->ySizeNode->getIntValue() : 600;
678       
679     // OK, PUI didn't want the event,
680     // so we can play with it.
681     if (x != m.x) {
682       int delta = x - m.x;
683       d->xAccelNode->setIntValue( delta );
684       for (unsigned int i = 0; i < mode.x_bindings[modifiers].size(); i++)
685         mode.x_bindings[modifiers][i]->fire(double(delta), double(xsize));
686     }
687     if (y != m.y) {
688       int delta = y - m.y;
689       d->yAccelNode->setIntValue( -delta );
690       for (unsigned int i = 0; i < mode.y_bindings[modifiers].size(); i++)
691         mode.y_bindings[modifiers][i]->fire(double(delta), double(ysize));
692     }
693   }
694   
695   // Constrain the mouse if requested
696   if (mode.constrained) {
697     d->constrainMouse(x, y);
698   }
699 }
700
701 void FGMouseInput::doMouseMotion (int x, int y, const osgGA::GUIEventAdapter* ea)
702 {
703     if (!d->initialized) {
704         // can occur during reset
705         return;
706     }
707     
708   mouse &m = d->mice[0];
709
710   if (m.current_mode < 0 || m.current_mode >= m.nModes) {
711       m.x = x;
712       m.y = y;
713       return;
714   }
715
716   m.timeSinceLastMove.stamp();
717   FGMouseCursor::instance()->mouseMoved();
718
719   // TODO Get rid of this as soon as soon as cursor hide timeout works globally
720   if( ea->getHandled() )
721     return;
722
723   processMotion(x, y, ea);
724     
725   m.x = x;
726   m.y = y;
727   d->mouseXNode->setIntValue(x);
728   d->mouseYNode->setIntValue(y);
729 }
730
731
732