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 <memory>
30 
31 #include "FGMouseInput.hxx"
32 
33 #include <osgGA/GUIEventAdapter>
34 
35 #include <simgear/scene/util/SGPickCallback.hxx>
36 #include <simgear/timing/timestamp.hxx>
37 #include <simgear/scene/model/SGPickAnimation.hxx>
38 
39 #include "FGButton.hxx"
40 #include "Main/globals.hxx"
41 #include <Viewer/renderer.hxx>
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 
init(int button,const osgGA::GUIEventAdapter * ea)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 = globals->get_renderer()->pick(windowPos);
81   if (pickList.empty()) {
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 
update(double dt,unsigned int keyModState)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 
114     FGMouseCursor::Cursor cursor;
115     bool constrained;
116     bool pass_through;
117     std::unique_ptr<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 
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     std::unique_ptr<mouse_mode[]> modes;
137 };
138 
139 static
140 const SGSceneryPick*
getPick(const SGSceneryPicks & pick_list,const SGPickCallback * cb)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:
FGMouseInputPrivate()156     FGMouseInputPrivate() :
157         haveWarped(false),
158         xSizeNode(fgGetNode("/sim/startup/xsize", false ) ),
159         ySizeNode(fgGetNode("/sim/startup/ysize", false ) ),
160         xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ),
161         yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ),
162         mouseXNode(fgGetNode("/devices/status/mice/mouse/x", true)),
163         mouseYNode(fgGetNode("/devices/status/mice/mouse/y", true))
164     {
165         tooltipTimeoutDone = false;
166         hoverPickScheduled = false;
167         tooltipsEnabled = false;
168 
169         fgGetNode("/sim/mouse/hide-cursor", true )->addChangeListener(this, true);
170         fgGetNode("/sim/mouse/cursor-timeout-sec", true )->addChangeListener(this, true);
171         fgGetNode("/sim/mouse/right-button-mode-cycle-enabled", true)->addChangeListener(this, true);
172         fgGetNode("/sim/mouse/tooltip-delay-msec", true)->addChangeListener(this, true);
173         fgGetNode("/sim/mouse/click-shows-tooltip", true)->addChangeListener(this, true);
174         fgGetNode("/sim/mouse/tooltips-enabled", true)->addChangeListener(this, true);
175         fgGetNode("/sim/mouse/drag-sensitivity", true)->addChangeListener(this, true);
176         fgGetNode("/sim/mouse/invert-mouse-wheel", true)->addChangeListener(this, true);
177     }
178 
centerMouseCursor(mouse & m)179     void centerMouseCursor(mouse& m)
180     {
181       // center the cursor
182       m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2;
183       m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2;
184       fgWarpMouse(m.x, m.y);
185       haveWarped = true;
186     }
187 
constrainMouse(int x,int y)188     void constrainMouse(int x, int y)
189     {
190         int new_x=x,new_y=y;
191         int xsize = xSizeNode ? xSizeNode->getIntValue() : 800;
192         int ysize = ySizeNode ? ySizeNode->getIntValue() : 600;
193 
194         bool need_warp = false;
195         if (x <= (xsize * .25) || x >= (xsize * .75)) {
196           new_x = int(xsize * .5);
197           need_warp = true;
198         }
199 
200         if (y <= (ysize * .25) || y >= (ysize * .75)) {
201           new_y = int(ysize * .5);
202           need_warp = true;
203         }
204 
205         if (need_warp)
206         {
207           fgWarpMouse(new_x, new_y);
208           haveWarped = true;
209         }
210     }
211 
scheduleHoverPick(const osg::Vec2d & windowPos)212     void scheduleHoverPick(const osg::Vec2d& windowPos)
213     {
214       hoverPickScheduled = true;
215       hoverPos = windowPos;
216     }
217 
doHoverPick(const osg::Vec2d & windowPos)218     void doHoverPick(const osg::Vec2d& windowPos)
219     {
220         FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_ARROW;
221         bool explicitCursor = false;
222         bool didPick = false;
223 
224         SGPickCallback::Priority priority = SGPickCallback::PriorityScenery;
225         SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
226 
227         SGSceneryPicks::const_iterator i;
228         for( i = pickList.begin(); i != pickList.end(); ++i )
229         {
230             bool done = i->callback->hover(windowPos, i->info);
231             std::string curName(i->callback->getCursor());
232             if (!curName.empty()) {
233                 explicitCursor = true;
234                 cur = FGMouseCursor::cursorFromString(curName.c_str());
235             }
236 
237             // if the callback is of higher prioirty (lower enum index),
238             // record that.
239             if (i->callback->getPriority() < priority) {
240                 priority = i->callback->getPriority();
241             }
242 
243             if (done) {
244                 didPick = true;
245                 break;
246             }
247         } // of picks iteration
248 
249         // Check if any pick from the previous iteration has disappeared. If so
250         // notify the callback that the mouse has left its element.
251         for( i = _previous_picks.begin(); i != _previous_picks.end(); ++i )
252         {
253           if( !getPick(pickList, i->callback) )
254             i->callback->mouseLeave(windowPos);
255         }
256         _previous_picks = pickList;
257 
258         if (!explicitCursor && (priority == SGPickCallback::PriorityPanel)) {
259             cur = FGMouseCursor::CURSOR_HAND;
260         }
261 
262         FGMouseCursor::instance()->setCursor(cur);
263         if (!didPick) {
264           SGPropertyNode_ptr args(new SGPropertyNode);
265           globals->get_commands()->execute("update-hover", args, nullptr);
266 
267         }
268     }
269 
doMouseMoveWithCallbacks(const osgGA::GUIEventAdapter * ea)270     void doMouseMoveWithCallbacks(const osgGA::GUIEventAdapter* ea)
271     {
272         FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_CLOSED_HAND;
273 
274         osg::Vec2d windowPos;
275         const bool ok = flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
276         if (!ok) {
277             SG_LOG(SG_GUI, SG_WARN, "doMouseMoveWithCallbacks: ignoring mouse move with missing context/traits");
278             return;
279         }
280 
281         SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
282         if(pickList.empty())
283           return;
284 
285         for( ActivePickCallbacks::iterator mi = activePickCallbacks.begin();
286                                            mi != activePickCallbacks.end();
287                                          ++mi )
288         {
289           SGPickCallbackList::iterator li;
290           for( li = mi->second.begin(); li != mi->second.end(); ++li )
291           {
292             const SGSceneryPick* pick = getPick(pickList, *li);
293             (*li)->mouseMoved(*ea, pick ? &pick->info : 0);
294 
295             std::string curName((*li)->getCursor());
296             if( !curName.empty() )
297               cur = FGMouseCursor::cursorFromString(curName.c_str());
298           }
299         }
300 
301         FGMouseCursor::instance()->setCursor(cur);
302     }
303 
304     // implement the property-change-listener interfacee
valueChanged(SGPropertyNode * node)305     virtual void valueChanged( SGPropertyNode * node )
306     {
307         if (node->getNameString() == "drag-sensitivity") {
308             SGKnobAnimation::setDragSensitivity(node->getDoubleValue());
309         } else if (node->getNameString() == "invert-mouse-wheel") {
310             SGKnobAnimation::setAlternateMouseWheelDirection(node->getBoolValue());
311         } else if (node->getNameString() == "hide-cursor") {
312             hideCursor = node->getBoolValue();
313         } else if (node->getNameString() == "cursor-timeout-sec") {
314             cursorTimeoutMsec = node->getDoubleValue() * 1000;
315         } else if (node->getNameString() == "tooltip-delay-msec") {
316             tooltipDelayMsec = node->getIntValue();
317         } else if (node->getNameString() == "right-button-mode-cycle-enabled") {
318             rightClickModeCycle = node->getBoolValue();
319         } else if (node->getNameString() == "click-shows-tooltip") {
320             clickTriggersTooltip = node->getBoolValue();
321         } else if (node->getNameString() == "tooltips-enabled") {
322             tooltipsEnabled = node->getBoolValue();
323         }
324     }
325 
326     ActivePickCallbacks activePickCallbacks;
327     SGSceneryPicks _previous_picks;
328 
329     mouse mice[MAX_MICE];
330 
331     bool hideCursor, haveWarped;
332     bool tooltipTimeoutDone;
333     bool clickTriggersTooltip;
334     int tooltipDelayMsec, cursorTimeoutMsec;
335     bool rightClickModeCycle;
336     bool tooltipsEnabled;
337 
338     SGPropertyNode_ptr xSizeNode;
339     SGPropertyNode_ptr ySizeNode;
340     SGPropertyNode_ptr xAccelNode;
341     SGPropertyNode_ptr yAccelNode;
342     SGPropertyNode_ptr mouseXNode, mouseYNode;
343 
344     bool hoverPickScheduled;
345     osg::Vec2d hoverPos;
346 };
347 
348 
349 ////////////////////////////////////////////////////////////////////////
350 // The Mouse Input Implementation
351 ////////////////////////////////////////////////////////////////////////
352 
353 static FGMouseInput* global_mouseInput = nullptr;
354 
mouseClickHandler(int button,int updown,int x,int y,bool mainWindow,const osgGA::GUIEventAdapter * ea)355 static void mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
356 {
357     if(global_mouseInput)
358         global_mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea);
359 }
360 
mouseMotionHandler(int x,int y,const osgGA::GUIEventAdapter * ea)361 static void mouseMotionHandler(int x, int y, const osgGA::GUIEventAdapter* ea)
362 {
363     if (global_mouseInput != 0)
364         global_mouseInput->doMouseMotion(x, y, ea);
365 }
366 
367 FGMouseInput::FGMouseInput() = default;
368 
init()369 void FGMouseInput::init()
370 {
371   SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse bindings");
372 
373   d.reset(new FGMouseInputPrivate());
374   std::string module = "";
375 
376   SGPropertyNode * mouse_nodes = fgGetNode("/input/mice");
377   if (mouse_nodes == 0) {
378     SG_LOG(SG_INPUT, SG_WARN, "No mouse bindings (/input/mice)!!");
379     mouse_nodes = fgGetNode("/input/mice", true);
380   }
381 
382   int j;
383   for (int i = 0; i < MAX_MICE; i++) {
384     SGPropertyNode * mouse_node = mouse_nodes->getChild("mouse", i, true);
385     mouse &m = d->mice[i];
386 
387                                 // Grab node pointers
388     std::ostringstream buf;
389     buf <<  "/devices/status/mice/mouse[" << i << "]/mode";
390     m.mode_node = fgGetNode(buf.str().c_str());
391     if (m.mode_node == NULL) {
392       m.mode_node = fgGetNode(buf.str().c_str(), true);
393       m.mode_node->setIntValue(0);
394     }
395     for (j = 0; j < MAX_MOUSE_BUTTONS; j++) {
396       // According to <https://stackoverflow.com/a/12112642/4756009> and other
397       // similar questions on stackoverflow.com, it seems safer not to try to
398       // reuse the 'buf' variable we have above.
399       std::ostringstream buf;
400       buf << "/devices/status/mice/mouse["<< i << "]/button[" << j << "]";
401       m.mouse_button_nodes[j] = fgGetNode(buf.str().c_str(), true);
402       m.mouse_button_nodes[j]->setBoolValue(false);
403     }
404 
405    // Read all the modes
406     m.nModes = mouse_node->getIntValue("mode-count", 1);
407     m.modes.reset(new mouse_mode[m.nModes]);
408 
409     for (int j = 0; j < m.nModes; j++) {
410       int k;
411       SGPropertyNode * mode_node = mouse_node->getChild("mode", j, true);
412 
413     // Read the mouse cursor for this mode
414       m.modes[j].cursor = FGMouseCursor::cursorFromString(mode_node->getStringValue("cursor", "inherit"));
415 
416       // Read other properties for this mode
417       m.modes[j].constrained = mode_node->getBoolValue("constrained", false);
418       m.modes[j].pass_through = mode_node->getBoolValue("pass-through", false);
419 
420       // Read the button bindings for this mode
421       m.modes[j].buttons.reset(new FGButton[MAX_MOUSE_BUTTONS]);
422       for (k = 0; k < MAX_MOUSE_BUTTONS; k++) {
423         std::ostringstream buf;
424         buf << "mouse button " << k;
425         m.modes[j].buttons[k].init( mode_node->getChild("button", k), buf.str(), module );
426       }
427 
428       // Read the axis bindings for this mode
429       read_bindings(mode_node->getChild("x-axis", 0, true), m.modes[j].x_bindings, KEYMOD_NONE, module );
430       read_bindings(mode_node->getChild("y-axis", 0, true), m.modes[j].y_bindings, KEYMOD_NONE, module );
431 
432       if (mode_node->hasChild("x-axis-ctrl")) {
433         read_bindings(mode_node->getChild("x-axis-ctrl"), m.modes[j].x_bindings, KEYMOD_CTRL, module );
434       }
435       if (mode_node->hasChild("x-axis-shift")) {
436         read_bindings(mode_node->getChild("x-axis-shift"), m.modes[j].x_bindings, KEYMOD_SHIFT, module );
437       }
438       if (mode_node->hasChild("x-axis-ctrl-shift")) {
439         read_bindings(mode_node->getChild("x-axis-ctrl-shift"), m.modes[j].x_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
440       }
441 
442       if (mode_node->hasChild("y-axis-ctrl")) {
443         read_bindings(mode_node->getChild("y-axis-ctrl"), m.modes[j].y_bindings, KEYMOD_CTRL, module );
444       }
445       if (mode_node->hasChild("y-axis-shift")) {
446         read_bindings(mode_node->getChild("y-axis-shift"), m.modes[j].y_bindings, KEYMOD_SHIFT, module );
447       }
448       if (mode_node->hasChild("y-axis-ctrl-shift")) {
449         read_bindings(mode_node->getChild("y-axis-ctrl-shift"), m.modes[j].y_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
450       }
451     } // of modes iteration
452   }
453 
454   fgRegisterMouseClickHandler(mouseClickHandler);
455   fgRegisterMouseMotionHandler(mouseMotionHandler);
456   global_mouseInput = this;
457 }
458 
shutdown()459 void FGMouseInput::shutdown()
460 {
461   SG_LOG(SG_INPUT, SG_DEBUG, "Shutting down mouse bindings");
462 
463   // This ensures that mouseClickHandler and mouseMotionHandler are no-ops.
464   global_mouseInput = nullptr;
465   // Reset the Pimpl
466   d.reset();
467 }
468 
reinit()469 void FGMouseInput::reinit()
470 {
471   shutdown();
472   init();
473 }
474 
update(double dt)475 void FGMouseInput::update ( double dt )
476 {
477     if (!d) {
478         SG_LOG(SG_INPUT, SG_WARN, "update of mouse before init");
479     }
480 
481   mouse &m = d->mice[0];
482   int mode =  m.mode_node->getIntValue();
483   if (mode != m.current_mode) {
484     // current mode has changed
485     m.current_mode = mode;
486     m.timeSinceLastMove.stamp();
487 
488     if (mode >= 0 && mode < m.nModes) {
489       FGMouseCursor::instance()->setCursor(m.modes[mode].cursor);
490       d->centerMouseCursor(m);
491     } else {
492       SG_LOG(SG_INPUT, SG_WARN, "Mouse mode " << mode << " out of range");
493       FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_ARROW);
494     }
495   }
496 
497   if ((mode == 0) && d->hoverPickScheduled) {
498     d->doHoverPick(d->hoverPos);
499     d->hoverPickScheduled = false;
500   }
501 
502   if ( !d->tooltipTimeoutDone &&
503       d->tooltipsEnabled &&
504       (m.timeSinceLastMove.elapsedMSec() > d->tooltipDelayMsec))
505   {
506       d->tooltipTimeoutDone = true;
507       SGPropertyNode_ptr arg(new SGPropertyNode);
508       globals->get_commands()->execute("tooltip-timeout", arg, nullptr);
509   }
510 
511   if ( d->hideCursor ) {
512       if ( m.timeSinceLastMove.elapsedMSec() > d->cursorTimeoutMsec) {
513           FGMouseCursor::instance()->hideCursorUntilMouseMove();
514           m.timeSinceLastMove.stamp();
515       }
516   }
517 
518   d->activePickCallbacks.update( dt, fgGetKeyModifiers() );
519 }
520 
mouse()521 mouse::mouse ()
522   : x(-1),
523     y(-1),
524     nModes(1),
525     current_mode(0),
526     modes()
527 {
528 }
529 
mouse_mode()530 mouse_mode::mouse_mode ()
531   : cursor(FGMouseCursor::CURSOR_ARROW),
532     constrained(false),
533     pass_through(false),
534     buttons()
535 {
536 }
537 
doMouseClick(int b,int updown,int x,int y,bool mainWindow,const osgGA::GUIEventAdapter * ea)538 void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
539 {
540     if (!d) {
541         // can occur during reset
542         return;
543     }
544 
545   int modifiers = fgGetKeyModifiers();
546 
547   mouse &m = d->mice[0];
548   mouse_mode &mode = m.modes[m.current_mode];
549                                 // Let the property manager know.
550   if (b >= 0 && b < MAX_MOUSE_BUTTONS)
551     m.mouse_button_nodes[b]->setBoolValue(updown == MOUSE_BUTTON_DOWN);
552 
553   if (!d->rightClickModeCycle && (b == 2)) {
554     // in spring-loaded look mode, ignore right clicks entirely here
555     return;
556   }
557 
558 
559   // Pass on to PUI and the panel if
560   // requested, and return if one of
561   // them consumes the event.
562 
563   osg::Vec2d windowPos;
564   bool ok = flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
565   if (!ok) {
566       SG_LOG(SG_GUI, SG_WARN, "Ignoring mouse click with null context/traits");
567       return;
568   }
569 
570   SGSceneryPicks pickList;
571 
572   if (isRightDragLookActive() && (updown == MOUSE_BUTTON_DOWN)) {
573       // when spring-loaded mode is active, don't do scene selection for picks
574       // https://sourceforge.net/p/flightgear/codetickets/2108/
575   } else {
576       pickList = globals->get_renderer()->pick(windowPos);
577   }
578 
579   if( updown == MOUSE_BUTTON_UP )
580   {
581     // Execute the mouse up event in any case, may be we should
582     // stop processing here?
583 
584     SGPickCallbackList& callbacks = d->activePickCallbacks[b];
585 
586     while( !callbacks.empty() )
587     {
588       SGPickCallbackPtr& cb = callbacks.front();
589       const SGSceneryPick* pick = getPick(pickList, cb);
590       cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : nullptr);
591 
592       callbacks.pop_front();
593     }
594 
595     if (ea->getHandled()) {
596         // for https://sourceforge.net/p/flightgear/codetickets/2347/
597         // we cleared the active picks, but don't do further processing
598         return;
599     }
600   }
601 
602   if (mode.pass_through) {
603     // compute a
604     // scenegraph intersection point corresponding to the mouse click
605     if (updown == MOUSE_BUTTON_DOWN) {
606       d->activePickCallbacks.init( b, ea );
607 
608       if (d->clickTriggersTooltip) {
609             SGPropertyNode_ptr args(new SGPropertyNode);
610             args->setStringValue("reason", "click");
611             globals->get_commands()->execute("tooltip-timeout", args, nullptr);
612             d->tooltipTimeoutDone = true;
613       }
614     } else {
615       // do a hover pick now, to fix up cursor
616       d->doHoverPick(windowPos);
617     } // mouse button was released
618   } // of pass-through mode
619 
620   if (b >= MAX_MOUSE_BUTTONS) {
621     SG_LOG(SG_INPUT, SG_ALERT, "Mouse button " << b
622            << " where only " << MAX_MOUSE_BUTTONS << " expected");
623     return;
624   }
625 
626   m.modes[m.current_mode].buttons[b].update( modifiers, 0 != updown, x, y);
627 }
628 
processMotion(int x,int y,const osgGA::GUIEventAdapter * ea)629 void FGMouseInput::processMotion(int x, int y, const osgGA::GUIEventAdapter* ea)
630 {
631   if (!d->activePickCallbacks[0].empty()) {
632     d->doMouseMoveWithCallbacks(ea);
633     return;
634   }
635 
636   mouse &m = d->mice[0];
637   int modeIndex = m.current_mode;
638 
639   if (isRightDragLookActive()) {
640       // right mouse is down, force look mode
641       modeIndex = 3;
642   }
643 
644   if (modeIndex == 0) {
645     osg::Vec2d windowPos;
646     flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
647     d->scheduleHoverPick(windowPos);
648     // mouse has moved, so we may need to issue tooltip-timeout command again
649     d->tooltipTimeoutDone = false;
650   }
651 
652   mouse_mode &mode = m.modes[modeIndex];
653 
654   if (d->haveWarped)
655   {
656     // don't fire mouse-movement events at the first update after warping the mouse,
657     // just remember the new mouse position
658     d->haveWarped = false;
659   }
660   else
661   {
662     int modifiers = fgGetKeyModifiers();
663     int xsize = d->xSizeNode ? d->xSizeNode->getIntValue() : 800;
664     int ysize = d->ySizeNode ? d->ySizeNode->getIntValue() : 600;
665 
666     // OK, PUI didn't want the event,
667     // so we can play with it.
668     if (x != m.x) {
669       int delta = x - m.x;
670       d->xAccelNode->setIntValue( delta );
671       for (unsigned int i = 0; i < mode.x_bindings[modifiers].size(); i++)
672         mode.x_bindings[modifiers][i]->fire(double(delta), double(xsize));
673     }
674     if (y != m.y) {
675       int delta = y - m.y;
676       d->yAccelNode->setIntValue( -delta );
677       for (unsigned int i = 0; i < mode.y_bindings[modifiers].size(); i++)
678         mode.y_bindings[modifiers][i]->fire(double(delta), double(ysize));
679     }
680   }
681 
682   // Constrain the mouse if requested
683   if (mode.constrained) {
684     d->constrainMouse(x, y);
685   }
686 }
687 
doMouseMotion(int x,int y,const osgGA::GUIEventAdapter * ea)688 void FGMouseInput::doMouseMotion (int x, int y, const osgGA::GUIEventAdapter* ea)
689 {
690     if (!d) {
691         // can occur during reset
692         return;
693     }
694 
695   mouse &m = d->mice[0];
696 
697   if (m.current_mode < 0 || m.current_mode >= m.nModes) {
698       m.x = x;
699       m.y = y;
700       return;
701   }
702 
703   m.timeSinceLastMove.stamp();
704   FGMouseCursor::instance()->mouseMoved();
705 
706   // TODO Get rid of this as soon as soon as cursor hide timeout works globally
707 
708     if (!ea->getHandled()) {
709         processMotion(x, y, ea);
710     }
711 
712   m.x = x;
713   m.y = y;
714   d->mouseXNode->setIntValue(x);
715   d->mouseYNode->setIntValue(y);
716 }
717 
isRightDragToLookEnabled() const718 bool FGMouseInput::isRightDragToLookEnabled() const
719 {
720     if (!d) {
721         return false;
722     }
723 
724     return (d->rightClickModeCycle == false);
725 }
726 
isActiveModePassThrough() const727 bool FGMouseInput::isActiveModePassThrough() const
728 {
729     if (!d) {
730         return false;
731     }
732 
733     mouse &m = d->mice[0];
734     int mode = m.current_mode;
735     if (isRightDragToLookEnabled() && m.mouse_button_nodes[2]->getBoolValue()) {
736         mode = 3;
737     }
738 
739     return m.modes[mode].pass_through;
740 }
741 
isRightDragLookActive() const742 bool FGMouseInput::isRightDragLookActive() const
743 {
744     if (!d) {
745         return false;
746     }
747 
748     const auto& m = d->mice[0];
749     if (!d->rightClickModeCycle && m.nModes > 3) {
750         return m.mouse_button_nodes[2]->getBoolValue();
751     }
752 
753     return false;
754 }
755 
756 
757 // Register the subsystem.
758 SGSubsystemMgr::Registrant<FGMouseInput> registrantFGMouseInput;
759