// -*-c++-*- osgWidget - Code by: Jeremy Moles (cubicool) 2007-2008 // $Id: WindowManager.cpp 66 2008-07-14 21:54:09Z cubicool $ #include #include #include #include #include #include #include #include #include #include namespace osgWidget { WindowManager::WindowManager( osgViewer::View* view, point_type width, point_type height, unsigned int nodeMask, unsigned int flags ): _width (width), _height (height), _zNear (0.0f), _zFar (-1.0f), _numForeground (0.0f), _numBackground (0.0f), _flags (flags), _nodeMask (nodeMask), _view (view), _lastX (0.0f), _lastY (0.0f), _lastEvent (0), _lastPush (0), _lastVertical (PD_NONE), _lastHorizontal (PD_NONE), _focusMode (PFM_FOCUS), _leftDown (false), _middleDown (false), _rightDown (false), _scrolling (osgGA::GUIEventAdapter::SCROLL_NONE), _styleManager (new StyleManager()) { _name = generateRandomName("WindowManager"); if(_flags & WM_USE_LUA) { _lua = new LuaEngine(this); if(!_lua->initialize()) warn() << "Error creating LuaEngine." << std::endl; } if(_flags & WM_USE_PYTHON) { _python = new PythonEngine(this); if(!_python->initialize()) warn() << "Error creating PythonEngine." << std::endl; } // Setup our picking debug (is debug the right word here?) Window... if(_flags & WM_PICK_DEBUG) { _pickWindow = new Box("PickWindow", Box::VERTICAL); Label* label = new Label("PickLabel"); label->setFontSize(13); label->setFontColor(1.0f, 1.0f, 1.0f, 1.0f); label->setFont("fonts/monospace.ttf"); label->setPadding(5.0f); label->setCanFill(true); _pickWindow->getBackground()->setColor(0.0f, 0.0f, 0.0f, 0.85f); _pickWindow->addWidget(label); _pickWindow->setNodeMask(~_nodeMask); _pickWindow->removeEventMask(EVENT_MASK_FOCUS); _pickWindow->setStrata(Window::STRATA_FOREGROUND); addChild(_pickWindow.get()); _updatePickWindow(0, 0, 0); } if(!(_flags & WM_NO_BETA_WARN)) { Box* box = new Box("BetaWarningBox", Box::VERTICAL); Label* label = new Label("BetaWarning"); label->setFontSize(15); label->setFontColor(0.0f, 0.0f, 1.0f, 1.0f); label->setFont("fonts/arial.ttf"); label->setPadding(5.0f); label->setCanFill(true); label->setLabel("This is BETA software! Please see: http://osgwidget.googlecode.com"); box->getBackground()->setColor(1.0f, 0.7f, 0.0f, 1.0f); box->addWidget(label); box->setNodeMask(~_nodeMask); box->removeEventMask(EVENT_MASK_FOCUS); box->setStrata(Window::STRATA_BACKGROUND); box->setOrigin(0.0f, 0.0f); addChild(box); box->resizePercent(100.0f, 0.0f); } } WindowManager::WindowManager(const WindowManager& wm, const osg::CopyOp& co): osg::Switch(wm, co) { } WindowManager::~WindowManager() { if(_flags & WM_USE_LUA) _lua->close(); if(_flags & WM_USE_PYTHON) _python->close(); } void WindowManager::setEventFromInterface(Event& ev, EventInterface* ei) { Widget* widget = dynamic_cast(ei); Window* window = dynamic_cast(ei); if(widget) { ev._window = widget->getParent(); ev._widget = widget; } else if(window) ev._window = window; } bool WindowManager::_handleMousePushed(float x, float y, bool& down) { down = true; Event ev(this, EVENT_MOUSE_PUSH); WidgetList widgetList; if(!pickAtXY(x, y, widgetList)) return false; ev.makeMouse(x, y); _lastPush = getFirstEventInterface(widgetList, ev); if(!_lastPush) return false; bool handled = _lastPush->callMethodAndCallbacks(ev); if(_focusMode != PFM_SLOPPY) { if(ev._window) { Window* topmostWindow = ev._window->getTopmostParent(); setFocused(topmostWindow); if(ev._widget) topmostWindow->setFocused(ev._widget); } // If the user wants to be able to "unfocus" the last Window. else if(_focusMode == PFM_UNFOCUS) setFocused(0); } return handled; } bool WindowManager::_handleMouseReleased(float x, float y, bool& down) { down = false; // If were were in a drag state, reset our boolean flag. // if(_lastDrag) _lastDrag = 0; if(!_lastPush) return false; // By design, we can only release an EventInterface we previously pressed. // Whether or not we're ON the EventInterface when the release occurs isn't important. Event ev(this, EVENT_MOUSE_RELEASE); setEventFromInterface(ev, _lastPush); bool handled = _lastPush->callMethodAndCallbacks(ev); _lastPush = 0; return handled; } void WindowManager::_getPointerXYDiff(float& x, float& y) { x -= _lastX; if(isInvertedY()) y = -(y - _lastY); else y -= _lastY; } void WindowManager::_updatePickWindow(const WidgetList* wl, point_type x, point_type y) { Label* label = dynamic_cast(_pickWindow->getByName("PickLabel")); if(!wl) { setValue(0, false); return; } setValue(0, true); std::stringstream ss; point_type xdiff = x; point_type ydiff = y; _getPointerXYDiff(xdiff, ydiff); ss << "At XY Coords: " << x << ", " << _height - y << " ( diff " << xdiff << ", " << ydiff << " )" << std::endl ; const Window* parent = wl->back()->getParent(); ss << "Window: " << parent->getName() << " ( xyz " << parent->getPosition() << " )" << " { zRange " << parent->getZRange() << " }" << " < size " << parent->getSize() << " >" << " EventMask: " << std::hex << parent->getEventMask() << std::endl ; for(WidgetList::const_iterator i = wl->begin(); i != wl->end(); i++) { Widget* widget = i->get(); ss << " - " << widget->getName() << " ( xyz " << widget->getPosition() << " )" << " [ XYZ " << widget->getPosition() * parent->getMatrix() << " ] < size " << widget->getSize() << " >" << " EventMask: " << std::hex << widget->getEventMask() << std::endl ; } label->setLabel(ss.str()); XYCoord size = label->getTextSize(); _pickWindow->resize(size.x() + 10.0f, size.y() + 10.0f); _pickWindow->setOrigin(5.0f, _height - _pickWindow->getHeight() - 5.0f); _pickWindow->update(); } void WindowManager::childInserted(unsigned int i) { Window* window = dynamic_cast(getChild(i)); if(!window) return; _objects.push_back(window); window->_index = i; setFocused(window); window->setNodeMask(_nodeMask); window->managed(this); for(Window::Iterator w = window->begin(); w != window->end(); w++) if(w->valid()) { _styleManager->applyStyles(w->get()); } _styleManager->applyStyles(window); } void WindowManager::childRemoved(unsigned int start, unsigned int end) { while(start < end) { Window* window = getByIndex(start); if(!window) continue; if(_remove(window)) { window->_index = -1; window->unmanaged(this); } start++; } } // This method performs intersection testing at the given XY coords, and returns true if // any intersections were found. It will break after processing the first pickable Window // it finds. bool WindowManager::pickAtXY(float x, float y, WidgetList& wl) { Intersections intr; if(_view->computeIntersections(x, y, intr, _nodeMask)) { // Get the first Window at the XY coordinates; if you want a Window to be // non-pickable, set the NodeMask to something else. Window* activeWin = 0; // Iterate over every picked result and create a list of Widgets that belong // to that Window. for(Intersections::iterator i = intr.begin(); i != intr.end(); i++) { Window* win = dynamic_cast(i->nodePath.back()->getParent(0)); // Make sure that our window is valid, and that our pick is within the // "visible area" of the Window. if( (!win || win->getVisibilityMode() == Window::VM_PARTIAL) && !win->isPointerXYWithinVisible(x, y) ) continue; // Set our activeWin, so that we know when we've got all the Widgets // that belong to it. if(!activeWin) activeWin = win; // If we've found a new Widnow, break out! else if(activeWin != win) break; Widget* widget = dynamic_cast(i->drawable.get()); if(!widget) continue; // We need to return a list of every Widget that was picked, so // that the handler can operate on it accordingly. else wl.push_back(widget); } if(wl.size()) { // Potentially VERY expensive; only to be used for debugging. :) if(_flags & WM_PICK_DEBUG) _updatePickWindow(&wl, x, y); return true; } } if(_flags & WM_PICK_DEBUG) _updatePickWindow(0, x, y); return false; } bool WindowManager::setFocused(Window* window) { Event ev(this); ev._window = window; // Inform the previously focused Window that it is going to be unfocused. if(_focused.valid()) _focused->callMethodAndCallbacks(ev.makeType(EVENT_UNFOCUS)); _focused = window; if(!window || !window->canFocus()) return false; // Build a vector of every Window that is focusable, in the foreground, and in the // background. All these Windows are handled differently. Vector focusable; Vector bg; Vector fg; for(ConstIterator i = begin(); i != end(); i++) if(i->valid()) { Window* w = i->get(); if(w->getStrata() == Window::STRATA_FOREGROUND) fg.push_back(w); else if(w->getStrata() == Window::STRATA_BACKGROUND) bg.push_back(w); else focusable.push_back(w); } // After this call to sort, the internal objects will be arranged such that the // previously focused window is the first, followed by all other Windows in // descending order. std::sort(focusable.begin(), focusable.end(), WindowZCompare()); // This is the depth range for each Window. Each Window object must be informed of // the Z space allocated to it so that it can properly arrange it's children. We // add 2 additional Windows here for anything that should appear in the background // and foreground areas. matrix_type zRange = (_zNear - _zFar) / (focusable.size() + 2.0f); // Our offset for the following for() loop. unsigned int i = 3; // Handle all of our focusable Windows. for(Iterator w = focusable.begin(); w != focusable.end(); w++) { Window* win = w->get(); // Set our newly focused Window as the topmost element. if(*w == window) win->_z = -zRange * 2.0f; // Set the current Z of the remaining Windows and set their zRange so that // they can update their own children. else { win->_z = -zRange * i; i++; } } // Handled our special BACKGROUND Windows. for(Iterator w = bg.begin(); w != bg.end(); w++) w->get()->_z = -zRange * i; // Handle our special FOREGOUND Windows. for(Iterator w = fg.begin(); w != fg.end(); w++) w->get()->_z = -zRange; // Update every window, regardless. for(Iterator w = begin(); w != end(); w++) { Window* win = w->get(); win->_zRange = zRange; win->update(); } _focused->callMethodAndCallbacks(ev.makeType(EVENT_FOCUS)); return true; } void WindowManager::setPointerXY(float x, float y) { float xdiff = x; float ydiff = y; _getPointerXYDiff(xdiff, ydiff); // If ydiff isn't NEAR 0 (floating point booleans aren't 100% reliable, but that // doesn't matter in our case), assume we have either up or down movement. if(ydiff != 0.0f) _lastVertical = ydiff > 0.0f ? PD_UP : PD_DOWN; else _lastVertical = PD_NONE; // If xdiff isn't 0, assume we have either left or right movement. if(xdiff != 0.0f) _lastHorizontal = xdiff > 0.0f ? PD_RIGHT : PD_LEFT; else _lastHorizontal = PD_NONE; _lastX = x; _lastY = y; } void WindowManager::setStyleManager(StyleManager* sm) { _styleManager = sm; for(Iterator i = begin(); i != end(); i++) if(i->valid()) { Window* window = i->get(); for(Window::Iterator w = window->begin(); w != window->end(); w++) { if(!w->valid()) continue; _styleManager->applyStyles(w->get()); } _styleManager->applyStyles(window); } } void WindowManager::resizeAllWindows(bool visible) { for(Iterator i = begin(); i != end(); i++) if(i->valid()) { if(visible && !getValue(i->get()->_index)) continue; i->get()->resize(); } } // This is called by a ViewerEventHandler/MouseHandler (or whatever) as the pointer moves // around and intersects with objects. It also resets our state data (_widget, _leftDown, // etc.) The return value of this method is mostly useless. bool WindowManager::pointerMove(float x, float y) { WidgetList wl; Event ev(this); if(!pickAtXY(x, y, wl)) { if(_lastEvent) { setEventFromInterface(ev.makeMouse(x, y, EVENT_MOUSE_LEAVE), _lastEvent); _lastEvent->callMethodAndCallbacks(ev); } if(_focusMode == PFM_SLOPPY) setFocused(0); _lastEvent = 0; _leftDown = 0; _middleDown = 0; _rightDown = 0; return false; } EventInterface* ei = getFirstEventInterface(wl, ev.makeMouse(x, y, EVENT_MOUSE_OVER)); if(!ei) return false; if(_lastEvent != ei) { if(_lastEvent) { Event evLeave(this); evLeave.makeMouse(x, y, EVENT_MOUSE_LEAVE); setEventFromInterface(evLeave, _lastEvent); _lastEvent->callMethodAndCallbacks(evLeave); } _lastEvent = ei; if(_focusMode == PFM_SLOPPY && ev._window) setFocused(ev._window); _lastEvent->callMethodAndCallbacks(ev.makeMouse(x, y, EVENT_MOUSE_ENTER)); } ei->callMethodAndCallbacks(ev.makeMouse(x, y, EVENT_MOUSE_OVER)); return true; } bool WindowManager::pointerDrag(float x, float y) { WidgetList widgetList; Event ev(this); float xdiff = x; float ydiff = y; _getPointerXYDiff(xdiff, ydiff); ev.makeMouse(xdiff, ydiff, EVENT_MOUSE_DRAG); // If we're still in the drag state... if(_lastPush) { setEventFromInterface(ev, _lastPush); return _lastPush->callMethodAndCallbacks(ev); } return false; } bool WindowManager::mouseScroll(float x, float y) { WidgetList wl; if(!pickAtXY(x, y, wl)) return false; Event ev(this, EVENT_MOUSE_SCROLL); EventInterface* ei = getFirstEventInterface(wl, ev); if(!ei) return false; return ei->callMethodAndCallbacks(ev); } // Keypresses only go the focused Window. bool WindowManager::keyDown(int key, int mask) { if(_focused.valid()) { Event ev(this, EVENT_KEY_DOWN); ev.makeKey(key, mask); Widget* focusedWidget = _focused->getFocused(); ev._window = _focused.get(); ev._widget = focusedWidget; bool handled = false; if(focusedWidget) handled = focusedWidget->callMethodAndCallbacks(ev); if(!handled) return _focused->callMethodAndCallbacks(ev); else return true; } return false; } bool WindowManager::keyUp(int key, int mask) { return true; } // A convenience wrapper for creating a proper orthographic camera using the current // width and height. osg::Camera* WindowManager::createParentOrthoCamera() { osg::Camera* camera = 0; if(isInvertedY()) camera = createInvertedYOrthoCamera(_width, _height); else camera = createOrthoCamera(_width, _height); camera->addChild(this); return camera; } }