]> git.mxchange.org Git - flightgear.git/blobdiff - src/Canvas/gui_mgr.cxx
Fix for bug 1304 - crash loading XML route
[flightgear.git] / src / Canvas / gui_mgr.cxx
index 4fa66febd24830533b1c932d5a0145f1b2c5edad..453893886ba6ca100590fb8b7135c330242ab758 100644 (file)
 
 #include "gui_mgr.hxx"
 #include <Canvas/window.hxx>
-#include <Canvas/canvas.hxx>
 
+#include <Main/fg_os.hxx>
+#include <Main/fg_props.hxx>
 #include <Main/globals.hxx>
 #include <Viewer/CameraGroup.hxx>
 #include <Viewer/renderer.hxx>
 
+#include <simgear/canvas/Canvas.hxx>
+#include <simgear/canvas/CanvasPlacement.hxx>
+#include <simgear/scene/util/OsgMath.hxx>
+
 #include <osg/BlendFunc>
 #include <osgViewer/Viewer>
 #include <osgGA/GUIEventHandler>
 
 #include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+class DesktopGroup;
+typedef boost::shared_ptr<DesktopGroup> DesktopPtr;
+typedef boost::weak_ptr<DesktopGroup> DesktopWeakPtr;
 
 /**
  * Event handler
@@ -37,31 +47,28 @@ class GUIEventHandler:
   public osgGA::GUIEventHandler
 {
   public:
-    GUIEventHandler(GUIMgr* gui_mgr):
-      _gui_mgr( gui_mgr )
-    {}
+    GUIEventHandler(const DesktopWeakPtr& desktop_group);
 
     bool handle( const osgGA::GUIEventAdapter& ea,
-                 osgGA::GUIActionAdapter& aa,
+                 osgGA::GUIActionAdapter&,
                  osg::Object*,
-                 osg::NodeVisitor* )
-    {
-      return _gui_mgr->handleEvent(ea);
-    }
+                 osg::NodeVisitor* );
 
   protected:
-    GUIMgr *_gui_mgr;
+    DesktopWeakPtr _desktop;
 };
 
 /**
  * Track a canvas placement on a window
  */
 class WindowPlacement:
-  public canvas::Placement
+  public simgear::canvas::Placement
 {
   public:
-    WindowPlacement( canvas::WindowPtr window,
-                     CanvasPtr canvas ):
+    WindowPlacement( SGPropertyNode* node,
+                     canvas::WindowPtr window,
+                     simgear::canvas::CanvasPtr canvas ):
+      Placement(node),
       _window(window),
       _canvas(canvas)
     {}
@@ -72,60 +79,104 @@ class WindowPlacement:
     virtual ~WindowPlacement()
     {
       canvas::WindowPtr window = _window.lock();
-      CanvasPtr canvas = _canvas.lock();
+      simgear::canvas::CanvasPtr canvas = _canvas.lock();
 
-      if( window && canvas && canvas == window->getCanvas().lock() )
-        window->setCanvasCanvasPtr() );
+      if( window && canvas && canvas == window->getCanvasContent().lock() )
+        window->setCanvasContent( simgear::canvas::CanvasPtr() );
     }
 
   private:
     canvas::WindowWeakPtr _window;
-    CanvasWeakPtr _canvas;
+    simgear::canvas::CanvasWeakPtr _canvas;
 };
 
 /**
- * Store pointer to window as user data
+ * Desktop root group
  */
