]> git.mxchange.org Git - flightgear.git/blob - src/Canvas/gui_mgr.cxx
Code cleanups, code updates and fix at least on (possible) devide-by-zero
[flightgear.git] / src / Canvas / gui_mgr.cxx
1 // Canvas gui/dialog manager
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #include "gui_mgr.hxx"
20
21 #include <Main/fg_os.hxx>
22 #include <Main/fg_props.hxx>
23 #include <Main/globals.hxx>
24 #include <Viewer/CameraGroup.hxx>
25 #include <Viewer/renderer.hxx>
26
27 #include <simgear/canvas/Canvas.hxx>
28 #include <simgear/canvas/CanvasPlacement.hxx>
29 #include <simgear/canvas/CanvasWindow.hxx>
30 #include <simgear/canvas/events/KeyboardEvent.hxx>
31 #include <simgear/scene/util/OsgMath.hxx>
32
33 #include <osg/BlendFunc>
34 #include <osgViewer/Viewer>
35 #include <osgGA/GUIEventHandler>
36
37 #include <boost/bind.hpp>
38 #include <boost/lexical_cast.hpp>
39
40 class DesktopGroup;
41 typedef SGSharedPtr<DesktopGroup> DesktopPtr;
42 typedef SGWeakPtr<DesktopGroup> DesktopWeakPtr;
43
44 namespace sc = simgear::canvas;
45
46 /**
47  * Event handler
48  */
49 class GUIEventHandler:
50   public osgGA::GUIEventHandler
51 {
52   public:
53     GUIEventHandler(const DesktopWeakPtr& desktop_group);
54
55     bool handle( const osgGA::GUIEventAdapter& ea,
56                  osgGA::GUIActionAdapter&,
57                  osg::Object*,
58                  osg::NodeVisitor* );
59
60   protected:
61     DesktopWeakPtr _desktop;
62 };
63
64 /**
65  * Track a canvas placement on a window
66  */
67 class WindowPlacement:
68   public sc::Placement
69 {
70   public:
71     WindowPlacement( SGPropertyNode* node,
72                      sc::WindowPtr window,
73                      sc::CanvasPtr canvas ):
74       Placement(node),
75       _window(window),
76       _canvas(canvas)
77     {}
78
79     /**
80      * Remove placement from window
81      */
82     virtual ~WindowPlacement()
83     {
84       sc::WindowPtr window = _window.lock();
85       sc::CanvasPtr canvas = _canvas.lock();
86
87       if( window && canvas && canvas == window->getCanvasContent().lock() )
88         window->setCanvasContent( sc::CanvasPtr() );
89     }
90
91   private:
92     sc::WindowWeakPtr _window;
93     sc::CanvasWeakPtr _canvas;
94 };
95
96 /**
97  * Desktop root group
98  */
99 class DesktopGroup:
100   public sc::Group
101 {
102   public:
103     DesktopGroup();
104
105     void setFocusWindow(const sc::WindowPtr& window);
106
107     bool grabPointer(const sc::WindowPtr& window);
108     void ungrabPointer(const sc::WindowPtr& window);
109
110     bool handleEvent(const osgGA::GUIEventAdapter& ea);
111
112   protected:
113
114     friend class GUIMgr;
115
116     SGPropertyChangeCallback<DesktopGroup> _cb_mouse_mode;
117     bool                                   _handle_events;
118
119     simgear::PropertyObject<int>        _width,
120                                         _height;
121
122     sc::WindowWeakPtr _last_push,
123                       _last_mouse_over,
124                       _resize_window,
125                       _focus_window,
126                       _pointer_grab_window;
127
128     uint8_t _resize;
129     int     _last_cursor;
130
131     osg::Vec2 _drag_start;
132     float _last_x,
133           _last_y;
134     double _last_scroll_time;
135
136     uint32_t _last_key_down_no_mod; // Key repeat for non modifier keys
137
138     bool handleMouse(const osgGA::GUIEventAdapter& ea);
139     bool handleKeyboard(const osgGA::GUIEventAdapter& ea);
140
141     bool handleEvent( const sc::EventPtr& event,
142                       const sc::WindowPtr& active_window );
143     bool handleRootEvent(const sc::EventPtr& event);
144
145     void handleResize(int x, int y, int width, int height);
146     void handleMouseMode(SGPropertyNode* node);
147
148     /**
149      *
150      */
151     sc::ElementFactory
152     getChildFactory(const std::string& type) const
153     {
154       if( type == "window" )
155         return &Element::create<sc::Window>;
156
157       return Group::getChildFactory(type);
158     }
159 };
160
161 //------------------------------------------------------------------------------
162 GUIEventHandler::GUIEventHandler(const DesktopWeakPtr& desktop_group):
163   _desktop( desktop_group )
164 {
165
166 }
167
168 //------------------------------------------------------------------------------
169 bool GUIEventHandler::handle( const osgGA::GUIEventAdapter& ea,
170                               osgGA::GUIActionAdapter&,
171                               osg::Object*,
172                               osg::NodeVisitor* )
173 {
174   if( ea.getHandled() )
175     return false;
176
177   DesktopPtr desktop = _desktop.lock();
178   return desktop && desktop->handleEvent(ea);
179 }
180
181 //------------------------------------------------------------------------------
182 DesktopGroup::DesktopGroup():
183   Group(sc::CanvasPtr(), fgGetNode("/sim/gui/canvas", true)),
184   _cb_mouse_mode( this,
185                   &DesktopGroup::handleMouseMode,
186                   fgGetNode("/devices/status/mice/mouse[0]/mode") ),
187   _handle_events(true),
188   _width(_node, "size[0]"),
189   _height(_node, "size[1]"),
190   _resize(sc::Window::NONE),
191   _last_cursor(MOUSE_CURSOR_NONE),
192   _last_x(-1),
193   _last_y(-1),
194   _last_scroll_time(0),
195   _last_key_down_no_mod(-1)
196 {
197   osg::Camera* camera =
198     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
199   assert(camera);
200   camera->addChild( getMatrixTransform() );
201
202   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
203   stateSet->setDataVariance(osg::Object::STATIC);
204   stateSet->setRenderBinDetails(1000, "RenderBin");
205
206   // speed optimization?
207   stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
208   stateSet->setAttribute(new osg::BlendFunc(
209     osg::BlendFunc::SRC_ALPHA,
210     osg::BlendFunc::ONE_MINUS_SRC_ALPHA)
211   );
212   stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
213   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
214   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
215   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
216
217   _width = _height = -1;
218 }
219
220 //------------------------------------------------------------------------------
221 void DesktopGroup::setFocusWindow(const sc::WindowPtr& window)
222 {
223   _focus_window = window;
224 }
225
226 //------------------------------------------------------------------------------
227 bool DesktopGroup::grabPointer(const sc::WindowPtr& window)
228 {
229   sc::WindowPtr resize = _resize_window.lock();
230   if( (resize && resize != window) || !_pointer_grab_window.expired() )
231     // Already grabbed (resize -> implicit grab)
232     return false;
233
234   _pointer_grab_window = window;
235   return true;
236 }
237
238 //------------------------------------------------------------------------------
239 void DesktopGroup::ungrabPointer(const sc::WindowPtr& window)
240 {
241   if( _pointer_grab_window.expired() )
242     SG_LOG(SG_GUI, SG_WARN, "ungrabPointer: no active grab.");
243   else if( window != _pointer_grab_window.lock() )
244     SG_LOG(SG_GUI, SG_WARN, "ungrabPointer: window is not owner of the grab.");
245   else
246     _pointer_grab_window.reset();
247 }
248
249 //------------------------------------------------------------------------------
250 bool DesktopGroup::handleEvent(const osgGA::GUIEventAdapter& ea)
251 {
252   switch( ea.getEventType() )
253   {
254     case osgGA::GUIEventAdapter::PUSH:
255     case osgGA::GUIEventAdapter::RELEASE:
256 //    case osgGA::GUIEventAdapter::DOUBLECLICK:
257 //    // DOUBLECLICK doesn't seem to be triggered...
258     case osgGA::GUIEventAdapter::DRAG:
259     case osgGA::GUIEventAdapter::MOVE:
260     case osgGA::GUIEventAdapter::SCROLL:
261       return handleMouse(ea);
262     case osgGA::GUIEventAdapter::KEYDOWN:
263     case osgGA::GUIEventAdapter::KEYUP:
264       return handleKeyboard(ea);
265     case osgGA::GUIEventAdapter::RESIZE:
266       handleResize( ea.getWindowX(),
267                     ea.getWindowY(),
268                     ea.getWindowWidth(),
269                     ea.getWindowHeight() );
270       return false; // Let other event handlers also consume resize events
271     default:
272       return false;
273   }
274 }
275
276 /*
277 RESIZE AREAS
278 ============
279
280 |   || |      _ inside corner region (L-shaped part inside margin) both
281 |___||_|_ _ _/  directions can be resized (outside only one axis)
282 |   || |     |
283 |   || |
284 |   || |_____|__                  _
285 |   ||       |   } margin_neg      \
286 |    ========|== <-- window border  |_ area where resize
287 |            |   } margin_pos       |  can be initiated
288 |____________|__/                 _/
289 |<- corner ->|
290 */
291 const float resize_margin_pos = 12;
292 const float resize_margin_neg = 2;
293 const float resize_corner = 20;
294
295 //------------------------------------------------------------------------------
296 bool DesktopGroup::handleMouse(const osgGA::GUIEventAdapter& ea)
297 {
298   if( !_transform->getNumChildren() || !_handle_events )
299     return false;
300
301   sc::MouseEventPtr event(new sc::MouseEvent(ea));
302
303   event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
304   event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
305   if(    ea.getMouseYOrientation()
306       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
307     event->screen_pos.y() = _height - event->screen_pos.y();
308
309   event->delta.x() = event->getScreenX() - _last_x;
310   event->delta.y() = event->getScreenY() - _last_y;
311
312   _last_x = event->getScreenX();
313   _last_y = event->getScreenY();
314
315   event->local_pos = event->client_pos = event->screen_pos;
316
317   if( !_resize_window.expired() )
318   {
319     switch( ea.getEventType() )
320     {
321       case osgGA::GUIEventAdapter::RELEASE:
322         _resize_window.lock()->handleResize(sc::Window::NONE);
323         _resize_window.reset();
324         break;
325       case osgGA::GUIEventAdapter::DRAG:
326         _resize_window.lock()->handleResize( _resize,
327                                              event->screen_pos - _drag_start );
328         return true;
329       default:
330         return false;
331     }
332   }
333
334   sc::WindowPtr window_at_cursor = _pointer_grab_window.lock();
335   if( !window_at_cursor )
336   {
337     for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
338     {
339       osg::Group *element = _transform->getChild(i)->asGroup();
340
341       assert(element);
342       assert(element->getUserData());
343
344       sc::WindowPtr window =
345         dynamic_cast<sc::Window*>
346         (
347           static_cast<sc::Element::OSGUserData*>(
348             element->getUserData()
349           )->element.get()
350         );
351
352       if( !window || !window->isCapturingEvents() || !window->isVisible() )
353         continue;
354
355       float margin = window->isResizable() ? resize_margin_pos : 0;
356       if( window->getScreenRegion().contains( event->getScreenX(),
357                                               event->getScreenY(),
358                                               margin ) )
359       {
360         window_at_cursor = window;
361         break;
362       }
363     }
364   }
365
366   if( window_at_cursor )
367   {
368     const SGRect<float>& reg = window_at_cursor->getScreenRegion();
369
370     if(     window_at_cursor->isResizable()
371         && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
372            || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
373            || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
374            )
375         && !reg.contains( event->getScreenX(),
376                           event->getScreenY(),
377                           -resize_margin_neg ) )
378     {
379       if( !_last_cursor )
380         _last_cursor = fgGetMouseCursor();
381
382       _resize = 0;
383
384       if( event->getScreenX() <= reg.l() + resize_corner )
385         _resize |= sc::Window::LEFT;
386       else if( event->getScreenX() >= reg.r() - resize_corner )
387         _resize |= sc::Window::RIGHT;
388
389       if( event->getScreenY() <= reg.t() + resize_corner )
390         _resize |= sc::Window::TOP;
391       else if( event->getScreenY() >= reg.b() - resize_corner )
392         _resize |= sc::Window::BOTTOM;
393
394       static const int cursor_mapping[] =
395       {
396         0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
397         MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
398         MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
399       };
400
401       if( !cursor_mapping[_resize] )
402         return false;
403
404       fgSetMouseCursor(cursor_mapping[_resize]);
405
406       if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
407       {
408         _resize_window = window_at_cursor;
409         _drag_start = event->screen_pos;
410
411         window_at_cursor->raise();
412         window_at_cursor->handleResize(_resize | sc::Window::INIT);
413       }
414
415       return true;
416     }
417   }
418
419   if( _last_cursor )
420   {
421     fgSetMouseCursor(_last_cursor);
422     _last_cursor = 0;
423     return true;
424   }
425
426   sc::WindowPtr target_window = window_at_cursor;
427   switch( ea.getEventType() )
428   {
429     case osgGA::GUIEventAdapter::PUSH:
430       _last_push = window_at_cursor;
431       event->type = sc::Event::MOUSE_DOWN;
432       break;
433     case osgGA::GUIEventAdapter::SCROLL:
434       switch( ea.getScrollingMotion() )
435       {
436         case osgGA::GUIEventAdapter::SCROLL_UP:
437           event->delta.y() = 1;
438           break;
439         case osgGA::GUIEventAdapter::SCROLL_DOWN:
440           event->delta.y() = -1;
441           break;
442         default:
443           return false;
444       }
445
446       // osg sends two events for every scrolling motion. We don't need
447       // duplicate events, so lets ignore the second event with the same
448       // timestamp.
449       if( _last_scroll_time == ea.getTime() )
450         return window_at_cursor ? true : false;
451       _last_scroll_time = ea.getTime();
452
453       event->type = sc::Event::WHEEL;
454       break;
455     case osgGA::GUIEventAdapter::MOVE:
456     {
457       sc::WindowPtr last_mouse_over = _last_mouse_over.lock();
458       if( last_mouse_over != window_at_cursor && last_mouse_over )
459       {
460         sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
461         move_event->type = sc::Event::MOUSE_LEAVE;
462         move_event->client_pos -= toOsg(last_mouse_over->getPosition());
463         move_event->local_pos = move_event->client_pos;
464
465         last_mouse_over->handleEvent(move_event);
466       }
467       _last_mouse_over = window_at_cursor;
468       event->type = sc::Event::MOUSE_MOVE;
469       break;
470     }
471     case osgGA::GUIEventAdapter::RELEASE:
472     {
473       event->type = sc::Event::MOUSE_UP;
474
475       sc::WindowPtr last_push = _last_push.lock();
476       _last_push.reset();
477
478       if( last_push && last_push != target_window )
479       {
480         // Leave old window
481         sc::MouseEventPtr leave_event( new sc::MouseEvent(*event) );
482         leave_event->type = sc::Event::MOUSE_LEAVE;
483         leave_event->client_pos -= toOsg(last_push->getPosition());
484         leave_event->local_pos = leave_event->client_pos;
485
486         last_push->handleEvent(leave_event);
487       }
488       break;
489     }
490     case osgGA::GUIEventAdapter::DRAG:
491       target_window = _last_push.lock();
492       event->type = sc::Event::DRAG;
493       break;
494
495     default:
496       return false;
497   }
498
499   if( target_window )
500   {
501     event->client_pos -= toOsg(target_window->getPosition());
502     event->local_pos = event->client_pos;
503     return target_window->handleEvent(event);
504   }
505   else
506     return handleRootEvent(event);
507 }
508
509 //------------------------------------------------------------------------------
510 bool DesktopGroup::handleKeyboard(const osgGA::GUIEventAdapter& ea)
511 {
512   if( !_transform->getNumChildren() || !_handle_events )
513     return false;
514
515   sc::KeyboardEventPtr event(new sc::KeyboardEvent(ea));
516
517   // Detect key repeat (of non modifier keys)
518   if( !event->isModifier() )
519   {
520     if( event->getType() == sc::Event::KEY_DOWN )
521     {
522       if( event->keyCode() == _last_key_down_no_mod )
523         event->setRepeat(true);
524       _last_key_down_no_mod = event->keyCode();
525     }
526     else
527     {
528       if( event->keyCode() == _last_key_down_no_mod )
529       _last_key_down_no_mod = -1;
530     }
531   }
532
533   sc::WindowPtr active_window = _focus_window.lock();
534   bool handled = handleEvent(event, active_window);
535
536   if(    event->getType() == sc::Event::KEY_DOWN
537       && !event->defaultPrevented()
538       && event->isPrint() )
539   {
540     sc::KeyboardEventPtr keypress( new sc::KeyboardEvent(*event) );
541     keypress->type = sc::Event::KEY_PRESS;
542
543     handled |= handleEvent(keypress, active_window);
544   }
545
546   return handled;
547 }
548
549 //------------------------------------------------------------------------------
550 bool DesktopGroup::handleEvent( const sc::EventPtr& event,
551                                 const sc::WindowPtr& active_window )
552 {
553   return active_window
554        ? active_window->handleEvent(event)
555        : handleRootEvent(event);
556 }
557
558 //------------------------------------------------------------------------------
559 bool DesktopGroup::handleRootEvent(const sc::EventPtr& event)
560 {
561   sc::Element::handleEvent(event);
562
563   // stopPropagation() on DesktopGroup stops propagation to internal event
564   // handling.
565   return event->propagation_stopped;
566 }
567
568 //------------------------------------------------------------------------------
569 void DesktopGroup::handleResize(int x, int y, int width, int height)
570 {
571   if( _width == width && _height == height )
572     return;
573
574   _width = width;
575   _height = height;
576
577   // Origin should be at top left corner, therefore we need to mirror the y-axis
578   _transform->setMatrix(osg::Matrix(
579     1,  0, 0, 0,
580     0, -1, 0, 0,
581     0,  0, 1, 0,
582     0, _height, 0, 1
583   ));
584 }
585
586 //------------------------------------------------------------------------------
587 void DesktopGroup::handleMouseMode(SGPropertyNode* node)
588 {
589   // pass-through indicates events should pass through to the UI
590   _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
591                      ->getBoolValue("pass-through");
592 }
593
594 //------------------------------------------------------------------------------
595 GUIMgr::GUIMgr()
596 {
597
598 }
599
600 //------------------------------------------------------------------------------
601 sc::WindowPtr GUIMgr::createWindow(const std::string& name)
602 {
603   sc::WindowPtr window = _desktop->createChild<sc::Window>(name);
604   if( name.empty() )
605     window->set<std::string>
606     (
607       "id",
608       boost::lexical_cast<std::string>(window->getProps()->getIndex())
609     );
610   return window;
611 }
612
613 //------------------------------------------------------------------------------
614 void GUIMgr::init()
615 {
616   if( _desktop && _event_handler )
617   {
618     SG_LOG(SG_GUI, SG_WARN, "GUIMgr::init() already initialized.");
619     return;
620   }
621
622   DesktopPtr desktop( new DesktopGroup );
623   desktop->handleResize
624   (
625     0,
626     0,
627     fgGetInt("/sim/startup/xsize"),
628     fgGetInt("/sim/startup/ysize")
629   );
630   _desktop = desktop;
631
632   _event_handler = new GUIEventHandler(desktop);
633   globals->get_renderer()
634          ->getViewer()
635          ->getEventHandlers()
636          // GUI is on top of everything so lets install as first event handler
637          .push_front( _event_handler );
638
639   sc::Canvas::addPlacementFactory
640   (
641     "window",
642     boost::bind(&GUIMgr::addWindowPlacement, this, _1, _2)
643   );
644
645   _desktop->getProps()->fireCreatedRecursive();
646 }
647
648 //------------------------------------------------------------------------------
649 void GUIMgr::shutdown()
650 {
651   if( !_desktop && !_event_handler )
652   {
653     SG_LOG(SG_GUI, SG_WARN, "GUIMgr::shutdown() not running.");
654     return;
655   }
656
657   sc::Canvas::removePlacementFactory("window");
658
659   if( _desktop )
660   {
661     _desktop->destroy();
662     _desktop.reset();
663   }
664
665   if( _event_handler )
666   {
667     globals->get_renderer()
668            ->getViewer()
669            ->removeEventHandler( _event_handler );
670     _event_handler = 0;
671   }
672 }
673
674 //------------------------------------------------------------------------------
675 void GUIMgr::update(double dt)
676 {
677   _desktop->update(dt);
678 }
679
680 //------------------------------------------------------------------------------
681 sc::GroupPtr GUIMgr::getDesktop()
682 {
683   return _desktop;
684 }
685
686 //------------------------------------------------------------------------------
687 void GUIMgr::setInputFocus(const simgear::canvas::WindowPtr& window)
688 {
689   static_cast<DesktopGroup*>(_desktop.get())->setFocusWindow(window);
690 }
691
692 //------------------------------------------------------------------------------
693 bool GUIMgr::grabPointer(const sc::WindowPtr& window)
694 {
695   return static_cast<DesktopGroup*>(_desktop.get())->grabPointer(window);
696 }
697
698 //------------------------------------------------------------------------------
699 void GUIMgr::ungrabPointer(const sc::WindowPtr& window)
700 {
701   static_cast<DesktopGroup*>(_desktop.get())->ungrabPointer(window);
702 }
703
704 //------------------------------------------------------------------------------
705 sc::Placements
706 GUIMgr::addWindowPlacement( SGPropertyNode* placement,
707                             sc::CanvasPtr canvas )
708 {
709   const std::string& id = placement->getStringValue("id");
710
711   sc::Placements placements;
712   sc::WindowPtr window = _desktop->getChild<sc::Window>(id);
713   if( window )
714   {
715     window->setCanvasContent(canvas);
716     placements.push_back(
717       sc::PlacementPtr(
718         new WindowPlacement(placement, window, canvas)
719     ));
720   }
721   return placements;
722 }