Hi Robert,
osgWidget::WindowManager did nothing in its keyUp event, and in
particular didn't call any callbacks. Since I wanted to have callbacks
on keyUp, I copied what it does on keyDown, which works for me. I could
have just used keyDown and saved myself the trouble, but you know me... :-)
osgWidget::Input:
[Functional changes]
- Previously, the field would be filled with spaces up to its max
length, and typing would just replace the spaces. Also, there was a
_textLength variable that kept track of the real length of text in the
field, since the osgText::Text's length just reflected the length of
spaces+text entered. This was not great, as you could still select the
spaces with the mouse and it just feels hacky. So I changed it to only
contain the text entered, no spaces, and _textLength was removed since
it's now redundant (the osgText::Text's length is used instead).
- Fixed the selection size which (visually only) showed one more
character selected than what was really selected.
- Fixed selection by dragging the mouse, it would sometimes not select
the last character of the string.
- Cursor will now accurately reflect whether insert mode is activated
(block cursor) or we're in normal mode (line cursor) like in most editors.
- Implemented Ctrl-X (cut)
- Added a new clear() method that allows the field to be emptied
correctly. Useful for a command line interface, for example (hint, hint).
- Mouse and keyboard event handler methods would always return false,
which meant selecting with the mouse would also rotate the trackball,
and typing an 's' would turn on stats.
[Code cleanup]
- Renamed the (local) _selectionMin and _selectionMax variables which
are used in a lot of places, as the underscores would lead to think they
were members. Either I called them selection{Min|Max} or delete{Min|Max}
where it made more sense.
- Fixed some indenting which was at 3 spaces (inconsistently), I'm sure
I didn't catch all the lines where this was the case though.
- Put spaces between variable, operator and value where missing,
especially in for()s. Again I only did this where I made changes, there
are probably others left.
The result is that delete, backspace, Ctrl-X, Ctrl-C, Ctrl-V, and typing
behaviour should now be consistent with text editor conventions, whether
insert mode is enabled or not. I hope. :-)
Note, there's a nasty const_cast in there. Why isn't
osgText::Font::getGlyph() declared const?
Also, as a note, the current implementation of cut, copy and paste (in
addition to being Windows only, yuck) gets and puts the data into an
std::string, thus if the osgText::String in the field contains unicode
characters I think it won't work correctly. Perhaps someone could
implement a proper clipboard class that would be cross-platform and
support osgText::String (more precisely other languages like Chinese)
correctly? Cut, copy and paste are not critical to what I'm doing so I
won't invest the time to do that, but I just thought I'd mention it.
Thanks,
J-S
--
______________________________________________________
Jean-Sebastien Guay [email protected]
http://www.cm-labs.com/
http://whitestar02.webhop.org/
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
// Code by: Jeremy Moles (cubicool) 2007-2008
#ifndef OSGWIDGET_INPUT
#define OSGWIDGET_INPUT
#include <osgWidget/Label>
namespace osgWidget {
// This is a string of values we use to try and determine the best Y
// descent value (yoffset); you're welcome to use what works best for
// your font.
const std::string DESCENT_STRING("qpl");
class OSGWIDGET_EXPORT Input: public Label
{
public:
Input(const std::string& = "", const std::string& = "", unsigned int =
20);
virtual void parented (Window*);
virtual void positioned ();
virtual bool focus (const WindowManager*);
virtual bool unfocus (const WindowManager*);
virtual bool keyUp (int, int, const WindowManager*);
virtual bool keyDown (int, int, const WindowManager*);
virtual bool mouseDrag (double, double, const WindowManager*);
virtual bool mousePush (double x, double y, const WindowManager*);
virtual bool mouseRelease (double, double, const WindowManager*);
void setCursor (Widget*);
unsigned int calculateBestYOffset (const std::string& = "qgl");
void clear();
void setXOffset(point_type xo) {
_xoff = xo;
}
void setYOffset(point_type yo) {
_yoff = yo;
}
void setXYOffset(point_type xo, point_type yo) {
_xoff = xo;
_yoff = yo;
}
osg::Drawable* getCursor() {
return _cursor.get();
}
const osg::Drawable* getCursor() const {
return _cursor.get();
}
point_type getXOffset() const {
return _xoff;
}
point_type getYOffset() const {
return _yoff;
}
XYCoord getXYOffset() const {
return XYCoord(_xoff, _yoff);
}
protected:
virtual void _calculateSize(const XYCoord&);
void _calculateCursorOffsets();
point_type _xoff;
point_type _yoff;
unsigned int _index;
unsigned int _size;
unsigned int _cursorIndex;
unsigned int _maxSize;
std::vector<point_type> _offsets;
std::vector<unsigned int> _wordsOffsets;
std::vector<point_type> _widths;
osg::ref_ptr<Widget> _cursor;
bool _insertMode; // Insert was pressed -->
true --> typing will overwrite existing text
osg::ref_ptr<Widget> _selection;
unsigned int _selectionStartIndex;
unsigned int _selectionEndIndex;
unsigned int _selectionIndex;
point_type _mouseClickX;
};
}
#endif
// -*-c++-*- osgWidget - Code by: Jeremy Moles (cubicool) 2007-2008
#include <osg/io_utils>
#include <osgWidget/WindowManager>
#include <osgWidget/Input>
#ifdef WIN32
#include <windows.h>
#endif
namespace osgWidget {
class BlinkCursorCallback: public osg::Drawable::DrawCallback
{
public:
BlinkCursorCallback(const bool& insertMode)
: _insertMode(insertMode)
{
}
virtual void drawImplementation( osg::RenderInfo & ri,const osg::Drawable*
drawable ) const
{
static bool on = true;
static osg::Timer_t startTime = osg::Timer::instance()->tick();
osg::Timer_t now = osg::Timer::instance()->tick();
if
(osg::Timer::instance()->delta_s(startTime,now)>(_insertMode?0.125:0.25))
{
on = !on;
startTime = now;
}
if (on)
drawable->drawImplementation(ri);
}
protected:
const bool& _insertMode;
};
Input::Input(const std::string& name, const std::string& label, unsigned int
size):
Label(name, label),
_xoff(0.0f),
_yoff(0.0f),
_index(0),
_size(0),
_cursorIndex(0),
_maxSize(size),
_cursor(new Widget("cursor")),
_insertMode(false),
_selection(new Widget("selection")),
_selectionStartIndex(0),
_selectionEndIndex(0),
_selectionIndex(0),
_mouseClickX(0)
{
_text->setAlignment(osgText::Text::LEFT_BOTTOM_BASE_LINE);
_text->setKerningType(osgText::KERNING_NONE);
// Make the cursor un-copyable.
_cursor->setCanClone(false);
_cursor->setDataVariance(osg::Object::DYNAMIC);
_cursor->setColor(0.0f, 0.0f, 0.0f, 1.0f);
_selection->setCanClone(false);
_selection->setDataVariance(osg::Object::DYNAMIC);
setEventMask(
// For showing/hiding the "cursor."
EVENT_MASK_FOCUS |
// For keypresses, obviously.
EVENT_MASK_KEY |
// For "click" focusing.
EVENT_MOUSE_PUSH |
EVENT_MASK_MOUSE_DRAG
);
_offsets.resize(_text->getText().size()+1, 0.0f);
_widths.resize(_text->getText().size()+1, 1.0f);
_text->update();
_cursor->setDrawCallback( new BlinkCursorCallback(_insertMode) );
}
void Input::_calculateSize(const XYCoord& size) {
// An Input cannot currently set it's own size RELIABLY until the osgText
implementation
// is dratiscally improved. I'm getting wildly crazy results. :(
// point_type height = size.y() > _cursor->getHeight() ? size.y() :
_cursor->getHeight();
#if 0
point_type width = size.x() + _cursor->getWidth();
point_type height = _cursor->getHeight();
if(width > getWidth()) setWidth(osg::round(width));
if(height > getHeight()) setHeight(osg::round(height));
#endif
}
void Input::_calculateCursorOffsets() {
// Determine the "offset"
_offsets.resize(_text->getText().size()+1, 0.0f);
_widths.resize(_text->getText().size()+1, 1.0f);
if (_text->getText().size()==0)
{
_offsets[0] = 0;
_widths[0] = 1.f;
return;
}
osg::Vec3 pos = _text->getPosition();
osgText::Text::TextureGlyphQuadMap& tgqm =
const_cast<osgText::Text::TextureGlyphQuadMap&>(_text->getTextureGlyphQuadMap());
osgText::Text::TextureGlyphQuadMap::iterator tgqmi = tgqm.begin();
std::vector<osg::Vec2> coords;
std::vector<osgText::Font::Glyph*> glyphs;
for ( ; tgqmi != tgqm.end(); tgqmi++ )
{
const osgText::Text::GlyphQuads& gq = tgqmi->second;
//coords.insert(coords.end(),gq.getTransformedCoords(0).begin(),gq.getTransformedCoords(0).end());
coords.insert(coords.end(),gq.getCoords().begin(),gq.getCoords().end());
for (unsigned int i=0; i<gq.getGlyphs().size(); ++i)
{
glyphs.push_back(gq.getGlyphs().at(i));
}
}
std::list<unsigned int> keys;
for (unsigned int i=0; i<_text->getText().size(); ++i)
{
keys.push_back(_text->getText().at(i));
}
unsigned int idx=0;
osg::Vec2 lr;
osg::Vec2 ll;
while (!keys.empty())
{
unsigned int key = keys.front();
for (unsigned int i=0; i<glyphs.size(); ++i)
{
static osgText::Font::Glyph* previous_g = 0;
osgText::Font::Glyph* g = glyphs.at(i);
if (g->getGlyphCode()==key)
{
lr = coords[2 + (i * 4)];
ll = coords[1 + (i * 4)];
point_type width = lr.x() - ll.x();
_widths[idx] = width == 0 ? g->getHorizontalAdvance() : width;
_offsets[idx] = lr.x() + pos.x();
if (width == 0)
_offsets[idx] += g->getHorizontalAdvance();
++idx;
if (previous_g)
{
{
point_type& ref = _offsets[idx];
ref += previous_g->getHorizontalAdvance();
}
{
point_type& ref = _widths[idx];
ref += previous_g->getHorizontalAdvance();
}
}
previous_g = g;
glyphs.erase(glyphs.begin()+i);
coords.erase(coords.begin()+i*4);
coords.erase(coords.begin()+i*4);
coords.erase(coords.begin()+i*4);
coords.erase(coords.begin()+i*4);
break;
}
}
keys.pop_front();
}
_offsets[idx] = lr.x() + pos.x();
_widths[idx] = 1.f;
_wordsOffsets.clear();
for ( unsigned int i=0; i<_text->getText().size(); ++i )
{
while (i<_text->getText().size() && _text->getText().at(i)==' ') ++i;
if (i<_text->getText().size())_wordsOffsets.push_back(i);
while (i<_text->getText().size() && _text->getText().at(i)!=' ') ++i;
}
positioned();
}
bool Input::focus(const WindowManager*) {
_cursor->setColor(0.5f, 0.5f, 0.6f, 1.0f);
_selection->setColor(0.8f, 0.8f, 0.9f, 1.0f);
return true;
}
bool Input::unfocus(const WindowManager*) {
_cursor->setColor(0.0f, 0.0f, 0.0f, 0.0f);
_selection->setColor(0.0f, 0.0f, 0.0f, 0.0f);
return true;
}
void Input::parented(Window* parent) {
Label::parented(parent);
_cursor->setSize(_widths[_index], getHeight());
if(_cursorIndex) parent->getGeode()->setDrawable(_cursorIndex,
_cursor.get());
else _cursorIndex = parent->addDrawableAndGetIndex(_cursor.get());
if(_selectionIndex) parent->getGeode()->setDrawable(_selectionIndex,
_selection.get());
else _selectionIndex = parent->addDrawableAndGetIndex(_selection.get());
}
void Input::positioned()
{
point_type ln = static_cast<point_type>(_text->getLineCount());
ln = ln == 0.0f ? 1.0f : ln;
// point_type th = (_text->getCharacterHeight() * ln) +
(_text->getLineSpacing() * (ln - 1.0f));
point_type x = getX() + _xoff;
point_type y = getY() + _yoff;
// XYCoord size = getTextSize();
_text->setPosition(osg::Vec3(x, y, _calculateZ(LAYER_MIDDLE)));
point_type xoffset = _index > 0 ? _offsets[_index - 1] : 0.0f;
if (_insertMode)
{
if (_index < _text->getText().size())
{
_cursor->setSize(_widths[_index], getHeight());
}
else
{
// We're at the end of the string, perhaps the string is empty,
// so get the advance for any character, perhaps a large one, I
chose 'A'.
osgText::Font::Glyph* glyph =
const_cast<osgText::Font*>(_text->getFont())->getGlyph(osgText::FontResolution(_text->getFontWidth(),
_text->getFontHeight()), 'A');
_cursor->setSize(glyph->getHorizontalAdvance(), getHeight());
}
}
else
{
_cursor->setSize(1.f, getHeight());
}
_cursor->setOrigin(getX() + xoffset, getY() );
_cursor->setZ(_calculateZ(LAYER_MIDDLE-1));
unsigned int selectionMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int selectionMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (selectionMax - selectionMin > 0)
{
point_type xstart = selectionMin > 0 ? _offsets[selectionMin - 1] :
0.0f;
point_type xend = selectionMax > 0 ? _offsets[selectionMax - 1] :
0.0f;
_selection->setSize(xend-xstart, getHeight());
_selection->setOrigin(getX() + xstart, getY());
_selection->setZ(_calculateZ(LAYER_MIDDLE-2));
}
else
{
_selection->setSize(0, getHeight());
}
}
bool Input::keyUp(int key, int mask, const WindowManager*) {
return false;
}
bool Input::mouseDrag (double x, double y, const WindowManager*)
{
_mouseClickX += x;
x = _mouseClickX;
for ( unsigned int i = 0; i < _offsets.size(); ++i )
{
point_type offset1 = i > 0 ? _offsets.at(i-1) : 0;
point_type offset2 = _offsets.at(i);
if (x >= offset1 && x <= offset2 ||
i == _offsets.size() - 1) // If we're at the last one, obviously
it will be there.
{
_selectionEndIndex = _index = i;
positioned();
break;
}
}
return true;
}
bool Input::mousePush (double x, double y, const WindowManager* wm)
{
double offset = getOrigin().x();
Window* window = getParent();
if (window)
{
offset += window->getOrigin().x();
}
x -= offset;
_mouseClickX = x;
for ( unsigned int i = 0; i < _offsets.size(); ++i )
{
point_type offset1 = i > 0 ? _offsets.at(i-1) : 0;
point_type offset2 = _offsets.at(i);
if (x >= offset1 && x <= offset2 ||
i == _offsets.size() - 1) // If we're at the last one, obviously
it will be there.
{
_selectionStartIndex = _selectionEndIndex = _index = i;
positioned();
break;
}
}
return true;
}
bool Input::mouseRelease(double, double, const WindowManager*)
{
return true;
}
bool Input::keyDown(int key, int mask, const WindowManager*)
{
osgText::String& s = _text->getText();
switch (key)
{
case osgGA::GUIEventAdapter::KEY_Left:
if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL)
{
bool found = false;
for (unsigned int i = 0; i < _wordsOffsets.size() - 1; ++i)
{
if (_wordsOffsets.at(i) < _index && _index <=
_wordsOffsets.at(i+1))
{
found = true;
_index = _wordsOffsets.at(i);
break;
}
}
if (!found && _wordsOffsets.size())
{
_index = _wordsOffsets.at(_wordsOffsets.size()-1);
}
}
else
if (_index > 0)
{
--_index;
}
if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT)
{
_selectionEndIndex = _index;
}
else
{
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
case osgGA::GUIEventAdapter::KEY_Right:
if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL)
{
bool found = false;
for (unsigned int i = 0; i < _wordsOffsets.size() - 1; ++i)
{
if (_wordsOffsets.at(i) <= _index && _index <
_wordsOffsets.at(i+1))
{
found = true;
_index = _wordsOffsets.at(i+1);
break;
}
}
if (!found && _wordsOffsets.size())
{
_index = _wordsOffsets.at(_wordsOffsets.size()-1);
}
}
else
if (_index < s.size())
{
++_index;
}
if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT)
{
_selectionEndIndex = _index;
}
else
{
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
case osgGA::GUIEventAdapter::KEY_Home:
_index = 0;
if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT)
{
_selectionEndIndex = _index;
}
else
{
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
case osgGA::GUIEventAdapter::KEY_End:
_index = s.size();
if (mask & osgGA::GUIEventAdapter::MODKEY_SHIFT)
{
_selectionEndIndex = _index;
}
else
{
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
case osgGA::GUIEventAdapter::KEY_Insert:
_insertMode = !_insertMode;
break;
case osgGA::GUIEventAdapter::KEY_Delete:
{
unsigned int deleteMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int deleteMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (deleteMax - deleteMin > 0)
{
}
else if (mask & osgGA::GUIEventAdapter::MODKEY_CTRL)
{
deleteMin = 0;
deleteMax = 0;
for (unsigned int i =0; i < _wordsOffsets.size() - 1; ++i)
{
if (_wordsOffsets.at(i) <= _index && _index <
_wordsOffsets.at(i+1))
{
deleteMin = _wordsOffsets.at(i);
deleteMax = _wordsOffsets.at(i+1);
break;
}
}
}
else if (_index < s.size())
{
deleteMin = _index;
deleteMax = _index + 1;
}
if (deleteMin != deleteMax)
s.erase(s.begin() + deleteMin, s.begin() + deleteMax);
_text->update();
_calculateCursorOffsets();
_index = deleteMin;
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
case osgGA::GUIEventAdapter::KEY_BackSpace:
{
unsigned int deleteMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int deleteMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (deleteMax - deleteMin > 0)
{
}
else if(_index >= 1)
{
deleteMin = _index - 1;
deleteMax = _index;
}
if (deleteMin != deleteMax)
s.erase(s.begin() + deleteMin, s.begin() + deleteMax);
_text->update();
_calculateCursorOffsets();
_index = deleteMin;
_selectionStartIndex = _selectionEndIndex = _index;
}
break;
default:
if(key > 255 || _index >= _maxSize) return false;
if (((key=='v' || key=='V') && (mask &
osgGA::GUIEventAdapter::MODKEY_CTRL)) || (key==22))
{
std::string data;
// Data from clipboard
#ifdef WIN32
if (::OpenClipboard(NULL))
{
HANDLE hData = ::GetClipboardData( CF_TEXT );
char* buff = (char*)::GlobalLock( hData );
if (buff) data = buff;
::GlobalUnlock( hData );
::CloseClipboard();
}
#endif
if (!data.empty())
{
unsigned int deleteMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int deleteMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (deleteMax - deleteMin > 0)
{
data = data.substr(0, _maxSize-s.size()-(deleteMax -
deleteMin));
s.erase(s.begin() + deleteMin, s.begin() + deleteMax);
s.insert(s.begin() + deleteMin, data.begin(), data.end());
_index = deleteMin + data.size();
}
else
{
data = data.substr(0, _maxSize-s.size());
s.insert(s.begin() + _index, data.begin(), data.end());
_index += data.length();
}
_selectionStartIndex = _selectionEndIndex = _index;
_text->update();
_calculateCursorOffsets();
_calculateSize(getTextSize());
getParent()->resize();
return false;
}
}
else
if (((key=='c' || key=='C' || key=='x' || key=='X') && (mask &
osgGA::GUIEventAdapter::MODKEY_CTRL)) || (key==3) || (key==24))
{
unsigned int selectionMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int selectionMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (selectionMax - selectionMin > 0)
{
std::string data;
data.insert(data.begin(), s.begin() + selectionMin, s.begin() +
selectionMax);
// Data to clipboard
#ifdef WIN32
if(::OpenClipboard(NULL))
{
::EmptyClipboard();
HGLOBAL clipbuffer = ::GlobalAlloc(GMEM_DDESHARE,
data.length()+1);
char* buffer = (char*)::GlobalLock(clipbuffer);
strcpy(buffer, data.c_str());
::GlobalUnlock(clipbuffer);
::SetClipboardData(CF_TEXT,clipbuffer);
::CloseClipboard();
}
#endif
if (key=='x' || key=='X' || key == 24)
{
s.erase(s.begin() + selectionMin, s.begin() + selectionMax);
_index = selectionMin;
_selectionStartIndex = _selectionEndIndex = _index;
_text->update();
_calculateCursorOffsets();
_calculateSize(getTextSize());
getParent()->resize();
}
}
return false;
}
{
// If something is selected, we need to delete it and insert the
character there.
unsigned int deleteMin =
osg::minimum(_selectionStartIndex,_selectionEndIndex);
unsigned int deleteMax =
osg::maximum(_selectionStartIndex,_selectionEndIndex);
if (deleteMax - deleteMin > 0)
{
s.erase(s.begin() + deleteMin, s.begin() + deleteMax);
_text->update();
_calculateCursorOffsets();
_index = deleteMin;
_selectionStartIndex = _selectionEndIndex = _index;
}
}
if (_insertMode && _index < s.size())
{
s[_index] = key;
}
else
{
if (_index < _maxSize)
s.insert(s.begin() + _index, key);
}
_text->update();
_calculateCursorOffsets();
_index++;
_selectionStartIndex = _selectionEndIndex = _index;
}
_calculateSize(getTextSize());
getParent()->resize();
return true;
}
void Input::setCursor(Widget*) {
}
unsigned int Input::calculateBestYOffset(const std::string& s)
{
if (!_text->getFont()) return 0;
const osgText::FontResolution fr(static_cast<unsigned
int>(_text->getCharacterHeight()),
static_cast<unsigned
int>(_text->getCharacterHeight()));
osgText::String utf(s);
unsigned int descent = 0;
for(osgText::String::iterator i = utf.begin(); i != utf.end(); i++) {
osgText::Font* font =
const_cast<osgText::Font*>(_text->getFont());
osgText::Font::Glyph* glyph = font->getGlyph(fr, *i);
unsigned int d =
abs((int)glyph->getHorizontalBearing().y());
if(d > descent) descent = d;
}
return descent;
}
void Input::clear()
{
setLabel("");
_text->update();
_calculateCursorOffsets();
_index = 0;
_selectionStartIndex = _selectionEndIndex = _index;
_selectionIndex = _index;
_cursorIndex = _index;
_calculateSize(getTextSize());
getParent()->resize();
}
}
// -*-c++-*- osgWidget - Code by: Jeremy Moles (cubicool) 2007-2008
// $Id: WindowManager.cpp 66 2008-07-14 21:54:09Z cubicool $
#include <iostream>
#include <algorithm>
#include <osg/io_utils>
#include <osgWidget/Types>
#include <osgWidget/Util>
#include <osgWidget/WindowManager>
#include <osgWidget/Lua>
#include <osgWidget/Python>
#include <osgWidget/Box>
#include <osgWidget/Label>
namespace osgWidget {
WindowManager::WindowManager(
osgViewer::View* view,
point_type width,
point_type height,
unsigned int nodeMask,
unsigned int flags
):
_width (width),
_height (height),
_windowWidth (width),
_windowHeight (height),
_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;
}
if(_flags & WM_USE_RENDERBINS)
getOrCreateStateSet()->setMode(GL_DEPTH_TEST, false);
// 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/VeraMono.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);
}
getOrCreateStateSet()->setMode(
GL_BLEND,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE
);
getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
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<Widget*>(ei);
Window* window = dynamic_cast<Window*>(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;
// TODO: This is the old way; it didn't allow Event handler code to call
grabFocus().
// 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 _lastPush->callMethodAndCallbacks(ev);
}
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;
y -= _lastY;
}
void WindowManager::_updatePickWindow(const WidgetList* wl, point_type x,
point_type y) {
Label* label = dynamic_cast<Label*>(_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<Window*>(getChild(i));
if(!window) return;
// Update Window's index
for(Iterator w = begin(); w != end(); w++) {
if(w->get()->_index >= i) w->get()->_index++;
}
_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 numChildren) {
for (unsigned int i = start; i < start+numChildren; i++)
{
Window* window = getByIndex(i);
if(!window) continue;
if(_remove(window)) {
window->_index = 0;
window->unmanaged(this);
}
}
// Update Window's index
for(Iterator w = begin(); w != end(); w++) {
if(w->get()->_index >= start) w->get()->_index -= numChildren;
}
}
// 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<Window*>(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<Widget*>(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::pickAtXY(float x, float y, WidgetList& wl) {
Intersections intr;
if(!_view->computeIntersections(x, y, intr, _nodeMask)) return false;
typedef std::vector<osg::observer_ptr<Window> > WindowVector;
WindowVector windows;
Window* activeWin = 0;
for(Intersections::iterator i = intr.begin(); i != intr.end(); i++) {
Window* win = dynamic_cast<Window*>(i->nodePath.back()->getParent(0));
if(
!win ||
(win->getVisibilityMode() == Window::VM_PARTIAL &&
!win->isPointerXYWithinVisible(x, y))
) {
continue;
}
if(activeWin != win) {
activeWin = win;
windows.push_back(win);
}
}
if(!windows.size()) return false;
std::sort(windows.begin(), windows.end(), WindowBinNumberCompare());
for(WindowVector::iterator i = windows.begin(); i != windows.end(); i++) {
warn() << "- " << i->get()->getName() << " " <<
i->get()->getOrCreateStateSet()->getBinNumber() << std::endl;
}
warn() << std::endl;
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 it = begin(); it != end(); it++) if(it->valid()) {
Window* w = it->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 = 1.0f / (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();
}
}
// Returns the application window coordinates of the WindowManager XY position.
XYCoord WindowManager::windowXY(double x, double y) const {
return XYCoord((_windowWidth / _width) * x, (_windowHeight / _height) * y);
}
// Returns the WindowManager coordinates of the application window XY position.
XYCoord WindowManager::localXY(double x, double y) const {
return XYCoord((_width / _windowWidth) * x, (_height / _windowHeight) * y);
}
// 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) {
if(_focused.valid()) {
Event ev(this, EVENT_KEY_UP);
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;
}
// A convenience wrapper for creating a proper orthographic camera using the
current
// width and height.
osg::Camera* WindowManager::createParentOrthoCamera() {
osg::Camera* camera = createOrthoCamera(_width, _height);
camera->addChild(this);
return camera;
}
}
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org