-class WindowUserData:
-  public osg::Referenced
+class DesktopGroup:
+  public simgear::canvas::Group
 {
   public:
-    canvas::WindowPtr window;
-    WindowUserData(canvas::WindowPtr window):
-      window(window)
-    {}
+    DesktopGroup();
+    bool handleEvent(const osgGA::GUIEventAdapter& ea);
+
+  protected:
+
+    friend class GUIMgr;
+
+    SGPropertyChangeCallback<DesktopGroup> _cb_mouse_mode;
+    bool                                   _handle_events;
+
+    simgear::PropertyObject<int>        _width,
+                                        _height;
+
+    canvas::WindowWeakPtr _last_push,
+                          _last_mouse_over,
+                          _resize_window;
+    uint8_t _resize;
+    int     _last_cursor;
+
+    osg::Vec2 _drag_start;
+    float _last_x,
+          _last_y;
+    double _last_scroll_time;
+
+    bool handleMouse(const osgGA::GUIEventAdapter& ea);
+    void handleResize(int x, int y, int width, int height);
+    void handleMouseMode(SGPropertyNode* node);
+
+    /**
+     *
+     */
+    simgear::canvas::ElementFactory
+    getChildFactory(const std::string& type) const
+    {
+      if( type == "window" )
+        return &Element::create<canvas::Window>;
+
+      return Group::getChildFactory(type);
+    }
 };
 
 //------------------------------------------------------------------------------
-typedef boost::shared_ptr<canvas::Window> WindowPtr;
-WindowPtr windowFactory(SGPropertyNode* node)
+GUIEventHandler::GUIEventHandler(const DesktopWeakPtr& desktop_group):
+  _desktop( desktop_group )
 {
-  return WindowPtr(new canvas::Window(node));
+
 }
 
 //------------------------------------------------------------------------------
-GUIMgr::GUIMgr():
-  PropertyBasedMgr("/sim/gui/canvas", "window", &windowFactory),
-  _event_handler( new GUIEventHandler(this) ),
-  _transform( new osg::MatrixTransform ),
-  _width(_props, "size[0]"),
-  _height(_props, "size[1]")
+bool GUIEventHandler::handle( const osgGA::GUIEventAdapter& ea,
+                              osgGA::GUIActionAdapter&,
+                              osg::Object*,
+                              osg::NodeVisitor* )
 {
-  _width = _height = -1;
+  if( ea.getHandled() )
+    return false;
+
+  DesktopPtr desktop = _desktop.lock();
+  return desktop && desktop->handleEvent(ea);
+}
 
+//------------------------------------------------------------------------------
+DesktopGroup::DesktopGroup():
+  Group(simgear::canvas::CanvasPtr(), fgGetNode("/sim/gui/canvas", true)),
+  _cb_mouse_mode( this,
+                  &DesktopGroup::handleMouseMode,
+                  fgGetNode("/devices/status/mice/mouse[0]/mode") ),
+  _handle_events(true),
+  _width(_node, "size[0]"),
+  _height(_node, "size[1]"),
+  _resize(canvas::Window::NONE),
+  _last_cursor(MOUSE_CURSOR_NONE),
+  _last_x(-1),
+  _last_y(-1),
+  _last_scroll_time(0)
+{
   osg::Camera* camera =
     flightgear::getGUICamera( flightgear::CameraGroup::getDefault() );
   assert(camera);
-  camera->addChild(_transform);
-
-  osg::Viewport* vp = camera->getViewport();
-  handleResize(vp->x(), vp->y(), vp->width(), vp->height());
-
-  Canvas::addPlacementFactory
-  (
-    "window",
-    boost::bind(&GUIMgr::addPlacement, this, _1, _2)
-  );
+  camera->addChild( getMatrixTransform() );
 
   osg::StateSet* stateSet = _transform->getOrCreateStateSet();
   stateSet->setDataVariance(osg::Object::STATIC);
@@ -141,56 +192,20 @@ GUIMgr::GUIMgr():
   stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
   stateSet->setMode(GL_FOG, osg::StateAttribute::OFF);
   stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
-}
-
-//------------------------------------------------------------------------------
-void GUIMgr::init()
-{
-  PropertyBasedMgr::init();
-
-  globals->get_renderer()
-         ->getViewer()
-         ->addEventHandler( _event_handler );
-}
-
-//------------------------------------------------------------------------------
-void GUIMgr::shutdown()
-{
-  PropertyBasedMgr::shutdown();
 
-  globals->get_renderer()
-         ->getViewer()
-         ->removeEventHandler( _event_handler );
-}
-
-//------------------------------------------------------------------------------
-void GUIMgr::elementCreated(PropertyBasedElementPtr element)
-{
-  canvas::WindowPtr window =
-    boost::static_pointer_cast<canvas::Window>(element);
+  _width = _height = -1;
 
-  size_t layer_index = std::max(0, window->getProps()->getIntValue("layer", 1));
-  osg::Group *layer = 0;
+  // Do not change values on reinit
+  _width.node()->setAttribute(SGPropertyNode::PRESERVE, true);
+  _height.node()->setAttribute(SGPropertyNode::PRESERVE, true);
 
-  if( layer_index < _transform->getNumChildren() )
-  {
-    layer = _transform->getChild(layer_index)->asGroup();
-    assert(layer);
-  }
-  else
-  {
-    while( _transform->getNumChildren() <= layer_index )
-    {
-      layer = new osg::Group;
-      _transform->addChild(layer);
-    }
-  }
-  window->getGroup()->setUserData(new WindowUserData(window));
-  layer->addChild(window->getGroup());
+  // Do not restore windows on reinit (all windows will need to be recreated,
+  // but hey it's a reset ;-))
+  _node->setAttribute(SGPropertyNode::PRESERVE, true);
 }
 
 //------------------------------------------------------------------------------
-bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
+bool DesktopGroup::handleEvent(const osgGA::GUIEventAdapter& ea)
 {
   switch( ea.getEventType() )
   {
@@ -202,91 +217,161 @@ bool GUIMgr::handleEvent(const osgGA::GUIEventAdapter& ea)
     case osgGA::GUIEventAdapter::MOVE:
     case osgGA::GUIEventAdapter::SCROLL:
       return handleMouse(ea);
-//        case osgGA::GUIEventAdapter::MOVE:
-//          std::cout << "MOVE" << std::endl;
-//          break;
     case osgGA::GUIEventAdapter::RESIZE:
       handleResize( ea.getWindowX(),
                     ea.getWindowY(),
                     ea.getWindowWidth(),
                     ea.getWindowHeight() );
-      return true;
+      return false; // Let other event handlers also consume resize events
     default:
       return false;
   }
 }
 
-//------------------------------------------------------------------------------
-canvas::WindowPtr GUIMgr::getWindow(size_t i)
-{
-  return boost::static_pointer_cast<canvas::Window>(_elements[i]);
-}
+/*
+RESIZE AREAS
+============
+
+|   || |      _ inside corner region (L-shaped part inside margin) both
+|___||_|_ _ _/  directions can be resized (outside only one axis)
+|   || |     |
+|   || |
+|   || |_____|__                  _
+|   ||       |   } margin_neg      \
+|    ========|== <-- window border  |_ area where resize
+|            |   } margin_pos       |  can be initiated
+|____________|__/                 _/
+|<- corner ->|
+*/
+const float resize_margin_pos = 12;
+const float resize_margin_neg = 2;
+const float resize_corner = 20;
 
 //------------------------------------------------------------------------------
