]> git.mxchange.org Git - flightgear.git/blob - src/Input/FGMouseInput.cxx
Update for tweaked SGPickCallback API.
[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 <osgGA/GUIEventAdapter>
32 #include "Main/globals.hxx"
33
34 using std::ios_base;
35
36 void ActivePickCallbacks::init( int button, const osgGA::GUIEventAdapter* ea )
37 {
38   // Get the list of hit callbacks. Take the first callback that
39   // accepts the mouse button press and ignore the rest of them
40   // That is they get sorted by distance and by scenegraph depth.
41   // The nearest one is the first one and the deepest
42   // (the most specialized one in the scenegraph) is the first.
43   std::vector<SGSceneryPick> pickList;
44   if (!globals->get_renderer()->pick(pickList, ea)) {
45     return;
46   }
47
48   std::vector<SGSceneryPick>::const_iterator i;
49   for (i = pickList.begin(); i != pickList.end(); ++i) {
50     if (i->callback->buttonPressed(button, ea, i->info)) {
51         (*this)[button].push_back(i->callback);
52         return;
53     }
54   }
55 }
56
57 void ActivePickCallbacks::update( double dt )
58 {
59   // handle repeatable mouse press events
60   for( iterator mi = begin(); mi != end(); ++mi ) {
61     std::list<SGSharedPtr<SGPickCallback> >::iterator li;
62     for (li = mi->second.begin(); li != mi->second.end(); ++li) {
63       (*li)->update(dt);
64     }
65   }
66 }
67
68
69 #include <plib/pu.h>
70 #include <Model/panelnode.hxx>
71 #include <Cockpit/panel.hxx>
72 ////////////////////////////////////////////////////////////////////////
73 // The Mouse Input Implementation
74 ////////////////////////////////////////////////////////////////////////
75
76 const FGMouseInput::MouseCursorMap FGMouseInput::mouse_cursor_map[] = {
77     { "none", MOUSE_CURSOR_NONE },
78     { "inherit", MOUSE_CURSOR_POINTER },
79     { "wait", MOUSE_CURSOR_WAIT },
80     { "crosshair", MOUSE_CURSOR_CROSSHAIR },
81     { "left-right", MOUSE_CURSOR_LEFTRIGHT },
82     { 0, 0 }
83 };
84
85 FGMouseInput * FGMouseInput::mouseInput = NULL;
86
87 FGMouseInput::FGMouseInput() :
88   haveWarped(false),
89   xSizeNode(fgGetNode("/sim/startup/xsize", false ) ),
90   ySizeNode(fgGetNode("/sim/startup/ysize", false ) ),
91   xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ),
92   yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ),
93   hideCursorNode(fgGetNode("/sim/mouse/hide-cursor", true ) ),
94   cursorTimeoutNode(fgGetNode("/sim/mouse/cursor-timeout-sec", true ) )
95 {
96   if( mouseInput == NULL )
97     mouseInput = this;
98 }
99
100 FGMouseInput::~FGMouseInput()
101 {
102   if( mouseInput == this )
103     mouseInput = NULL;
104 }
105
106 void FGMouseInput::init()
107 {
108   SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse bindings");
109   string module = "";
110
111   SGPropertyNode * mouse_nodes = fgGetNode("/input/mice");
112   if (mouse_nodes == 0) {
113     SG_LOG(SG_INPUT, SG_WARN, "No mouse bindings (/input/mice)!!");
114     mouse_nodes = fgGetNode("/input/mice", true);
115   }
116
117   int j;
118   for (int i = 0; i < MAX_MICE; i++) {
119     SGPropertyNode * mouse_node = mouse_nodes->getChild("mouse", i, true);
120     mouse &m = bindings[i];
121
122                                 // Grab node pointers
123     std::ostringstream buf;
124     buf <<  "/devices/status/mice/mouse[" << i << "]/mode";
125     m.mode_node = fgGetNode(buf.str().c_str());
126     if (m.mode_node == NULL) {
127       m.mode_node = fgGetNode(buf.str().c_str(), true);
128       m.mode_node->setIntValue(0);
129     }
130     for (j = 0; j < MAX_MOUSE_BUTTONS; j++) {
131       buf.seekp(ios_base::beg);
132       buf << "/devices/status/mice/mouse["<< i << "]/button[" << j << "]";
133       m.mouse_button_nodes[j] = fgGetNode(buf.str().c_str(), true);
134       m.mouse_button_nodes[j]->setBoolValue(false);
135     }
136
137                                 // Read all the modes
138     m.nModes = mouse_node->getIntValue("mode-count", 1);
139     m.modes = new mouse_mode[m.nModes];
140
141     for (int j = 0; j < m.nModes; j++) {
142       int k;
143
144                                 // Read the mouse cursor for this mode
145       SGPropertyNode * mode_node = mouse_node->getChild("mode", j, true);
146       const char * cursor_name =
147         mode_node->getStringValue("cursor", "inherit");
148       m.modes[j].cursor = MOUSE_CURSOR_POINTER;
149       for (k = 0; mouse_cursor_map[k].name != 0; k++) {
150         if (!strcmp(mouse_cursor_map[k].name, cursor_name)) {
151           m.modes[j].cursor = mouse_cursor_map[k].cursor;
152           break;
153         }
154       }
155
156                                 // Read other properties for this mode
157       m.modes[j].constrained = mode_node->getBoolValue("constrained", false);
158       m.modes[j].pass_through = mode_node->getBoolValue("pass-through", false);
159
160                                 // Read the button bindings for this mode
161       m.modes[j].buttons = new FGButton[MAX_MOUSE_BUTTONS];
162       std::ostringstream buf;
163       for (k = 0; k < MAX_MOUSE_BUTTONS; k++) {
164         buf.seekp(ios_base::beg);
165         buf << "mouse button " << k;
166         SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse button " << k);
167         m.modes[j].buttons[k].init( mode_node->getChild("button", k), buf.str(), module );
168       }
169
170                                 // Read the axis bindings for this mode
171       read_bindings(mode_node->getChild("x-axis", 0, true), m.modes[j].x_bindings, KEYMOD_NONE, module );
172       read_bindings(mode_node->getChild("y-axis", 0, true), m.modes[j].y_bindings, KEYMOD_NONE, module );
173     }
174   }
175
176   fgRegisterMouseClickHandler(mouseClickHandler);
177   fgRegisterMouseMotionHandler(mouseMotionHandler);
178 }
179
180 void FGMouseInput::update ( double dt )
181 {
182   double cursorTimeout = cursorTimeoutNode ? cursorTimeoutNode->getDoubleValue() : 10.0;
183
184   mouse &m = bindings[0];
185   int mode =  m.mode_node->getIntValue();
186   if (mode != m.current_mode) {
187     m.current_mode = mode;
188     m.timeout = cursorTimeout;
189     if (mode >= 0 && mode < m.nModes) {
190       fgSetMouseCursor(m.modes[mode].cursor);
191       m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2;
192       m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2;
193       fgWarpMouse(m.x, m.y);
194       haveWarped = true;
195     } else {
196       SG_LOG(SG_INPUT, SG_DEBUG, "Mouse mode " << mode << " out of range");
197       fgSetMouseCursor(MOUSE_CURSOR_POINTER);
198     }
199   }
200
201   if ( hideCursorNode ==NULL || hideCursorNode->getBoolValue() ) {
202       if ( m.x != m.save_x || m.y != m.save_y ) {
203           m.timeout = cursorTimeout;
204           if (fgGetMouseCursor() == MOUSE_CURSOR_NONE)
205               fgSetMouseCursor(m.modes[mode].cursor);
206       } else {
207           m.timeout -= dt;
208           if ( m.timeout <= 0.0 ) {
209               fgSetMouseCursor(MOUSE_CURSOR_NONE);
210               m.timeout = 0.0;
211           }
212       }
213       m.save_x = m.x;
214       m.save_y = m.y;
215   }
216
217   activePickCallbacks.update( dt );
218 }
219
220 FGMouseInput::mouse::mouse ()
221   : x(-1),
222     y(-1),
223     save_x(-1),
224     save_y(-1),
225     nModes(1),
226     current_mode(0),
227     timeout(0),
228     modes(NULL)
229 {
230 }
231
232 FGMouseInput::mouse::~mouse ()
233 {
234   delete [] modes;
235 }
236
237 FGMouseInput::mouse_mode::mouse_mode ()
238   : cursor(MOUSE_CURSOR_POINTER),
239     constrained(false),
240     pass_through(false),
241     buttons(NULL)
242 {
243 }
244
245 FGMouseInput::mouse_mode::~mouse_mode ()
246 {
247                                 // FIXME: memory leak
248 //   for (int i = 0; i < KEYMOD_MAX; i++) {
249 //     int j;
250 //     for (j = 0; i < x_bindings[i].size(); j++)
251 //       delete bindings[i][j];
252 //     for (j = 0; j < y_bindings[i].size(); j++)
253 //       delete bindings[i][j];
254 //   }
255   delete [] buttons;
256 }
257
258 void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
259 {
260   int modifiers = fgGetKeyModifiers();
261
262   mouse &m = bindings[0];
263   mouse_mode &mode = m.modes[m.current_mode];
264
265                                 // Let the property manager know.
266   if (b >= 0 && b < MAX_MOUSE_BUTTONS)
267     m.mouse_button_nodes[b]->setBoolValue(updown == MOUSE_BUTTON_DOWN);
268
269                                 // Pass on to PUI and the panel if
270                                 // requested, and return if one of
271                                 // them consumes the event.
272
273   if (updown != MOUSE_BUTTON_DOWN) {
274     // Execute the mouse up event in any case, may be we should
275     // stop processing here?
276     while (!activePickCallbacks[b].empty()) {
277       activePickCallbacks[b].front()->buttonReleased();
278       activePickCallbacks[b].pop_front();
279     }
280   }
281
282   if (mode.pass_through) {
283     // remove once PUI uses standard picking mechanism
284     if (0 <= x && 0 <= y && puMouse(b, updown, x, y))
285       return;
286     else {
287       // pui didn't want the click event so compute a
288       // scenegraph intersection point corresponding to the mouse click
289       if (updown == MOUSE_BUTTON_DOWN) {
290         activePickCallbacks.init( b, ea );
291       }
292     }
293   }
294
295   // OK, PUI and the panel didn't want the click
296   if (b >= MAX_MOUSE_BUTTONS) {
297     SG_LOG(SG_INPUT, SG_ALERT, "Mouse button " << b
298            << " where only " << MAX_MOUSE_BUTTONS << " expected");
299     return;
300   }
301
302   m.modes[m.current_mode].buttons[b].update( modifiers, 0 != updown, x, y);
303 }
304
305 void FGMouseInput::doMouseMotion (int x, int y)
306 {
307   // Don't call fgGetKeyModifiers() here, until we are using a
308   // toolkit that supports getting the mods from outside a key
309   // callback.  Glut doesn't.
310   int modifiers = KEYMOD_NONE;
311
312   int xsize = xSizeNode ? xSizeNode->getIntValue() : 800;
313   int ysize = ySizeNode ? ySizeNode->getIntValue() : 600;
314
315   mouse &m = bindings[0];
316
317   if (m.current_mode < 0 || m.current_mode >= m.nModes) {
318       m.x = x;
319       m.y = y;
320       return;
321   }
322   mouse_mode &mode = m.modes[m.current_mode];
323
324                                 // Pass on to PUI if requested, and return
325                                 // if PUI consumed the event.
326   if (mode.pass_through && puMouse(x, y)) {
327       m.x = x;
328       m.y = y;
329       return;
330   }
331   
332   if (haveWarped)
333   {
334       // don't fire mouse-movement events at the first update after warping the mouse,
335       // just remember the new mouse position
336       haveWarped = false;
337   }
338   else
339   {
340                                 // OK, PUI didn't want the event,
341                                 // so we can play with it.
342       if (x != m.x) {
343         int delta = x - m.x;
344         xAccelNode->setIntValue( delta );
345         for (unsigned int i = 0; i < mode.x_bindings[modifiers].size(); i++)
346           mode.x_bindings[modifiers][i]->fire(double(delta), double(xsize));
347       }
348       if (y != m.y) {
349         int delta = y - m.y;
350         yAccelNode->setIntValue( -delta );
351         for (unsigned int i = 0; i < mode.y_bindings[modifiers].size(); i++)
352           mode.y_bindings[modifiers][i]->fire(double(delta), double(ysize));
353       }
354   }
355                                 // Constrain the mouse if requested
356   if (mode.constrained) {
357     int new_x=x,new_y=y;
358     
359     bool need_warp = false;
360     if (x <= (xsize * .25) || x >= (xsize * .75)) {
361       new_x = int(xsize * .5);
362       need_warp = true;
363     }
364
365     if (y <= (ysize * .25) || y >= (ysize * .75)) {
366       new_y = int(ysize * .5);
367       need_warp = true;
368     }
369
370     if (need_warp)
371     {
372       fgWarpMouse(new_x, new_y);
373       haveWarped = true;
374       SG_LOG(SG_INPUT, SG_DEBUG, "Mouse warp: " << x << ", " << y << " => " << new_x << ", " << new_y);
375     }
376   }
377
378   if (m.x != x)
379       fgSetInt("/devices/status/mice/mouse/x", m.x = x);
380
381   if (m.y != y)
382       fgSetInt("/devices/status/mice/mouse/y", m.y = y);
383 }
384
385 void FGMouseInput::mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
386 {
387     if(mouseInput)
388       mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea);
389 }
390
391 void FGMouseInput::mouseMotionHandler(int x, int y)
392 {
393     if (mouseInput != 0)
394         mouseInput->doMouseMotion(x, y);
395 }
396
397