From f2c267f5d736652f602aa236554000aa484fe423 Mon Sep 17 00:00:00 2001 From: James Turner Date: Sat, 9 Feb 2013 16:05:54 +0000 Subject: [PATCH] MouseInput changes to support hover. This adds the framework for platform cursor implementations; Windows and X11 to be done. It also extend the mouse-input code to generate hover events suitable for driving tooltips. Note there should be no visible functionality change from this commit, since everything is inactive until fgdata changes are made. --- src/GUI/CMakeLists.txt | 6 +- src/GUI/CocoaMouseCursor.hxx | 28 +++ src/GUI/CocoaMouseCursor.mm | 93 ++++++++ src/GUI/MouseCursor.cxx | 203 ++++++++++++++++++ src/GUI/MouseCursor.hxx | 47 +++++ src/Input/FGMouseInput.cxx | 385 ++++++++++++++++++++++++---------- src/Input/FGMouseInput.hxx | 94 +-------- src/Main/fg_commands.cxx | 27 --- src/Main/fg_os.hxx | 2 +- src/Scenery/scenery.cxx | 5 + src/Scripting/NasalSys.cxx | 17 +- src/Scripting/NasalSys.hxx | 6 + src/Viewer/CameraGroup.cxx | 34 +-- src/Viewer/CameraGroup.hxx | 7 +- src/Viewer/FGEventHandler.cxx | 5 +- src/Viewer/renderer.cxx | 5 +- src/Viewer/renderer.hxx | 3 +- 17 files changed, 712 insertions(+), 255 deletions(-) create mode 100644 src/GUI/CocoaMouseCursor.hxx create mode 100644 src/GUI/CocoaMouseCursor.mm create mode 100644 src/GUI/MouseCursor.cxx create mode 100644 src/GUI/MouseCursor.hxx diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index d3bf72bdb..469117e4f 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES FGColor.cxx FileDialog.cxx PUIFileDialog.cxx + MouseCursor.cxx ) set(HEADERS @@ -39,11 +40,12 @@ set(HEADERS FGColor.hxx FileDialog.hxx PUIFileDialog.hxx + MouseCursor.hxx ) if (APPLE) - list(APPEND HEADERS FGCocoaMenuBar.hxx CocoaFileDialog.hxx) - list(APPEND SOURCES FGCocoaMenuBar.mm CocoaFileDialog.mm) + list(APPEND HEADERS FGCocoaMenuBar.hxx CocoaFileDialog.hxx CocoaMouseCursor.hxx) + list(APPEND SOURCES FGCocoaMenuBar.mm CocoaFileDialog.mm CocoaMouseCursor.mm) endif() flightgear_component(GUI "${SOURCES}" "${HEADERS}") diff --git a/src/GUI/CocoaMouseCursor.hxx b/src/GUI/CocoaMouseCursor.hxx new file mode 100644 index 000000000..6ee3483c9 --- /dev/null +++ b/src/GUI/CocoaMouseCursor.hxx @@ -0,0 +1,28 @@ +#ifndef FG_GUI_COCOA_MOUSE_CURSOR_HXX +#define FG_GUI_COCOA_MOUSE_CURSOR_HXX + +#include // for auto_ptr + +#include "MouseCursor.hxx" + +class CocoaMouseCursor : public FGMouseCursor +{ +public: + CocoaMouseCursor(); + virtual ~CocoaMouseCursor(); + + virtual void setCursor(Cursor aCursor); + + virtual void setCursorVisible(bool aVis); + + virtual void hideCursorUntilMouseMove(); + + virtual void mouseMoved(); + +private: + class CocoaMouseCursorPrivate; + std::auto_ptr d; +}; + + +#endif diff --git a/src/GUI/CocoaMouseCursor.mm b/src/GUI/CocoaMouseCursor.mm new file mode 100644 index 000000000..7cb631c3b --- /dev/null +++ b/src/GUI/CocoaMouseCursor.mm @@ -0,0 +1,93 @@ +#include "CocoaMouseCursor.hxx" + +#include +#include + +#include
+ +class CocoaMouseCursor::CocoaMouseCursorPrivate +{ +public: + Cursor activeCursorKey; + + typedef std::map CursorMap; + CursorMap cursors; +}; + +NSCursor* cocoaCursorForKey(FGMouseCursor::Cursor aKey) +{ + NSImage* img = nil; + + NSString* path = [NSString stringWithCString:globals->get_fg_root().c_str() + encoding:NSUTF8StringEncoding]; + path = [path stringByAppendingPathComponent:@"gui"]; + + switch (aKey) { + case FGMouseCursor::CURSOR_HAND: return [NSCursor pointingHandCursor]; + case FGMouseCursor::CURSOR_CROSSHAIR: return [NSCursor crosshairCursor]; + case FGMouseCursor::CURSOR_IBEAM: return [NSCursor IBeamCursor]; + + // FIXME - use a proper left-right cursor here. + case FGMouseCursor::CURSOR_LEFT_RIGHT: return [NSCursor resizeLeftRightCursor]; + + case FGMouseCursor::CURSOR_SPIN_CW: + path = [path stringByAppendingPathComponent:@"cursor-spin-cw.png"]; + img = [[NSImage alloc] initWithContentsOfFile:path]; + return [[NSCursor alloc] initWithImage:img hotSpot:NSMakePoint(16,16)]; + + case FGMouseCursor::CURSOR_SPIN_CCW: + path = [path stringByAppendingPathComponent:@"cursor-spin-cw.png"]; + img = [[NSImage alloc] initWithContentsOfFile:path]; + return [[NSCursor alloc] initWithImage:img hotSpot:NSMakePoint(16,16)]; + + default: return [NSCursor arrowCursor]; + } + +} + +CocoaMouseCursor::CocoaMouseCursor() : + d(new CocoaMouseCursorPrivate) +{ + +} + +CocoaMouseCursor::~CocoaMouseCursor() +{ + + +} + + +void CocoaMouseCursor::setCursor(Cursor aCursor) +{ + if (aCursor == d->activeCursorKey) { + return; + } + + d->activeCursorKey = aCursor; + if (d->cursors.find(aCursor) == d->cursors.end()) { + d->cursors[aCursor] = cocoaCursorForKey(aCursor); + [d->cursors[aCursor] retain]; + } + + [d->cursors[aCursor] set]; +} + +void CocoaMouseCursor::setCursorVisible(bool aVis) +{ + if (aVis) { + [NSCursor unhide]; + } else { + [NSCursor hide]; + } +} + +void CocoaMouseCursor::hideCursorUntilMouseMove() +{ + [NSCursor setHiddenUntilMouseMoves:YES]; +} + +void CocoaMouseCursor::mouseMoved() +{ + // no-op +} diff --git a/src/GUI/MouseCursor.cxx b/src/GUI/MouseCursor.cxx new file mode 100644 index 000000000..c3c720f12 --- /dev/null +++ b/src/GUI/MouseCursor.cxx @@ -0,0 +1,203 @@ +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "MouseCursor.hxx" + +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef SG_MAC +#include "CocoaMouseCursor.hxx" +#endif + +#include
+#include
+#include +#include
// for fgWarpMouse + +namespace +{ + +/** + * @brief when no native cursor implementation is available, use the osgViewer support. This + * has several limitations but is better than nothing + */ +class StockOSGCursor : public FGMouseCursor +{ +public: + StockOSGCursor() : + mCursorObscured(false), + mCursorVisible(true), + mCursor(osgViewer::GraphicsWindow::InheritCursor) + { + mActualCursor = mCursor; + + globals->get_renderer()->getViewer()->getWindows(mWindows); + } + + virtual void setCursor(Cursor aCursor) + { + mCursor = translateCursor(aCursor); + updateCursor(); + } + + virtual void setCursorVisible(bool aVis) + { + if (mCursorObscured == aVis) { + return; + } + + mCursorVisible = aVis; + updateCursor(); + } + + virtual void hideCursorUntilMouseMove() + { + if (mCursorObscured) { + return; + } + + mCursorObscured = true; + updateCursor(); + } + + virtual void mouseMoved() + { + if (mCursorObscured) { + mCursorObscured = false; + updateCursor(); + } + } +private: + osgViewer::GraphicsWindow::MouseCursor translateCursor(Cursor aCursor) + { + switch (aCursor) { + case CURSOR_HAND: return osgViewer::GraphicsWindow::HandCursor; + case CURSOR_CROSSHAIR: return osgViewer::GraphicsWindow::CrosshairCursor; + case CURSOR_IBEAM: return osgViewer::GraphicsWindow::TextCursor; + case CURSOR_LEFT_RIGHT: return osgViewer::GraphicsWindow::LeftRightCursor; + + default: return osgViewer::GraphicsWindow::InheritCursor; + } + } + + void updateCursor() + { + osgViewer::GraphicsWindow::MouseCursor cur = osgViewer::GraphicsWindow::InheritCursor; + if (mCursorObscured || !mCursorVisible) { + cur = osgViewer::GraphicsWindow::NoCursor; + } else { + cur = mCursor; + } + + if (cur == mActualCursor) { + return; + } + + std::cout << "actually setting cursor" << std::endl; + BOOST_FOREACH(osgViewer::GraphicsWindow* gw, mWindows) { + gw->setCursor(cur); + } + + mActualCursor = cur; + } + + bool mCursorObscured; + bool mCursorVisible; + osgViewer::GraphicsWindow::MouseCursor mCursor, mActualCursor; + std::vector mWindows; +}; + +} // of anonymous namespace + +static FGMouseCursor* static_instance = NULL; + +FGMouseCursor::FGMouseCursor() : + mAutoHideTimeMsec(10000) +{ +} + +FGMouseCursor* FGMouseCursor::instance() +{ + if (static_instance == NULL) { + #ifdef SG_MAC + if (true) { + static_instance = new CocoaMouseCursor; + } + #endif + + // windows + + // X11 + + if (static_instance == NULL) { + static_instance = new StockOSGCursor; + } + + // initialise mouse-hide delay from global properties + + globals->get_commands()->addCommand("set-cursor", static_instance, &FGMouseCursor::setCursorCommand); + } + + return static_instance; +} + +void FGMouseCursor::setAutoHideTimeMsec(unsigned int aMsec) +{ + mAutoHideTimeMsec = aMsec; +} + + +bool FGMouseCursor::setCursorCommand(const SGPropertyNode* arg) +{ + // JMT 2013 - I would prefer this was a seperate 'warp' command, but + // historically set-cursor has done both. + if (arg->hasValue("x") || arg->hasValue("y")) { + SGPropertyNode *mx = fgGetNode("/devices/status/mice/mouse/x", true); + SGPropertyNode *my = fgGetNode("/devices/status/mice/mouse/y", true); + int x = arg->getIntValue("x", mx->getIntValue()); + int y = arg->getIntValue("y", my->getIntValue()); + fgWarpMouse(x, y); + mx->setIntValue(x); + my->setIntValue(y); + } + + + Cursor c = cursorFromString(arg->getStringValue("cursor")); + setCursor(c); + return true; +} + +typedef struct { + const char * name; + FGMouseCursor::Cursor cursor; +} MouseCursorMap; + +const MouseCursorMap mouse_cursor_map[] = { + { "inherit", FGMouseCursor::CURSOR_ARROW }, + { "crosshair", FGMouseCursor::CURSOR_CROSSHAIR }, + { "left-right", FGMouseCursor::CURSOR_LEFT_RIGHT }, + { "hand", FGMouseCursor::CURSOR_HAND }, + { "text", FGMouseCursor::CURSOR_IBEAM }, + { 0, FGMouseCursor::CURSOR_ARROW } +}; + +FGMouseCursor::Cursor FGMouseCursor::cursorFromString(const char* cursor_name) +{ + for (unsigned int k = 0; mouse_cursor_map[k].name != 0; k++) { + if (!strcmp(mouse_cursor_map[k].name, cursor_name)) { + return mouse_cursor_map[k].cursor; + } + } + + SG_LOG(SG_GENERAL, SG_WARN, "unknown cursor:" << cursor_name); + return CURSOR_ARROW; +} diff --git a/src/GUI/MouseCursor.hxx b/src/GUI/MouseCursor.hxx new file mode 100644 index 000000000..d370c5938 --- /dev/null +++ b/src/GUI/MouseCursor.hxx @@ -0,0 +1,47 @@ + +// MouseCursor.hxx - abstract inteface for mouse cursor control + +#ifndef FG_GUI_MOUSE_CURSOR_HXX +#define FG_GUI_MOUSE_CURSOR_HXX 1 + +class SGPropertyNode; + +class FGMouseCursor +{ +public: + static FGMouseCursor* instance(); + + virtual void setAutoHideTimeMsec(unsigned int aMsec); + + enum Cursor + { + CURSOR_ARROW, + CURSOR_HAND, + CURSOR_CROSSHAIR, + CURSOR_IBEAM, ///< for editing text + CURSOR_IN_OUT, ///< arrow pointing into / out of the screen + CURSOR_LEFT_RIGHT, + CURSOR_UP_DOWN, + CURSOR_SPIN_CW, + CURSOR_SPIN_CCW + }; + + virtual void setCursor(Cursor aCursor) = 0; + + virtual void setCursorVisible(bool aVis) = 0; + + virtual void hideCursorUntilMouseMove() = 0; + + virtual void mouseMoved() = 0; + + static Cursor cursorFromString(const char* str); + +protected: + FGMouseCursor(); + + bool setCursorCommand(const SGPropertyNode* arg); + + unsigned int mAutoHideTimeMsec; +}; + +#endif // FG_GUI_MOUSE_CURSOR_HXX diff --git a/src/Input/FGMouseInput.cxx b/src/Input/FGMouseInput.cxx index f55e7c41c..bb76d10d5 100644 --- a/src/Input/FGMouseInput.cxx +++ b/src/Input/FGMouseInput.cxx @@ -28,20 +28,50 @@ #include "FGMouseInput.hxx" +#include #include + +#include +#include + +#include "FGButton.hxx" #include "Main/globals.hxx" +#include +#include +#include +#include +#include +#include using std::ios_base; +const int MAX_MICE = 1; +const int MAX_MOUSE_BUTTONS = 8; + +//////////////////////////////////////////////////////////////////////// + +/** + * List of currently pressed mouse button events + */ +class ActivePickCallbacks : public std::map > > { +public: + void update( double dt ); + void init( int button, const osgGA::GUIEventAdapter* ea ); +}; + + void ActivePickCallbacks::init( int button, const osgGA::GUIEventAdapter* ea ) { + osg::Vec2d windowPos; + flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y()); + // Get the list of hit callbacks. Take the first callback that // accepts the mouse button press and ignore the rest of them // That is they get sorted by distance and by scenegraph depth. // The nearest one is the first one and the deepest // (the most specialized one in the scenegraph) is the first. std::vector pickList; - if (!globals->get_renderer()->pick(pickList, ea)) { + if (!globals->get_renderer()->pick(pickList, windowPos)) { return; } @@ -65,42 +95,155 @@ void ActivePickCallbacks::update( double dt ) } } +//////////////////////////////////////////////////////////////////////// + + +/** + * Settings for a mouse mode. + */ +struct mouse_mode { + mouse_mode (); + virtual ~mouse_mode (); + FGMouseCursor::Cursor cursor; + bool constrained; + bool pass_through; + FGButton * buttons; + SGBindingList x_bindings[KEYMOD_MAX]; + SGBindingList y_bindings[KEYMOD_MAX]; +}; + + +/** + * Settings for a mouse. + */ +struct mouse { + mouse (); + virtual ~mouse (); + int x, y; + SGPropertyNode_ptr mode_node; + SGPropertyNode_ptr mouse_button_nodes[MAX_MOUSE_BUTTONS]; + int nModes; + int current_mode; + + SGTimeStamp timeSinceLastMove; + mouse_mode * modes; +}; + +//////////////////////////////////////////////////////////////////////// + +class FGMouseInput::FGMouseInputPrivate +{ +public: + FGMouseInputPrivate() : + haveWarped(false), + xSizeNode(fgGetNode("/sim/startup/xsize", false ) ), + ySizeNode(fgGetNode("/sim/startup/ysize", false ) ), + xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ), + yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ), + hideCursorNode(fgGetNode("/sim/mouse/hide-cursor", true ) ), + cursorTimeoutNode(fgGetNode("/sim/mouse/cursor-timeout-sec", true ) ), + rightButtonModeCycleNode(fgGetNode("/sim/mouse/right-button-mode-cycle-enabled", true)), + tooltipShowDelayNode( fgGetNode("/sim/mouse/tooltip-delay-msec", true) ), + clickTriggersTooltipNode( fgGetNode("/sim/mouse/click-shows-tooltip", true) ) + { + tooltipTimeoutDone = false; + } + + void centerMouseCursor(mouse& m) + { + // center the cursor + m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2; + m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2; + fgWarpMouse(m.x, m.y); + haveWarped = true; + } + + void doHoverPick(const osg::Vec2d& windowPos) + { + std::vector pickList; + SGPickCallback::Priority priority = SGPickCallback::PriorityScenery; + + if (globals->get_renderer()->pick(pickList, windowPos)) { + + std::vector::const_iterator i; + for (i = pickList.begin(); i != pickList.end(); ++i) { + if (i->callback->hover(windowPos, i->info)) { + return; + } + + // if the callback is of higher prioirty (lower enum index), + // record that. + if (i->callback->getPriority() < priority) { + priority = i->callback->getPriority(); + } + } + } // of have valid pick + + if (priority < SGPickCallback::PriorityScenery) { + FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_HAND); + } else { + // restore normal cursor + FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_ARROW); + } + + updateHover(); + } + + void updateHover() + { + SGPropertyNode_ptr args(new SGPropertyNode); + globals->get_commands()->execute("update-hover", args); + } + + + ActivePickCallbacks activePickCallbacks; + + mouse mice[MAX_MICE]; + + bool haveWarped; + bool tooltipTimeoutDone; + + SGPropertyNode_ptr xSizeNode; + SGPropertyNode_ptr ySizeNode; + SGPropertyNode_ptr xAccelNode; + SGPropertyNode_ptr yAccelNode; + SGPropertyNode_ptr hideCursorNode; + SGPropertyNode_ptr cursorTimeoutNode; + SGPropertyNode_ptr rightButtonModeCycleNode; + SGPropertyNode_ptr tooltipShowDelayNode; + SGPropertyNode_ptr clickTriggersTooltipNode; +}; + -#include -#include -#include //////////////////////////////////////////////////////////////////////// // The Mouse Input Implementation //////////////////////////////////////////////////////////////////////// -const FGMouseInput::MouseCursorMap FGMouseInput::mouse_cursor_map[] = { - { "none", MOUSE_CURSOR_NONE }, - { "inherit", MOUSE_CURSOR_POINTER }, - { "wait", MOUSE_CURSOR_WAIT }, - { "crosshair", MOUSE_CURSOR_CROSSHAIR }, - { "left-right", MOUSE_CURSOR_LEFTRIGHT }, - { 0, 0 } -}; +static FGMouseInput* global_mouseInput = NULL; + +static void mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea) +{ + if(global_mouseInput) + global_mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea); +} + +static void mouseMotionHandler(int x, int y, const osgGA::GUIEventAdapter* ea) +{ + if (global_mouseInput != 0) + global_mouseInput->doMouseMotion(x, y, ea); +} + -FGMouseInput * FGMouseInput::mouseInput = NULL; FGMouseInput::FGMouseInput() : - haveWarped(false), - xSizeNode(fgGetNode("/sim/startup/xsize", false ) ), - ySizeNode(fgGetNode("/sim/startup/ysize", false ) ), - xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ), - yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ), - hideCursorNode(fgGetNode("/sim/mouse/hide-cursor", true ) ), - cursorTimeoutNode(fgGetNode("/sim/mouse/cursor-timeout-sec", true ) ) + d(new FGMouseInputPrivate) { - if( mouseInput == NULL ) - mouseInput = this; + global_mouseInput = this; } FGMouseInput::~FGMouseInput() { - if( mouseInput == this ) - mouseInput = NULL; + global_mouseInput = NULL; } void FGMouseInput::init() @@ -117,7 +260,7 @@ void FGMouseInput::init() int j; for (int i = 0; i < MAX_MICE; i++) { SGPropertyNode * mouse_node = mouse_nodes->getChild("mouse", i, true); - mouse &m = bindings[i]; + mouse &m = d->mice[i]; // Grab node pointers std::ostringstream buf; @@ -140,19 +283,11 @@ void FGMouseInput::init() for (int j = 0; j < m.nModes; j++) { int k; - - // Read the mouse cursor for this mode SGPropertyNode * mode_node = mouse_node->getChild("mode", j, true); - const char * cursor_name = - mode_node->getStringValue("cursor", "inherit"); - m.modes[j].cursor = MOUSE_CURSOR_POINTER; - for (k = 0; mouse_cursor_map[k].name != 0; k++) { - if (!strcmp(mouse_cursor_map[k].name, cursor_name)) { - m.modes[j].cursor = mouse_cursor_map[k].cursor; - break; - } - } + // Read the mouse cursor for this mode + m.modes[j].cursor = FGMouseCursor::cursorFromString(mode_node->getStringValue("cursor", "inherit")); + // Read other properties for this mode m.modes[j].constrained = mode_node->getBoolValue("constrained", false); m.modes[j].pass_through = mode_node->getBoolValue("pass-through", false); @@ -163,7 +298,6 @@ void FGMouseInput::init() for (k = 0; k < MAX_MOUSE_BUTTONS; k++) { buf.seekp(ios_base::beg); buf << "mouse button " << k; - SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse button " << k); m.modes[j].buttons[k].init( mode_node->getChild("button", k), buf.str(), module ); } @@ -179,70 +313,68 @@ void FGMouseInput::init() void FGMouseInput::update ( double dt ) { - double cursorTimeout = cursorTimeoutNode ? cursorTimeoutNode->getDoubleValue() : 10.0; - - mouse &m = bindings[0]; + int cursorTimeoutMsec = d->cursorTimeoutNode->getDoubleValue() * 1000; + int tooltipDelayMsec = d->tooltipShowDelayNode->getIntValue(); + + mouse &m = d->mice[0]; int mode = m.mode_node->getIntValue(); if (mode != m.current_mode) { + // current mode has changed m.current_mode = mode; - m.timeout = cursorTimeout; + m.timeSinceLastMove.stamp(); + if (mode >= 0 && mode < m.nModes) { - fgSetMouseCursor(m.modes[mode].cursor); - m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2; - m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2; - fgWarpMouse(m.x, m.y); - haveWarped = true; + FGMouseCursor::instance()->setCursor(m.modes[mode].cursor); + d->centerMouseCursor(m); } else { - SG_LOG(SG_INPUT, SG_DEBUG, "Mouse mode " << mode << " out of range"); - fgSetMouseCursor(MOUSE_CURSOR_POINTER); + SG_LOG(SG_INPUT, SG_WARN, "Mouse mode " << mode << " out of range"); + FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_ARROW); } } - if ( hideCursorNode ==NULL || hideCursorNode->getBoolValue() ) { - if ( m.x != m.save_x || m.y != m.save_y ) { - m.timeout = cursorTimeout; - if (fgGetMouseCursor() == MOUSE_CURSOR_NONE) - fgSetMouseCursor(m.modes[mode].cursor); - } else { - m.timeout -= dt; - if ( m.timeout <= 0.0 ) { - fgSetMouseCursor(MOUSE_CURSOR_NONE); - m.timeout = 0.0; - } + if ( d->hideCursorNode == NULL || d->hideCursorNode->getBoolValue() ) { + // if delay is <= 0, disable tooltips + if ( !d->tooltipTimeoutDone && + (tooltipDelayMsec > 0) && + (m.timeSinceLastMove.elapsedMSec() > tooltipDelayMsec)) + { + d->tooltipTimeoutDone = true; + SGPropertyNode_ptr arg(new SGPropertyNode); + globals->get_commands()->execute("tooltip-timeout", arg); + } + + if ( m.timeSinceLastMove.elapsedMSec() > cursorTimeoutMsec) { + FGMouseCursor::instance()->hideCursorUntilMouseMove(); + m.timeSinceLastMove.stamp(); } - m.save_x = m.x; - m.save_y = m.y; } - - activePickCallbacks.update( dt ); + + d->activePickCallbacks.update( dt ); } -FGMouseInput::mouse::mouse () +mouse::mouse () : x(-1), y(-1), - save_x(-1), - save_y(-1), nModes(1), current_mode(0), - timeout(0), modes(NULL) { } -FGMouseInput::mouse::~mouse () +mouse::~mouse () { delete [] modes; } -FGMouseInput::mouse_mode::mouse_mode () - : cursor(MOUSE_CURSOR_POINTER), +mouse_mode::mouse_mode () + : cursor(FGMouseCursor::CURSOR_ARROW), constrained(false), pass_through(false), buttons(NULL) { } -FGMouseInput::mouse_mode::~mouse_mode () +mouse_mode::~mouse_mode () { // FIXME: memory leak // for (int i = 0; i < KEYMOD_MAX; i++) { @@ -252,30 +384,36 @@ FGMouseInput::mouse_mode::~mouse_mode () // for (j = 0; j < y_bindings[i].size(); j++) // delete bindings[i][j]; // } - delete [] buttons; + if (buttons) { + delete [] buttons; + } } void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea) { int modifiers = fgGetKeyModifiers(); - mouse &m = bindings[0]; + mouse &m = d->mice[0]; mouse_mode &mode = m.modes[m.current_mode]; - // Let the property manager know. if (b >= 0 && b < MAX_MOUSE_BUTTONS) m.mouse_button_nodes[b]->setBoolValue(updown == MOUSE_BUTTON_DOWN); - // Pass on to PUI and the panel if - // requested, and return if one of - // them consumes the event. + if (!d->rightButtonModeCycleNode->getBoolValue() && (b == 2)) { + // in spring-loaded look mode, ignore right clicks entirely here + return; + } + + // Pass on to PUI and the panel if + // requested, and return if one of + // them consumes the event. if (updown != MOUSE_BUTTON_DOWN) { // Execute the mouse up event in any case, may be we should // stop processing here? - while (!activePickCallbacks[b].empty()) { - activePickCallbacks[b].front()->buttonReleased(); - activePickCallbacks[b].pop_front(); + while (!d->activePickCallbacks[b].empty()) { + d->activePickCallbacks[b].front()->buttonReleased(); + d->activePickCallbacks[b].pop_front(); } } @@ -287,7 +425,7 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo // pui didn't want the click event so compute a // scenegraph intersection point corresponding to the mouse click if (updown == MOUSE_BUTTON_DOWN) { - activePickCallbacks.init( b, ea ); + d->activePickCallbacks.init( b, ea ); } } } @@ -300,54 +438,91 @@ void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindo } m.modes[m.current_mode].buttons[b].update( modifiers, 0 != updown, x, y); + + if (d->clickTriggersTooltipNode->getBoolValue()) { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setStringValue("reason", "click"); + globals->get_commands()->execute("tooltip-timeout", args); + d->tooltipTimeoutDone = true; + } } -void FGMouseInput::doMouseMotion (int x, int y) +void FGMouseInput::doMouseMotion (int x, int y, const osgGA::GUIEventAdapter* ea) { - // Don't call fgGetKeyModifiers() here, until we are using a - // toolkit that supports getting the mods from outside a key - // callback. Glut doesn't. - int modifiers = KEYMOD_NONE; + int modifiers = fgGetKeyModifiers(); - int xsize = xSizeNode ? xSizeNode->getIntValue() : 800; - int ysize = ySizeNode ? ySizeNode->getIntValue() : 600; + int xsize = d->xSizeNode ? d->xSizeNode->getIntValue() : 800; + int ysize = d->ySizeNode ? d->ySizeNode->getIntValue() : 600; - mouse &m = bindings[0]; + mouse &m = d->mice[0]; if (m.current_mode < 0 || m.current_mode >= m.nModes) { m.x = x; m.y = y; return; } - mouse_mode &mode = m.modes[m.current_mode]; - // Pass on to PUI if requested, and return - // if PUI consumed the event. + if (!d->activePickCallbacks[0].empty()) { + SG_LOG(SG_GENERAL, SG_INFO, "mouse-motion, have active pick callback"); + BOOST_FOREACH(SGPickCallback* cb, d->activePickCallbacks[0]) { + cb->mouseMoved(ea); + } + + m.x = x; + m.y = y; + return; + } + + m.timeSinceLastMove.stamp(); + FGMouseCursor::instance()->mouseMoved(); + + int modeIndex = m.current_mode; + // are we in spring-loaded look mode? + if (!d->rightButtonModeCycleNode->getBoolValue()) { + if (m.mouse_button_nodes[2]->getBoolValue()) { + // right mouse is down, force look mode + modeIndex = 3; + } + } + + if (modeIndex == 0) { + osg::Vec2d windowPos; + flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y()); + d->doHoverPick(windowPos); + // mouse has moved, so we may need to issue tooltip-timeout command + // again + d->tooltipTimeoutDone = false; + } + + mouse_mode &mode = m.modes[modeIndex]; + + // Pass on to PUI if requested, and return + // if PUI consumed the event. if (mode.pass_through && puMouse(x, y)) { m.x = x; m.y = y; return; } - if (haveWarped) + if (d->haveWarped) { // don't fire mouse-movement events at the first update after warping the mouse, // just remember the new mouse position - haveWarped = false; + d->haveWarped = false; } else { - // OK, PUI didn't want the event, - // so we can play with it. + // OK, PUI didn't want the event, + // so we can play with it. if (x != m.x) { int delta = x - m.x; - xAccelNode->setIntValue( delta ); + d->xAccelNode->setIntValue( delta ); for (unsigned int i = 0; i < mode.x_bindings[modifiers].size(); i++) mode.x_bindings[modifiers][i]->fire(double(delta), double(xsize)); } if (y != m.y) { int delta = y - m.y; - yAccelNode->setIntValue( -delta ); + d->yAccelNode->setIntValue( -delta ); for (unsigned int i = 0; i < mode.y_bindings[modifiers].size(); i++) mode.y_bindings[modifiers][i]->fire(double(delta), double(ysize)); } @@ -370,8 +545,7 @@ void FGMouseInput::doMouseMotion (int x, int y) if (need_warp) { fgWarpMouse(new_x, new_y); - haveWarped = true; - SG_LOG(SG_INPUT, SG_DEBUG, "Mouse warp: " << x << ", " << y << " => " << new_x << ", " << new_y); + d->haveWarped = true; } } @@ -382,16 +556,5 @@ void FGMouseInput::doMouseMotion (int x, int y) fgSetInt("/devices/status/mice/mouse/y", m.y = y); } -void FGMouseInput::mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea) -{ - if(mouseInput) - mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea); -} - -void FGMouseInput::mouseMotionHandler(int x, int y) -{ - if (mouseInput != 0) - mouseInput->doMouseMotion(x, y); -} diff --git a/src/Input/FGMouseInput.hxx b/src/Input/FGMouseInput.hxx index 01c6c2cbd..7e1634413 100644 --- a/src/Input/FGMouseInput.hxx +++ b/src/Input/FGMouseInput.hxx @@ -26,33 +26,19 @@ #ifndef _FGMOUSEINPUT_HXX #define _FGMOUSEINPUT_HXX -#ifndef __cplusplus -# error This library requires C++ -#endif - #include "FGCommonInput.hxx" -#include "FGButton.hxx" -#include -#include -#include -#include -#include +#include -/** - * List of currently pressed mouse button events - */ -class ActivePickCallbacks : public std::map > > { -public: - void update( double dt ); - void init( int button, const osgGA::GUIEventAdapter* ea ); -}; +#include +// forward decls +namespace osgGA { class GUIEventAdapter; } //////////////////////////////////////////////////////////////////////// // The Mouse Input Class //////////////////////////////////////////////////////////////////////// -class FGMouseInput : public SGSubsystem,FGCommonInput { +class FGMouseInput : public SGSubsystem, FGCommonInput { public: FGMouseInput(); virtual ~FGMouseInput(); @@ -60,74 +46,12 @@ public: virtual void init(); virtual void update( double dt ); - static const int MAX_MICE = 1; - static const int MAX_MOUSE_BUTTONS = 8; - + void doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea); + void doMouseMotion (int x, int y, const osgGA::GUIEventAdapter*); private: - void doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea); - void doMouseMotion (int x, int y); - static FGMouseInput * mouseInput; - static void mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter*); - static void mouseMotionHandler(int x, int y); - - ActivePickCallbacks activePickCallbacks; - /** - * Settings for a mouse mode. - */ - struct mouse_mode { - mouse_mode (); - virtual ~mouse_mode (); - int cursor; - bool constrained; - bool pass_through; - FGButton * buttons; - binding_list_t x_bindings[KEYMOD_MAX]; - binding_list_t y_bindings[KEYMOD_MAX]; - }; - - - /** - * Settings for a mouse. - */ - struct mouse { - mouse (); - virtual ~mouse (); - int x; - int y; - int save_x; - int save_y; - SGPropertyNode_ptr mode_node; - SGPropertyNode_ptr mouse_button_nodes[MAX_MOUSE_BUTTONS]; - int nModes; - int current_mode; - double timeout; - mouse_mode * modes; - }; - - // - // Map of all known cursor names - // This used to contain all the Glut cursors, but those are - // not defined by other toolkits. It now supports only the cursor - // images we actually use, in the interest of portability. Someday, - // it would be cool to write an OpenGL cursor renderer, with the - // cursors defined as textures referenced in the property tree. This - // list could then be eliminated. -Andy - // - const static struct MouseCursorMap { - const char * name; - int cursor; - } mouse_cursor_map[]; - - mouse bindings[MAX_MICE]; + class FGMouseInputPrivate; + std::auto_ptr d; - bool haveWarped; - - SGPropertyNode_ptr xSizeNode; - SGPropertyNode_ptr ySizeNode; - SGPropertyNode_ptr xAccelNode; - SGPropertyNode_ptr yAccelNode; - SGPropertyNode_ptr hideCursorNode; - SGPropertyNode_ptr cursorTimeoutNode; }; #endif diff --git a/src/Main/fg_commands.cxx b/src/Main/fg_commands.cxx index c0e388719..f1909c1fa 100644 --- a/src/Main/fg_commands.cxx +++ b/src/Main/fg_commands.cxx @@ -1108,32 +1108,6 @@ do_add_model (const SGPropertyNode * arg) return true; } - -/** - * Set mouse cursor coordinates and cursor shape. - */ -static bool -do_set_cursor (const SGPropertyNode * arg) -{ - if (arg->hasValue("x") || arg->hasValue("y")) { - SGPropertyNode *mx = fgGetNode("/devices/status/mice/mouse/x", true); - SGPropertyNode *my = fgGetNode("/devices/status/mice/mouse/y", true); - int x = arg->getIntValue("x", mx->getIntValue()); - int y = arg->getIntValue("y", my->getIntValue()); - fgWarpMouse(x, y); - mx->setIntValue(x); - my->setIntValue(y); - } - - SGPropertyNode *cursor = const_cast(arg)->getNode("cursor", true); - if (cursor->getType() != simgear::props::NONE) - fgSetMouseCursor(cursor->getIntValue()); - - cursor->setIntValue(fgGetMouseCursor()); - return true; -} - - /** * Built-in command: play an audio message (i.e. a wav file) This is * fire and forget. Call this once per message and it will get dumped @@ -1612,7 +1586,6 @@ static struct { { "open-browser", do_open_browser }, { "gui-redraw", do_gui_redraw }, { "add-model", do_add_model }, - { "set-cursor", do_set_cursor }, { "play-audio-sample", do_play_audio_sample }, { "presets-commit", do_presets_commit }, { "log-level", do_log_level }, diff --git a/src/Main/fg_os.hxx b/src/Main/fg_os.hxx index 74d2e468b..46bc235a7 100644 --- a/src/Main/fg_os.hxx +++ b/src/Main/fg_os.hxx @@ -82,7 +82,7 @@ typedef void (*fgWindowResizeHandler)(int w, int h); typedef void (*fgKeyHandler)(int key, int keymod, int mousex, int mousey); typedef void (*fgMouseClickHandler)(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter*); -typedef void (*fgMouseMotionHandler)(int x, int y); +typedef void (*fgMouseMotionHandler)(int x, int y, const osgGA::GUIEventAdapter*); void fgRegisterIdleHandler(fgIdleHandler func); void fgRegisterDrawHandler(fgDrawHandler func); diff --git a/src/Scenery/scenery.cxx b/src/Scenery/scenery.cxx index f9878ba05..1f6d4c3c2 100644 --- a/src/Scenery/scenery.cxx +++ b/src/Scenery/scenery.cxx @@ -47,9 +47,11 @@ #include #include #include +#include #include #include
+#include #include "tilemgr.hxx" #include "scenery.hxx" @@ -59,6 +61,9 @@ using namespace simgear; class FGGroundPickCallback : public SGPickCallback { public: + FGGroundPickCallback() : SGPickCallback(PriorityScenery) + { } + virtual bool buttonPressed(int button, const osgGA::GUIEventAdapter*, const Info& info) { // only on left mouse button diff --git a/src/Scripting/NasalSys.cxx b/src/Scripting/NasalSys.cxx index f9e29b9af..bf1a1a21f 100644 --- a/src/Scripting/NasalSys.cxx +++ b/src/Scripting/NasalSys.cxx @@ -28,6 +28,7 @@ #include #include #include +#include #include "NasalSys.hxx" #include "NasalSys_private.hxx" @@ -502,7 +503,7 @@ public: { _sys->setCmdArg(const_cast(aNode)); naRef args[1]; - args[0] = _sys->cmdArgGhost(); + args[0] = _sys->wrappedPropsNode(const_cast(aNode)); _sys->callMethod(_func, naNil(), 1, args, naNil() /* locals */); @@ -697,6 +698,20 @@ void FGNasalSys::init() postinitNasalGUI(_globals, _context); } +naRef FGNasalSys::wrappedPropsNode(SGPropertyNode* aProps) +{ + static naRef wrapNodeFunc = naNil(); + if (naIsNil(wrapNodeFunc)) { + nasal::Hash g(_globals, _context); + nasal::Hash props = g.get("props"); + wrapNodeFunc = props.get("wrapNode"); + } + + naRef args[1]; + args[0] = propNodeGhost(aProps); + return naCall(_context, wrapNodeFunc, 1, args, naNil(), naNil()); +} + void FGNasalSys::update(double) { if( NasalClipboard::getInstance() ) diff --git a/src/Scripting/NasalSys.hxx b/src/Scripting/NasalSys.hxx index 7a2048d7e..fc24f9959 100644 --- a/src/Scripting/NasalSys.hxx +++ b/src/Scripting/NasalSys.hxx @@ -64,6 +64,12 @@ public: void setCmdArg(SGPropertyNode* aNode); + /** + * create Nasal props.Node for an SGPropertyNode* + * This is the actual ghost, wrapped in a Nasal sugar class. + */ + naRef wrappedPropsNode(SGPropertyNode* aProps); + // Callbacks for command and timer bindings virtual bool handleCommand( const char* moduleName, const char* fileName, diff --git a/src/Viewer/CameraGroup.cxx b/src/Viewer/CameraGroup.cxx index 3d84717e5..255e675f4 100644 --- a/src/Viewer/CameraGroup.cxx +++ b/src/Viewer/CameraGroup.cxx @@ -27,6 +27,8 @@ #include "FGEventHandler.hxx" #include "WindowBuilder.hxx" #include "WindowSystemAdapter.hxx" + +#include #include #include #include @@ -1142,34 +1144,34 @@ Camera* getGUICamera(CameraGroup* cgroup) return info->getCamera(MAIN_CAMERA); } -static bool computeCameraIntersection(const CameraInfo* cinfo, - const osgGA::GUIEventAdapter* ea, + +static bool computeCameraIntersection(const CameraInfo* cinfo, const osg::Vec2d& windowPos, osgUtil::LineSegmentIntersector::Intersections& intersections) { using osgUtil::Intersector; using osgUtil::LineSegmentIntersector; - double x, y; - eventToWindowCoords(ea, x, y); - + if (!(cinfo->flags & CameraGroup::DO_INTERSECTION_TEST)) return false; const Camera* camera = cinfo->getCamera(MAIN_CAMERA); if ( !camera ) camera = cinfo->getCamera( GEOMETRY_CAMERA ); - if (camera->getGraphicsContext() != ea->getGraphicsContext()) - return false; + + // if (camera->getGraphicsContext() != ea->getGraphicsContext()) + // return false; const Viewport* viewport = camera->getViewport(); + SGRect viewportRect(viewport->x(), viewport->y(), + viewport->x() + viewport->width() - 1.0, + viewport->y() + viewport->height()- 1.0); + double epsilon = 0.5; - if (!(x >= viewport->x() - epsilon - && x < viewport->x() + viewport->width() -1.0 + epsilon - && y >= viewport->y() - epsilon - && y < viewport->y() + viewport->height() -1.0 + epsilon)) + if (!viewportRect.contains(windowPos.x(), windowPos.y(), epsilon)) return false; - Vec4d start(x, y, 0.0, 1.0); - Vec4d end(x, y, 1.0, 1.0); + Vec4d start(windowPos.x(), windowPos.y(), 0.0, 1.0); + Vec4d end(windowPos.x(), windowPos.y(), 1.0, 1.0); Matrix windowMat = viewport->computeWindowMatrix(); Matrix startPtMat = Matrix::inverse(camera->getProjectionMatrix() * windowMat); @@ -1200,12 +1202,12 @@ static bool computeCameraIntersection(const CameraInfo* cinfo, } bool computeIntersections(const CameraGroup* cgroup, - const osgGA::GUIEventAdapter* ea, + const osg::Vec2d& windowPos, osgUtil::LineSegmentIntersector::Intersections& intersections) { // test the GUI first const CameraInfo* guiCamera = cgroup->getGUICamera(); - if (guiCamera && computeCameraIntersection(guiCamera, ea, intersections)) + if (guiCamera && computeCameraIntersection(guiCamera, windowPos, intersections)) return true; // Find camera that contains event @@ -1217,7 +1219,7 @@ bool computeIntersections(const CameraGroup* cgroup, if (cinfo == guiCamera) continue; - if (computeCameraIntersection(cinfo, ea, intersections)) + if (computeCameraIntersection(cinfo, windowPos, intersections)) return true; } diff --git a/src/Viewer/CameraGroup.hxx b/src/Viewer/CameraGroup.hxx index 1bdcd02de..1d5bd0aa0 100644 --- a/src/Viewer/CameraGroup.hxx +++ b/src/Viewer/CameraGroup.hxx @@ -292,11 +292,6 @@ protected: } -namespace osgGA -{ -class GUIEventAdapter; -} - namespace flightgear { /** Get the osg::Camera that draws the GUI, if any, from a camera @@ -315,7 +310,7 @@ osg::Camera* getGUICamera(CameraGroup* cgroup); * @return true if any intersections are found */ bool computeIntersections(const CameraGroup* cgroup, - const osgGA::GUIEventAdapter* ea, + const osg::Vec2d& windowPos, osgUtil::LineSegmentIntersector::Intersections& intersections); /** Warp the pointer to coordinates in the GUI camera of a camera group. diff --git a/src/Viewer/FGEventHandler.cxx b/src/Viewer/FGEventHandler.cxx index b877cc746..aa3d5ac49 100644 --- a/src/Viewer/FGEventHandler.cxx +++ b/src/Viewer/FGEventHandler.cxx @@ -261,7 +261,7 @@ bool FGEventHandler::handle(const osgGA::GUIEventAdapter& ea, if (mouseWarped) return true; if (eventToViewport(ea, us, x, y) && mouseMotionHandler) - (*mouseMotionHandler)(x, y); + (*mouseMotionHandler)(x, y, &ea); return true; case osgGA::GUIEventAdapter::RESIZE: SG_LOG(SG_VIEW, SG_DEBUG, "FGEventHandler::handle: RESIZE event " << ea.getWindowHeight() << " x " << ea.getWindowWidth() << ", resizable: " << resizable); @@ -417,6 +417,7 @@ void eventToWindowCoords(const osgGA::GUIEventAdapter* ea, y = (double)traits->height - y; } +#if 0 void eventToWindowCoordsYDown(const osgGA::GUIEventAdapter* ea, double& x, double& y) { @@ -431,4 +432,6 @@ void eventToWindowCoordsYDown(const osgGA::GUIEventAdapter* ea, if (ea->getMouseYOrientation() == osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS) y = (double)traits->height - y; } +#endif + } diff --git a/src/Viewer/renderer.cxx b/src/Viewer/renderer.cxx index 2e8d8fe87..2584df141 100644 --- a/src/Viewer/renderer.cxx +++ b/src/Viewer/renderer.cxx @@ -1724,15 +1724,14 @@ FGRenderer::resize( int width, int height ) } bool -FGRenderer::pick(std::vector& pickList, - const osgGA::GUIEventAdapter* ea) +FGRenderer::pick(std::vector& pickList, const osg::Vec2& windowPos) { // wipe out the return ... pickList.clear(); typedef osgUtil::LineSegmentIntersector::Intersections Intersections; Intersections intersections; - if (!computeIntersections(CameraGroup::getDefault(), ea, intersections)) + if (!computeIntersections(CameraGroup::getDefault(), windowPos, intersections)) return false; for (Intersections::iterator hit = intersections.begin(), e = intersections.end(); diff --git a/src/Viewer/renderer.hxx b/src/Viewer/renderer.hxx index a064dcd30..959847408 100644 --- a/src/Viewer/renderer.hxx +++ b/src/Viewer/renderer.hxx @@ -61,8 +61,7 @@ public: /** Just pick into the scene and return the pick callbacks on the way ... */ - bool pick( std::vector& pickList, - const osgGA::GUIEventAdapter* ea ); + bool pick( std::vector& pickList, const osg::Vec2& windowPos); /** Get and set the OSG Viewer object, if any. */ -- 2.39.5