-canvas::Placements GUIMgr::addPlacement( const SGPropertyNode* node,
-                                         CanvasPtr canvas )
+bool DesktopGroup::handleMouse(const osgGA::GUIEventAdapter& ea)
 {
-  int placement_index = node->getIntValue("index", -1);
-
-  canvas::Placements placements;
-  for( size_t i = 0; i < _elements.size(); ++i )
-  {
-    if( placement_index > 0 && static_cast<int>(i) != placement_index )
-      continue;
-
-    canvas::WindowPtr window = getWindow(i);
-    if( !window )
-      continue;
-
-    window->setCanvas(canvas);
-    placements.push_back(
-      canvas::PlacementPtr(new WindowPlacement(window, canvas))
-    );
-  }
-  return placements;
-}
-
-//------------------------------------------------------------------------------
-bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
-{
-  if( !_transform->getNumChildren() )
+  if( !_transform->getNumChildren() || !_handle_events )
     return false;
 
-  canvas::MouseEvent event( ea.getEventType() );
+  namespace sc = simgear::canvas;
+  sc::MouseEventPtr event(new sc::MouseEvent(ea));
 
-  event.x = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
-  event.y = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
+  event->screen_pos.x() = 0.5 * (ea.getXnormalized() + 1) * _width + 0.5;
+  event->screen_pos.y() = 0.5 * (ea.getYnormalized() + 1) * _height + 0.5;
   if(    ea.getMouseYOrientation()
       != osgGA::GUIEventAdapter::Y_INCREASING_DOWNWARDS )
-    event.y = _height - event.y;
+    event->screen_pos.y() = _height - event->screen_pos.y();
 
-  event.button = ea.getButton();
-  event.state = ea.getButtonMask();
-  event.mod = ea.getModKeyMask();
-  event.scroll = ea.getScrollingMotion();
+  event->delta.x() = event->getScreenX() - _last_x;
+  event->delta.y() = event->getScreenY() - _last_y;
+
+  _last_x = event->getScreenX();
+  _last_y = event->getScreenY();
+
+  event->local_pos = event->client_pos = event->screen_pos;
+
+  if( !_resize_window.expired() )
+  {
+    switch( ea.getEventType() )
+    {
+      case osgGA::GUIEventAdapter::RELEASE:
+        _resize_window.lock()->handleResize(canvas::Window::NONE);
+        _resize_window.reset();
+        break;
+      case osgGA::GUIEventAdapter::DRAG:
+        _resize_window.lock()->handleResize( _resize,
+                                             event->screen_pos - _drag_start );
+        return true;
+      default:
+        return false;
+    }
+  }
 
   canvas::WindowPtr window_at_cursor;
   for( int i = _transform->getNumChildren() - 1; i >= 0; --i )
   {
-    osg::Group *layer = _transform->getChild(i)->asGroup();
-    assert(layer);
-    if( !layer->getNumChildren() )
+    osg::Group *element = _transform->getChild(i)->asGroup();
+
+    assert(element);
+    assert(element->getUserData());
+
+    canvas::WindowPtr window =
+      boost::dynamic_pointer_cast<canvas::Window>
+      (
+        static_cast<sc::Element::OSGUserData*>(element->getUserData())->element
+      );
+
+    if( !window || !window->isCapturingEvents() || !window->isVisible() )
       continue;
 
-    for( int j = layer->getNumChildren() - 1; j >= 0; --j )
+    float margin = window->isResizable() ? resize_margin_pos : 0;
+    if( window->getScreenRegion().contains( event->getScreenX(),
+                                            event->getScreenY(),
+                                            margin ) )
+    {
+      window_at_cursor = window;
+      break;
+    }
+  }
+
+  if( window_at_cursor )
+  {
+    const SGRect<float>& reg = window_at_cursor->getScreenRegion();
+
+    if(     window_at_cursor->isResizable()
+        && (  ea.getEventType() == osgGA::GUIEventAdapter::MOVE
+           || ea.getEventType() == osgGA::GUIEventAdapter::PUSH
+           || ea.getEventType() == osgGA::GUIEventAdapter::RELEASE
+           )
+        && !reg.contains( event->getScreenX(),
+                          event->getScreenY(),
+                          -resize_margin_neg ) )
     {
-      assert(layer->getChild(j)->getUserData());
-      canvas::WindowPtr window =
-        static_cast<WindowUserData*>(layer->getChild(j)->getUserData())->window;
-      if( window->getRegion().contains(event.x, event.y) )
+      if( !_last_cursor )
+        _last_cursor = fgGetMouseCursor();
+
+      _resize = 0;
+
+      if( event->getScreenX() <= reg.l() + resize_corner )
+        _resize |= canvas::Window::LEFT;
+      else if( event->getScreenX() >= reg.r() - resize_corner )
+        _resize |= canvas::Window::RIGHT;
+
+      if( event->getScreenY() <= reg.t() + resize_corner )
+        _resize |= canvas::Window::TOP;
+      else if( event->getScreenY() >= reg.b() - resize_corner )
+        _resize |= canvas::Window::BOTTOM;
+
+      static const int cursor_mapping[] =
       {
-        window_at_cursor = window;
-        break;
+        0, MOUSE_CURSOR_LEFTSIDE, MOUSE_CURSOR_RIGHTSIDE, 0,
+        MOUSE_CURSOR_TOPSIDE, MOUSE_CURSOR_TOPLEFT, MOUSE_CURSOR_TOPRIGHT, 0,
+        MOUSE_CURSOR_BOTTOMSIDE, MOUSE_CURSOR_BOTTOMLEFT, MOUSE_CURSOR_BOTTOMRIGHT,
+      };
+
+      if( !cursor_mapping[_resize] )
+        return false;
+
+      fgSetMouseCursor(cursor_mapping[_resize]);
+
+      if( ea.getEventType() == osgGA::GUIEventAdapter::PUSH )
+      {
+        _resize_window = window_at_cursor;
+        _drag_start = event->screen_pos;
+
+        window_at_cursor->raise();
+        window_at_cursor->handleResize(_resize | canvas::Window::INIT);
       }
+
+      return true;
     }
+  }
 
-    if( window_at_cursor )
-      break;
+  if( _last_cursor )
+  {
+    fgSetMouseCursor(_last_cursor);
+    _last_cursor = 0;
+    return true;
   }
 
   canvas::WindowPtr target_window = window_at_cursor;
@@ -294,21 +379,68 @@ bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
   {
     case osgGA::GUIEventAdapter::PUSH:
       _last_push = window_at_cursor;
+      event->type = sc::Event::MOUSE_DOWN;
       break;
     case osgGA::GUIEventAdapter::SCROLL:
-    case osgGA::GUIEventAdapter::MOVE:
+      switch( ea.getScrollingMotion() )
+      {
+        case osgGA::GUIEventAdapter::SCROLL_UP:
+          event->delta.y() = 1;
+          break;
+        case osgGA::GUIEventAdapter::SCROLL_DOWN:
+          event->delta.y() = -1;
+          break;
+        default:
+          return false;
+      }
+
+      // osg sends two events for every scrolling motion. We don't need
+      // duplicate events, so lets ignore the second event with the same
+      // timestamp.
+      if( _last_scroll_time == ea.getTime() )
+        return window_at_cursor ? true : false;
+      _last_scroll_time = ea.getTime();
+
+      event->type = sc::Event::WHEEL;
       break;
+    case osgGA::GUIEventAdapter::MOVE:
+    {
+      canvas::WindowPtr last_mouse_over = _last_mouse_over.lock();
+      if( last_mouse_over != window_at_cursor && last_mouse_over )
+      {
+        sc::MouseEventPtr move_event( new sc::MouseEvent(*event) );
+        move_event->type = sc::Event::MOUSE_LEAVE;
+        move_event->client_pos -= toOsg(last_mouse_over->getPosition());
+        move_event->local_pos = move_event->client_pos;
 
+        last_mouse_over->handleEvent(move_event);
+      }
+      _last_mouse_over = window_at_cursor;
+      event->type = sc::Event::MOUSE_MOVE;
+      break;
+    }
     case osgGA::GUIEventAdapter::RELEASE:
-      if( !_last_push.expired() )
-        return false;
+    {
+      event->type = sc::Event::MOUSE_UP;
 
-      target_window = _last_push.lock();
+      canvas::WindowPtr last_push = _last_push.lock();
       _last_push.reset();
-      break;
 
+      if( last_push && last_push != target_window )
+      {
+        // Leave old window
+        sc::MouseEventPtr leave_event( new sc::MouseEvent(*event) );
+        leave_event->type = sc::Event::MOUSE_LEAVE;
+        leave_event->client_pos -= toOsg(last_push->getPosition());
+        leave_event->local_pos = leave_event->client_pos;
+
+        last_push->handleEvent(leave_event);
+      }
+      break;
+    }
     case osgGA::GUIEventAdapter::DRAG:
       target_window = _last_push.lock();
+      event->type = sc::Event::DRAG;
       break;
 
     default:
@@ -317,24 +449,16 @@ bool GUIMgr::handleMouse(const osgGA::GUIEventAdapter& ea)
 
   if( target_window )
   {
-    event.dx = event.x - _last_x;
-    event.dy = event.y - _last_y;
-
-    _last_x = event.x;
-    _last_y = event.y;
-
-    // Let the event position be always relative to the top left window corner
-    event.x -= target_window->getRegion().x();
-    event.y -= target_window->getRegion().y();
-
-    return target_window->handleMouseEvent(event);
+    event->client_pos -= toOsg(target_window->getPosition());
+    event->local_pos = event->client_pos;
+    return target_window->handleEvent(event);
   }
   else
     return false;
 }
 
 //------------------------------------------------------------------------------
-void GUIMgr::handleResize(int x, int y, int width, int height)
+void DesktopGroup::handleResize(int x, int y, int width, int height)
 {
   if( _width == width && _height == height )
     return;
@@ -350,3 +474,104 @@ void GUIMgr::handleResize(int x, int y, int width, int height)
     0, _height, 0, 1
   ));
 }
