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

Reply via email to