+
+//------------------------------------------------------------------------------
+void DesktopGroup::handleMouseMode(SGPropertyNode* node)
+{
+  // pass-through indicates events should pass through to the UI
+  _handle_events = fgGetNode("/input/mice/mouse[0]/mode", node->getIntValue())
+                     ->getBoolValue("pass-through");
+}
+
+//------------------------------------------------------------------------------
+GUIMgr::GUIMgr()
+{
+
+}
+
+//------------------------------------------------------------------------------
+canvas::WindowPtr GUIMgr::createWindow(const std::string& name)
+{
+  canvas::WindowPtr window = _desktop->createChild<canvas::Window>(name);
+  if( name.empty() )
+    window->set<std::string>
+    (
+      "id",
+      boost::lexical_cast<std::string>(window->getProps()->getIndex())
+    );
+  return window;
+}
+
+//------------------------------------------------------------------------------
+void GUIMgr::init()
+{
+  DesktopPtr desktop( new DesktopGroup );
+  desktop->handleResize
+  (
+    0,
+    0,
+    fgGetInt("/sim/startup/xsize"),
+    fgGetInt("/sim/startup/ysize")
+  );
+  _desktop = desktop;
+
+  _event_handler = new GUIEventHandler(desktop);
+  globals->get_renderer()
+         ->getViewer()
+         ->getEventHandlers()
+         // GUI is on top of everything so lets install as first event handler
+         .push_front( _event_handler );
+
+  simgear::canvas::Canvas::addPlacementFactory
+  (
+    "window",
+    boost::bind(&GUIMgr::addWindowPlacement, this, _1, _2)
+  );
+
+  _desktop->getProps()->fireCreatedRecursive();
+}
+
+//------------------------------------------------------------------------------
+void GUIMgr::shutdown()
+{
+  _desktop->destroy();
+  _desktop.reset();
+  simgear::canvas::Canvas::removePlacementFactory("window");
+
+  globals->get_renderer()
+         ->getViewer()
+         ->removeEventHandler( _event_handler );
+  _event_handler = 0;
+}
+
+//------------------------------------------------------------------------------
+void GUIMgr::update(double dt)
+{
+  _desktop->update(dt);
+}
+
+//------------------------------------------------------------------------------
+simgear::canvas::GroupPtr GUIMgr::getDesktop()
+{
+  return _desktop;
+}
+
+//------------------------------------------------------------------------------
+simgear::canvas::Placements
+GUIMgr::addWindowPlacement( SGPropertyNode* placement,
+                            simgear::canvas::CanvasPtr canvas )
+{
+  const std::string& id = placement->getStringValue("id");
+
+  simgear::canvas::Placements placements;
+  canvas::WindowPtr window = _desktop->getChild<canvas::Window>(id);
+  if( window )
+  {
+    window->setCanvasContent(canvas);
+    placements.push_back(
+      simgear::canvas::PlacementPtr(
+        new WindowPlacement(placement, window, canvas)
+    ));
+  }
+  return placements;
+}