/* ----------------------------------------------------------------------------
 * VSTGUI for X11/LV2/PNG
 * Author: David Robillard
 * Released under the revised BSD license, as below
 * ----------------------------------------------------------------------------
 *
 * Based on:
 * ----------------------------------------------------------------------------
 * VSTGUIL: Graphical User Interface Framework for VST plugins on LINUX
 * Version: 0.1, Date: 2007/01/21
 * Author: kRAkEn/gORe
 *
 * Which was based on:
 * ----------------------------------------------------------------------------
 * VSTGUI: Graphical User Interface Framework for VST plugins
 * Version 3.0       $Date: 2005/08/12 12:45:00 $
 * ----------------------------------------------------------------------------
 * VSTGUI LICENSE
 * 2004, Steinberg Media Technologies, All Rights Reserved
 * ----------------------------------------------------------------------------
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of the Steinberg Media Technologies nor the names of
 *     its contributors may be used to endorse or promote products derived from
 *     this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE  OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#ifndef __vstcontrols__
#include "vstcontrols.h"
#endif

#include "vstkeycode.h"

namespace VSTGUI {

// some external variables (vstgui.cpp)
extern long gStandardFontSize [];
extern const char *gStandardFontName [];

//------------------------------------------------------------------------
// CControl
//------------------------------------------------------------------------
/*! @class CControl
This object manages the tag identification and the value of a control object.

Note:
Since version 2.1, when an object uses the transparency for its background and draws on it (tranparency area)
or the transparency area changes during different draws (CMovieBitmap ,...), the background will be false (not updated),
you have to rewrite the draw function in order to redraw the background and then call the draw of the object.
*/
CControl::CControl (const CRect &size, CControlListener *listener, long tag,
                    CBitmap *pBackground)
:    CView (size),
    listener (listener), tag (tag), oldValue (1), defaultValue (0.5f),
    value (0), vmin (0), vmax (1.f), wheelInc (0.1f), lastTicks (-1)
{
    delta = 500;

    if (delta < 250)
        delta = 250;

    setTransparency (false);
    setMouseEnabled (true);
    backOffset (0 ,0);

    setBackground (pBackground);
}

//------------------------------------------------------------------------
CControl::~CControl ()
{
}

//------------------------------------------------------------------------
void CControl::beginEdit ()
{
    // begin of edit parameter
    getFrame ()->setFocusView(this);
    getFrame ()->beginEdit (tag);
}

//------------------------------------------------------------------------
void CControl::endEdit ()
{
    // end of edit parameter
    getFrame ()->endEdit (tag);
}

//------------------------------------------------------------------------
bool CControl::isDirty () const
{
    if (oldValue != value || CView::isDirty ())
        return true;
    return false;
}

//------------------------------------------------------------------------
void CControl::setDirty (const bool val)
{
    CView::setDirty (val);
    if (val)
    {
        if (value != -1.f)
            oldValue = -1.f;
        else
            oldValue = 0.f;
    }
    else
        oldValue = value;
}

//------------------------------------------------------------------------
void CControl::setBackOffset (CPoint &offset)
{
    backOffset = offset;
}

//-----------------------------------------------------------------------------
void CControl::copyBackOffset ()
{
    backOffset (size.left, size.top);
}

//------------------------------------------------------------------------
void CControl::bounceValue ()
{
    if (value > vmax)
        value = vmax;
    else if (value < vmin)
        value = vmin;
}

//-----------------------------------------------------------------------------
bool CControl::checkDefaultValue (CDrawContext *pContext, long button)
{
    if (button == (kControl|kLButton))
    {
        // begin of edit parameter
        beginEdit ();

        value = getDefaultValue ();
        if (isDirty () && listener)
            listener->valueChanged (pContext, this);

        // end of edit parameter
        endEdit ();
        return true;
    }
    return false;
}

//-----------------------------------------------------------------------------
bool CControl::isDoubleClick ()
{
    long ticks = getFrame ()->getTicks ();
    if (lastTicks <= 0)
    {
        lastTicks = ticks;
        return false;
    }

    if (lastTicks + delta > ticks)
        lastTicks = 0;
    else
    {
        lastTicks = ticks;
        return false;
    }
    return true;
}

//------------------------------------------------------------------------
// COnOffButton
//------------------------------------------------------------------------
/*! @class COnOffButton
Define a button with 2 positions.
The pixmap includes the 2 subpixmaps (i.e the rectangle used for the display of this button is half-height of the pixmap).
When its value changes, the listener is called.
*/
COnOffButton::COnOffButton (const CRect &size, CControlListener *listener, long tag,
                            CBitmap *background, long style)
: CControl (size, listener, tag, background)
, style (style)
{}

//------------------------------------------------------------------------
COnOffButton::~COnOffButton ()
{}

//------------------------------------------------------------------------
void COnOffButton::draw (CDrawContext *pContext)
{
//    DBG ("COnOffButton::draw");

    CCoord off;

    if (value && pBackground)
        off = pBackground->getHeight () / 2;
    else
        off = 0;

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, CPoint (0, off));
        else
            pBackground->draw (pContext, size, CPoint (0, off));
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void COnOffButton::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();
    if (!(button & kLButton))
        return;

    if (listener && (button & (kAlt | kShift | kControl | kApple)))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    value = ((long)value) ? 0.f : 1.f;

    if (listener && style == kPostListenerUpdate)
    {
        // begin of edit parameter
        beginEdit ();

        listener->valueChanged (pContext, this);

        // end of edit parameter
        endEdit ();
    }

    doIdleStuff ();

    if (listener && style == kPreListenerUpdate)
    {
        // begin of edit parameter
        beginEdit ();

        listener->valueChanged (pContext, this);

        // end of edit parameter
        endEdit ();
    }
}


//------------------------------------------------------------------------
// CKnob
//------------------------------------------------------------------------
/*! @class CKnob
Define a knob with a given background and foreground handle.
The handle describes a circle over the background (between -45deg and +225deg).
By clicking Alt+Left Mouse the default value is used.
By clicking Alt+Left Mouse the value changes with a vertical move (version 2.1)
*/
CKnob::CKnob (const CRect &size, CControlListener *listener, long tag,
              CBitmap *background, CBitmap *handle, const CPoint &offset)
:    CControl (size, listener, tag, background), offset (offset), pHandle (handle)
{
    if (pHandle)
    {
        pHandle->remember ();
        inset = (long)((float)pHandle->getWidth () / 2.f + 2.5f);
    }
    else
        inset = 3;

    colorShadowHandle = kGreyCColor;
    colorHandle = kWhiteCColor;
    radius = (float)(size.right - size.left) / 2.f;

    rangeAngle = 1.f;
    setStartAngle ((float)(5.f * kPI / 4.f));
    setRangeAngle ((float)(-3.f * kPI / 2.f));
    zoomFactor = 1.5f;

    setWantsFocus (true);
}

//------------------------------------------------------------------------
CKnob::~CKnob ()
{
    if (pHandle)
        pHandle->forget ();
}

//------------------------------------------------------------------------
void CKnob::draw (CDrawContext *pContext)
{
//    DBG ("CKnob::draw");

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, offset);
        else
            pBackground->draw (pContext, size, offset);
    }
    drawHandle (pContext);
    setDirty (false);
}

//------------------------------------------------------------------------
void CKnob::drawHandle (CDrawContext *pContext)
{
    CPoint where;
    valueToPoint (where);

    if (pHandle)
    {
        long width  = (long)pHandle->getWidth ();
        long height = (long)pHandle->getHeight ();
        where.offset (size.left - width / 2, size.top - height / 2);

        CRect handleSize (0, 0, width, height);
        handleSize.offset (where.h, where.v);
        pHandle->drawTransparent (pContext, handleSize);
    }
    else
    {
        CPoint origin (size.width () / 2, size.height () / 2);

        where.offset (size.left - 1, size.top);
        origin.offset (size.left - 1, size.top);
        pContext->setFrameColor (colorShadowHandle);
        pContext->moveTo (where);
        pContext->lineTo (origin);

        where.offset (1, -1);
        origin.offset (1, -1);
        pContext->setFrameColor (colorHandle);
        pContext->moveTo (where);
        pContext->lineTo (origin);
    }
}

//------------------------------------------------------------------------
void CKnob::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();
    if (!(button & kLButton))
        return;

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    // check if default value wanted
    if (checkDefaultValue (pContext, button))
        return;

    float old = oldValue;
    CPoint firstPoint;
    bool  modeLinear = false;
    float fEntryState = value;
    float middle = (vmax - vmin) * 0.5f;
    float range = 200.f;
    float coef = (vmax - vmin) / range;
    long  oldButton = button;

    long mode    = kCircularMode;
    long newMode = getFrame ()->getKnobMode ();
    if (kLinearMode == newMode)
    {
        if (!(button & kAlt))
            mode = newMode;
    }
    else if (button & kAlt)
        mode = kLinearMode;

    if (mode == kLinearMode && (button & kLButton))
    {
        if (button & kShift)
            range *= zoomFactor;
        firstPoint = where;
        modeLinear = true;
        coef = (vmax - vmin) / range;
    }
    else
    {
        CPoint where2 (where);
        where2.offset (-size.left, -size.top);
        old = valueFromPoint (where2);
    }

    CPoint oldWhere (-1, -1);

    // begin of edit parameter
    beginEdit ();
    do
    {
        button = pContext->getMouseButtons ();
        if (where != oldWhere)
        {
            oldWhere = where;
            if (modeLinear)
            {
                CCoord diff = (firstPoint.v - where.v) + (where.h - firstPoint.h);
                if (button != oldButton)
                {
                    range = 200.f;
                    if (button & kShift)
                        range *= zoomFactor;

                    float coef2 = (vmax - vmin) / range;
                    fEntryState += diff * (coef - coef2);
                    coef = coef2;
                    oldButton = button;
                }
                value = fEntryState + diff * coef;
                bounceValue ();
            }
            else
            {
                where.offset (-size.left, -size.top);
                value = valueFromPoint (where);
                if (old - value > middle)
                    value = vmax;
                else if (value - old > middle)
                    value = vmin;
                else
                    old = value;
            }
            if (isDirty () && listener)
                listener->valueChanged (pContext, this);
        }
        getMouseLocation (pContext, where);
        doIdleStuff ();

    } while (button & kLButton);

    // end of edit parameter
    endEdit ();
}

//------------------------------------------------------------------------
bool CKnob::onWheel (CDrawContext *pContext, const CPoint &where, float distance)
{
    if (!bMouseEnabled)
        return false;

    long buttons = pContext->getMouseButtons ();
    if (buttons & kShift)
        value += 0.1f * distance * wheelInc;
    else
        value += distance * wheelInc;
    bounceValue ();

    if (isDirty () && listener)
    {
        // begin of edit parameter
        beginEdit ();

        listener->valueChanged (pContext, this);

        // end of edit parameter
        endEdit ();
    }
    return true;
}

//------------------------------------------------------------------------
long CKnob::onKeyDown (VstKeyCode& keyCode)
{
    switch (keyCode.virt)
    {
    case VKEY_UP :
    case VKEY_RIGHT :
    case VKEY_DOWN :
    case VKEY_LEFT :
        {
            float distance = 1.f;
            if (keyCode.virt == VKEY_DOWN || keyCode.virt == VKEY_LEFT)
                distance = -distance;

            if (keyCode.modifier & MODIFIER_SHIFT)
                value += 0.1f * distance * wheelInc;
            else
                value += distance * wheelInc;
            bounceValue ();

            if (isDirty () && listener)
            {
                // begin of edit parameter
                beginEdit ();

                listener->valueChanged (0, this);

                // end of edit parameter
                endEdit ();
            }
        } return 1;
    }
    return -1;
}

//------------------------------------------------------------------------
void CKnob::setStartAngle (float val)
{
    startAngle = val;
    compute ();
}

//------------------------------------------------------------------------
void CKnob::setRangeAngle (float val)
{
    rangeAngle = val;
    compute ();
}

//------------------------------------------------------------------------
void CKnob::compute ()
{
    aCoef = (vmax - vmin) / rangeAngle;
    bCoef = vmin - aCoef * startAngle;
    halfAngle = ((float)k2PI - fabsf (rangeAngle)) * 0.5f;
    setDirty ();
}

//------------------------------------------------------------------------
void CKnob::valueToPoint (CPoint &point) const
{
    float alpha = (value - bCoef) / aCoef;
    point.h = (long)(radius + cosf (alpha) * (radius - inset) + 0.5f);
    point.v = (long)(radius - sinf (alpha) * (radius - inset) + 0.5f);
}

//------------------------------------------------------------------------
float CKnob::valueFromPoint (CPoint &point) const
{
    float v;
    float alpha = (float)atan2 (radius - point.v, point.h - radius);
    if (alpha < 0.f)
        alpha += (float)k2PI;

    float alpha2 = alpha - startAngle;
    if (rangeAngle < 0)
    {
        alpha2 -= rangeAngle;
        float alpha3 = alpha2;
        if (alpha3 < 0.f)
            alpha3 += (float)k2PI;
        else if (alpha3 > k2PI)
            alpha3 -= (float)k2PI;
        if (alpha3 > halfAngle - rangeAngle)
            v = vmax;
        else if (alpha3 > -rangeAngle)
            v = vmin;
        else
        {
            if (alpha2 > halfAngle - rangeAngle)
                alpha2 -= (float)k2PI;
            else if (alpha2 < -halfAngle)
                alpha2 += (float)k2PI;
            v = aCoef * alpha2 + vmax;
        }
    }
    else
    {
        float alpha3 = alpha2;
        if (alpha3 < 0.f)
            alpha3 += (float)k2PI;
        else if (alpha3 > k2PI)
            alpha3 -= (float)k2PI;
        if (alpha3 > rangeAngle + halfAngle)
            v = vmin;
        else if (alpha3 > rangeAngle)
            v = vmax;
        else
        {
            if (alpha2 > rangeAngle + halfAngle)
                alpha2 -= (float)k2PI;
            else if (alpha2 < -halfAngle)
                alpha2 += (float)k2PI;
            v = aCoef * alpha2 + vmin;
        }
    }

    return v;
}

//------------------------------------------------------------------------
void CKnob::setColorShadowHandle (CColor color)
{
    colorShadowHandle = color;
    setDirty ();
}

//------------------------------------------------------------------------
void CKnob::setColorHandle (CColor color)
{
    colorHandle = color;
    setDirty ();
}

//------------------------------------------------------------------------
void CKnob::setHandleBitmap (CBitmap *bitmap)
{
    if (pHandle)
    {
        pHandle->forget ();
        pHandle = 0;
    }

    if (bitmap)
    {
        pHandle = bitmap;
        pHandle->remember ();
        inset = (long)((float)pHandle->getWidth () / 2.f + 2.5f);
    }
}


//------------------------------------------------------------------------
// CParamDisplay
//------------------------------------------------------------------------
/*! @class CParamDisplay
Define a rectangle view where a text-value can be displayed with a given font and color.
The user can specify its convert function (from float to char) by default the string format is "%2.2f".
The text-value is centered in the given rect.
*/
CParamDisplay::CParamDisplay (const CRect &size, CBitmap *background, const long style)
:    CControl (size, 0, 0, background), stringConvert (0), stringConvert2 (0), string2FloatConvert (0),
    horiTxtAlign (kCenterText), style (style), bTextTransparencyEnabled (true)
{
    backOffset (0, 0);

    fontID      = kNormalFont;
    txtFace     = kNormalFace;
    fontColor   = kWhiteCColor;
    backColor   = kBlackCColor;
    frameColor  = kBlackCColor;
    shadowColor = kRedCColor;
    userData    = 0;
    if (style & kNoDrawStyle)
        setDirty (false);
}

//------------------------------------------------------------------------
CParamDisplay::~CParamDisplay ()
{}

//------------------------------------------------------------------------
void CParamDisplay::setStyle (long val)
{
    if (style != val)
    {
        style = val;
        setDirty ();
    }
}

//------------------------------------------------------------------------
void CParamDisplay::draw (CDrawContext *pContext)
{
//    DBG ("CParamDisplay::draw");

    char string[256];
    string[0] = 0;

    if (stringConvert2)
        stringConvert2 (value, string, userData);
    else if (stringConvert)
        stringConvert (value, string);
    else
        sprintf (string, "%2.2f", value);

    drawText (pContext, string);
}

//------------------------------------------------------------------------
void CParamDisplay::drawText (CDrawContext *pContext, char *string, CBitmap *newBack)
{
    setDirty (false);

    if (style & kNoDrawStyle)
        return;

    // draw the background
    if (newBack)
    {
        if (bTransparencyEnabled)
            newBack->drawTransparent (pContext, size, backOffset);
        else
            newBack->draw (pContext, size, backOffset);
    }
    else if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, backOffset);
        else
            pBackground->draw (pContext, size, backOffset);
    }
    else
    {
        if (!bTransparencyEnabled)
        {
            pContext->setFillColor (backColor);
            pContext->fillRect (size);

            if (!(style & (k3DIn|k3DOut|kNoFrame)))
            {
                pContext->setFrameColor (frameColor);
                pContext->drawRect (size);
            }
        }
    }
    // draw the frame for the 3D effect
    if (style & (k3DIn|k3DOut))
    {
        if (style & k3DIn)
            pContext->setFrameColor (backColor);
        else
            pContext->setFrameColor (frameColor);
        CPoint p;
        pContext->moveTo (p (size.left, size.bottom));
        pContext->lineTo (p (size.left, size.top));
        pContext->lineTo (p (size.right + 1, size.top));

        if (style & k3DIn)
            pContext->setFrameColor (frameColor);
        else
            pContext->setFrameColor (backColor);
        pContext->moveTo (p (size.right, size.top + 1));
        pContext->lineTo (p (size.right, size.bottom));
        pContext->lineTo (p (size.left, size.bottom));
    }

    if (!(style & kNoTextStyle) && string)
    {
        CRect oldClip;
        pContext->getClipRect (oldClip);
        CRect newClip (size);
        newClip.bound (oldClip);
        pContext->setClipRect (newClip);
        pContext->setFont (fontID, 0, txtFace);

        // draw darker text (as shadow)
        if (style & kShadowText)
        {
            CRect newSize (size);
            newSize.offset (1, 1);
            pContext->setFontColor (shadowColor);
            pContext->drawString (string, newSize, !bTextTransparencyEnabled, horiTxtAlign);
        }
        pContext->setFontColor (fontColor);
        pContext->drawString (string, size, !bTextTransparencyEnabled, horiTxtAlign);
        pContext->setClipRect (oldClip);
    }
}

//------------------------------------------------------------------------
void CParamDisplay::setFont (CFont newFontID)
{
    // to force the redraw
    if (fontID != newFontID)
        setDirty ();
    fontID = newFontID;
}

//------------------------------------------------------------------------
void CParamDisplay::setTxtFace (CTxtFace newTxtFace)
{
    // to force the redraw
    if (txtFace != newTxtFace)
        setDirty ();
    txtFace = newTxtFace;
}

//------------------------------------------------------------------------
void CParamDisplay::setFontColor (CColor color)
{
    // to force the redraw
    if (fontColor != color)
        setDirty ();
    fontColor = color;
}

//------------------------------------------------------------------------
void CParamDisplay::setBackColor (CColor color)
{
    // to force the redraw
    if (backColor != color)
        setDirty ();
    backColor = color;
}

//------------------------------------------------------------------------
void CParamDisplay::setFrameColor (CColor color)
{
    // to force the redraw
    if (frameColor != color)
        setDirty ();
    frameColor = color;
}

//------------------------------------------------------------------------
void CParamDisplay::setShadowColor (CColor color)
{
    // to force the redraw
    if (shadowColor != color)
        setDirty ();
    shadowColor = color;
}

//------------------------------------------------------------------------
void CParamDisplay::setHoriAlign (CHoriTxtAlign hAlign)
{
    // to force the redraw
    if (horiTxtAlign != hAlign)
        setDirty ();
    horiTxtAlign = hAlign;
}

//------------------------------------------------------------------------
void CParamDisplay::setStringConvert (void (*convert) (float value, char *string))
{
    stringConvert = convert;
}

//------------------------------------------------------------------------
void CParamDisplay::setStringConvert (void (*convert) (float value, char *string,
                                      void *userDta), void *userData_)
{
    stringConvert2 = convert;
    userData = userData_;
}

//------------------------------------------------------------------------
void CParamDisplay::setString2FloatConvert (void (*convert) (char *string, float &output))
{
    string2FloatConvert = convert;
}

//------------------------------------------------------------------------
// CTextLabel
//------------------------------------------------------------------------
/*! @class CTextLabel
*/
CTextLabel::CTextLabel (const CRect& size, const char* txt, CBitmap* background, const long style)
: CParamDisplay (size, background, style)
, text (0)
{
    setText (txt);
}

//------------------------------------------------------------------------
CTextLabel::~CTextLabel ()
{
    freeText ();
}

//------------------------------------------------------------------------
void CTextLabel::freeText ()
{
    if (text)
        free (text);
    text = 0;
}

//------------------------------------------------------------------------
void CTextLabel::setText (const char* txt)
{
    freeText ();
    if (txt)
    {
        text = (char*)malloc (strlen (txt)+1);
        strcpy (text, txt);
    }
}

//------------------------------------------------------------------------
const char* CTextLabel::getText () const
{
    return text;
}

//------------------------------------------------------------------------
void CTextLabel::draw (CDrawContext *pContext)
{
//    DBG ("CTextLabel::draw");

    drawText (pContext, text);
    setDirty (false);
}


//------------------------------------------------------------------------
// CTextEdit
//------------------------------------------------------------------------
/*! @class CTextEdit
Define a rectangle view where a text-value can be displayed and edited with a given font and color.
The user can specify its convert function (from char to char). The text-value is centered in the given rect.
A pixmap can be used as background.
*/
CTextEdit::CTextEdit (const CRect &size, CControlListener *listener, long tag,
    const char *txt, CBitmap *background, const long style)
:    CParamDisplay (size, background, style), platformFontColor (0), platformControl (0),
    platformFont (0), editConvert (0), editConvert2 (0)
{
    this->listener = listener;
    this->tag = tag;

    if (txt)
        strcpy (text, txt);
    else
        strcpy (text, "");
    setWantsFocus (true);
}

//------------------------------------------------------------------------
CTextEdit::~CTextEdit ()
{}

//------------------------------------------------------------------------
void CTextEdit::setText (char *txt)
{
    if (txt)
    {
        if (strcmp (text, txt))
        {
            strcpy (text, txt);

            // to force the redraw
            setDirty ();
        }
    }
    else
    {
        if (strcmp (text, ""))
        {
            strcpy (text, "");

            // to force the redraw
            setDirty ();
        }
    }
}

//------------------------------------------------------------------------
void CTextEdit::getText (char *txt) const
{
    if (txt)
        strcpy (txt, text);
}

//------------------------------------------------------------------------
void CTextEdit::draw (CDrawContext *pContext)
{
//    DBG ("CTextEdit::draw");

    if (platformControl)
    {
        setDirty (false);
        return;
    }

    char string[256];
    string[0] = 0;

    if (editConvert2)
        editConvert2 (text, string, userData);
    else if (editConvert)
        editConvert (text, string);
    // Allow to display strings through the stringConvert
    // callbacks inherited from CParamDisplay
    else if (stringConvert2)
    {
        string[0] = 0;
        stringConvert2 (value, string, userData);
        strcpy(text, string);
    }
    else if (stringConvert)
    {
        string[0] = 0;
        stringConvert (value, string);
        strcpy(text, string);
    }
    else
        sprintf (string, "%s", text);

    drawText (pContext, string);
    setDirty (false);
}

//------------------------------------------------------------------------
void CTextEdit::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (button & kLButton)
    {
        if (getFrame ()->getFocusView () != this)
        {
            if (style & kDoubleClickStyle)
                if (!isDoubleClick ())
                    return;

            beginEdit();
            takeFocus (pContext);
        }
    }
}

//------------------------------------------------------------------------
// #include <Xm/Text.h>
extern XFontStruct *gFontStructs[];

//------------------------------------------------------------------------
void CTextEdit::takeFocus (CDrawContext *pContext)
{
    bWasReturnPressed = false;

/*
    // we have to add the Text to the parent !!
    Dimension posX, posY;
    Widget widget = (Widget)(getFrame ()->getSystemWindow ());
    XtVaGetValues (widget, XmNx, &posX, XmNy, &posY, 0);

    Arg args[20];
    int n = 0;
    XtSetArg (args[n], XmNx, size.left + posX); n++;
    XtSetArg (args[n], XmNy, size.top + posY); n++;
    XtSetArg (args[n], XmNwidth, size.width () + 1); n++;
    XtSetArg (args[n], XmNheight, size.height () + 2); n++;

    XtSetArg (args[n], XmNvalue, text); n++;

    XtSetArg (args[n], XmNshadowType, XmSHADOW_IN); n++;
    XtSetArg (args[n], XmNshadowThickness, 0); n++;
    XtSetArg (args[n], XmNcursorPositionVisible, true); n++;

    XtSetArg (args[n], XmNmarginWidth, 0); n++;
    XtSetArg (args[n], XmNmarginHeight, 0); n++;
    XtSetArg (args[n], XmNresizeHeight, True); n++;
    XtSetArg (args[n], XmNborderWidth, 0); n++;
    XtSetArg (args[n], XmNeditMode, XmSINGLE_LINE_EDIT); n++;

    // get/set the current font
    XmFontList fl = 0;
    XFontStruct* fs = gFontStructs [fontID];
    if (fs)
    {
        XmFontListEntry entry = XmFontListEntryCreate (XmFONTLIST_DEFAULT_TAG, XmFONT_IS_FONT, fs);
        XmFontList fl = XmFontListAppendEntry (0, entry);
        XtSetArg (args[n], XmNfontList, fl); n++;
    }

    platformControl = XmCreateText (XtParent (widget), "Text", args, n);
    XtManageChild ((Widget)platformControl);
    if (fl)
        XmFontListFree (fl);
    XmTextSetSelection ((Widget)platformControl, 0, strlen (text), 0);
    XmTextSetHighlight ((Widget)platformControl, 0, strlen (text), XmHIGHLIGHT_SELECTED);
*/
}

//------------------------------------------------------------------------
void CTextEdit::looseFocus (CDrawContext *pContext)
{
    // Call this yet to avoid recursive call
    endEdit();
    if (getFrame ()->getFocusView () == this)
        getFrame ()->setFocusView (0);

    if (platformControl == 0)
        return;

/*
    char oldText[256];
    strcpy (oldText, text);

    char *pNewText = XmTextGetString ((Widget)platformControl);
    strcpy (text, pNewText);
    XtFree (pNewText);

    XtUnmanageChild ((Widget)platformControl);
    XtDestroyWidget ((Widget)platformControl);

    CPoint origOffset;
    bool resetContextOffset = false;
    if (!pContext)
    {
        // create a local context
        pContext = getFrame ()->createDrawContext ();
        if (getParentView ())
        {
            resetContextOffset = true;
            origOffset.x = pContext->offset.x;
            origOffset.y = pContext->offset.y;
            CView *view= getParentView ();
            CRect rect2;
            view->getViewSize (rect2);
            CPoint offset;
            view->localToFrame (offset);
            rect2.offset (offset.x, offset.y);
            pContext->offset.h = rect2.left;
            pContext->offset.v = rect2.top;
        }
    }
    else
        pContext->remember ();

    // update dependency
    bool change = false;
    if (strcmp (oldText, text))
    {
        change = true;
        if (listener)
            listener->valueChanged (pContext, this);
    }

    platformControl = 0;
    if (resetContextOffset)
    {
        pContext->offset.x = origOffset.x;
        pContext->offset.y = origOffset.y;
    }
    pContext->forget ();

    if (change)
        doIdleStuff ();

    CView* receiver = pParentView ? pParentView : pParentFrame;
    if (receiver)
        receiver->notify (this, "LooseFocus");
*/
}

//------------------------------------------------------------------------
void CTextEdit::setTextEditConvert (void (*convert) (char *input, char *string))
{
    editConvert = convert;
}

//------------------------------------------------------------------------
void CTextEdit::setTextEditConvert (void (*convert) (char *input, char *string,
                                      void *userDta), void *userData)
{
    editConvert2 = convert;
    this->userData = userData;
}

//------------------------------------------------------------------------
// COptionMenuScheme
//------------------------------------------------------------------------
/*! @class COptionMenuScheme
Used to define the appearance (font color, background color...) of a popup-menu.
To define the scheme of a menu, use the appropriate setScheme method (see COptionMenu).
@section coptionmenuscheme_new_in_3_0 New since 3.0
You can also use the global variable gOptionMenuScheme to use one scheme on all menus.
@section coptionmenuscheme_note Note
If you want to use it on Mac OS X, you must set the macro MAC_ENABLE_MENU_SCHEME (needs Mac OS X 10.3 or higher)
*/
COptionMenuScheme* gOptionMenuScheme = 0;

//------------------------------------------------------------------------
COptionMenuScheme::COptionMenuScheme ()
{
    backgroundColor = kGreyCColor;
    selectionColor = kBlueCColor;
    textColor = kBlackCColor;
    hiliteTextColor = kWhiteCColor;
    disableTextColor = kWhiteCColor;

    font = kNormalFontSmall;
}

//------------------------------------------------------------------------
COptionMenuScheme::~COptionMenuScheme ()
{
}

//------------------------------------------------------------------------
void COptionMenuScheme::getItemSize (const char* text, CDrawContext* pContext, CPoint& size)
{
    if (!strcmp (text, kMenuSeparator)) // separator
    {
        // was: size.h = size.v = 6;
        size.h = 6;
        size.v = 18;
        // separators must have same height, otherwise we have problems
        // in multi-column menus :(
    }
    else
    {
        pContext->setFont (font);
        size.h = pContext->getStringWidth (text) + 18;
        size.v = 18;
    }
}

//------------------------------------------------------------------------
void COptionMenuScheme::drawItemBack (CDrawContext* pContext, const CRect& rect, bool hilite)
{
    if (hilite)
        pContext->setFillColor (selectionColor);
    else
        pContext->setFillColor (backgroundColor);
    pContext->fillRect (rect);
}

//------------------------------------------------------------------------
void COptionMenuScheme::drawItem (const char* text, long itemId, long state, CDrawContext* pContext, const CRect& rect)
{
    bool hilite = (state & kSelected) != 0;

    drawItemBack (pContext, rect, hilite);

    if (!strcmp (text, kMenuSeparator))
    {
        CCoord y = rect.top + rect.height () / 2;

        const CColor bc = { 0, 0, 0, 150};
        const CColor wc = { 255, 255, 255, 150};

        pContext->setFrameColor (bc);
        pContext->moveTo (CPoint (rect.left + 2, y - 1));
        pContext->lineTo (CPoint (rect.right - 2, y - 1));
        pContext->setFrameColor (wc);
        pContext->moveTo (CPoint (rect.left + 2, y));
        pContext->lineTo (CPoint (rect.right - 2, y));
        return;
    }

    CRect r;
    if (state & kChecked)
    {
        r (6, 4, 14, 12);
        r.offset (rect.left, rect.top);
        if (hilite)
            pContext->setFillColor (hiliteTextColor);
        else
            pContext->setFillColor (textColor);
        pContext->fillEllipse (r);
    }

    r = rect;
    r.left += 18;
    pContext->setFont (font);
    if (state & kDisabled)
        pContext->setFontColor (disableTextColor);
    else
    {
        if (hilite)
            pContext->setFontColor (hiliteTextColor);
        else
            pContext->setFontColor (textColor);
    }

    // this needs to be done right, without changing the text pointer in anyway ;-)
    char *ptr = (char*)strstr (text, "\t");
    if (ptr)
    {
        char modifier[32];
        strcpy (modifier, ptr + 1);
        *ptr = 0;
        pContext->drawString (text, r, false, kLeftText);

        *ptr = '\t';
        r.left = r.right - 50;
        pContext->drawString (modifier, r, false, kLeftText);
    }
    else
        pContext->drawString (text, r, false, kLeftText);
}


//------------------------------------------------------------------------
// COptionMenu
//------------------------------------------------------------------------
/*! @class COptionMenu
Define a rectangle view where a text-value can be displayed with a given font and color.
The text-value is centered in the given rect.
A pixmap can be used as background, a second pixmap can be used when the option menu is popuped.
There are 2 styles with or without a shadowed text. When a mouse click occurs, a popup menu is displayed.
*/
COptionMenu::COptionMenu (const CRect &size, CControlListener *listener, long tag,
                          CBitmap *background, CBitmap *bgWhenClick, const long style)
:    CParamDisplay (size, background, style), bgWhenClick (bgWhenClick), nbItemsPerColumn (-1),
    prefixNumbers (0), scheme (0)
{
    this->listener = listener;
    this->tag = tag;

    nbEntries = 0;
    nbSubMenus = 0;
    currentIndex = -1;
    lastButton = kRButton;
    platformControl = 0;
    lastResult = -1;
    lastMenu = 0;

    if (bgWhenClick)
        bgWhenClick->remember ();

    nbSubMenuAllocated = nbAllocated = 0;

    check = 0;
    entry = 0;
    submenuEntry = 0;
}

//------------------------------------------------------------------------
COptionMenu::~COptionMenu ()
{
    removeAllEntry ();

    if (bgWhenClick)
        bgWhenClick->forget ();
}

//------------------------------------------------------------------------
void COptionMenu::setPrefixNumbers (long preCount)
{
    prefixNumbers = preCount;
}

//-----------------------------------------------------------------------------
bool COptionMenu::allocateSubMenu (long nb)
{
    long newAllocated = nbSubMenuAllocated + nb;

    if (submenuEntry)
        submenuEntry = (COptionMenu**)realloc (submenuEntry, newAllocated * sizeof (COptionMenu*));
    else
        submenuEntry = (COptionMenu**)malloc (newAllocated * sizeof (COptionMenu*));

    long i;
    for (i = nbSubMenuAllocated; i < newAllocated; i++)
        submenuEntry[i] = 0;

    nbSubMenuAllocated = newAllocated;

    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::allocateMenu (long nb)
{
    long newAllocated = nbAllocated + nb;

    if (check)
        check = (bool*)realloc (check, newAllocated * sizeof (bool));
    else
        check = (bool*)malloc (newAllocated * sizeof (bool));
    if (!check)
        return false;

    if (entry)
        entry = (char**)realloc (entry, newAllocated * sizeof (char*));
    else
        entry = (char**)malloc (newAllocated * sizeof (char*));
    if (!entry)
    {
        free (check);
        return false;
    }

    long i;
    for (i = nbAllocated; i < newAllocated; i++)
    {
        check[i] = false;
        entry[i] = 0;
    }

    nbAllocated = newAllocated;

    return true;
}

//------------------------------------------------------------------------
COptionMenu* COptionMenu::getSubMenu (long idx) const
{
    if (submenuEntry && idx < nbSubMenus)
        return submenuEntry[idx];
    return 0;
}

//------------------------------------------------------------------------
bool COptionMenu::addEntry (COptionMenu *subMenu, char *txt)
{
    if (nbEntries >= MAX_ENTRY || !subMenu || !txt)
        return false;

    if (nbEntries >= nbAllocated)
        if (!allocateMenu (32))
            return false;

    entry[nbEntries] = (char*)malloc (256);
    switch (prefixNumbers)
    {
        case 2:
            sprintf (entry[nbEntries], "-M%1d %s", (int)(nbEntries + 1), txt);
            break;

        case 3:
            sprintf (entry[nbEntries], "-M%02d %s", (int)(nbEntries + 1), txt);
            break;

        case 4:
            sprintf (entry[nbEntries], "-M%03d %s", (int)(nbEntries + 1), txt);
            break;

        default:
            sprintf (entry[nbEntries], "-M%s", txt);
    }


    if (nbSubMenus >= nbSubMenuAllocated)
        if (!allocateSubMenu (10))
            return false;

    submenuEntry[nbSubMenus++] = subMenu;
    subMenu->remember ();

    nbEntries++;

    if (currentIndex < 0)
        currentIndex = 0;

    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::addEntry (char *txt, long index)
{
    if (nbEntries >= MAX_ENTRY)
        return false;

    if (nbEntries >= nbAllocated)
        if (!allocateMenu (32))
            return false;

    entry[nbEntries] = (char*)malloc (256);

    long pos = nbEntries;

    // switch the entries for the insert
    if (index >= 0)
    {
        for (long i = nbEntries; i > index; i--)
            strcpy (entry[i], entry[i - 1]);
        if (index >= nbEntries)
            pos = nbEntries;
        else
            pos = index;
        if (currentIndex >= index)
            currentIndex++;
    }

    *entry[pos] = 0;
    if (txt)
    {
        switch (prefixNumbers)
        {
            case 2:
                sprintf (entry[pos], "%1d %s", (int)(index + 1), txt);
                break;

            case 3:
                sprintf (entry[pos], "%02d %s", (int)(index + 1), txt);
                break;

            case 4:
                sprintf (entry[pos], "%03d %s", (int)(index + 1), txt);
                break;

            default:
                strncpy (entry[pos], txt, 256);
        }
    }

    nbEntries++;

    if (currentIndex < 0)
        currentIndex = 0;

    return true;
}

//------------------------------------------------------------------------
long COptionMenu::getCurrent (char *txt, bool countSeparator) const
{
    if (currentIndex < 0)
        return -1;

    long result = 0;

    if (countSeparator)
    {
        if (txt)
            strcpy (txt, entry[currentIndex]);
        result = currentIndex;
    }
    else
    {
        for (long i = 0; i < currentIndex; i++)
        {
            if (strcmp (entry[i], kMenuSeparator) && strncmp (entry[i], kMenuTitle, 2))
                result++;
        }
        if (txt)
            strcpy (txt, entry[currentIndex]);
    }
    return result;
}

//------------------------------------------------------------------------
bool COptionMenu::setCurrent (long index, bool countSeparator)
{
    if (index < 0 || index >= nbEntries)
        return false;

    if (countSeparator)
    {
        if (!strcmp (entry[index], kMenuSeparator) && strncmp (entry[index], kMenuTitle, 2))
            return false;

        currentIndex = index;
    }
    else
    {
        long newCurrent = 0;
        long i = 0;
        while (i <= index && newCurrent < nbEntries)
        {
            if (strcmp (entry[newCurrent], kMenuSeparator) && strncmp (entry[newCurrent], kMenuTitle, 2))
                i++;
            newCurrent++;
        }
        currentIndex = newCurrent - 1;
    }
    if (style & (kMultipleCheckStyle & ~kCheckStyle))
        check[currentIndex] = !check[currentIndex];

    // to force the redraw
    setDirty ();

    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::getEntry (long index, char *txt) const
{
    if (index < 0 || index >= nbEntries)
        return false;

    if (txt)
        strcpy (txt, entry[index]);
    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::setEntry (long index, char *txt)
{
    if (index < 0 || index >= nbEntries)
        return false;

    if (txt)
        strcpy (entry[index], txt);
    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::removeEntry (long index)
{
    if (index < 0 || index >= nbEntries)
        return false;

    nbEntries--;

    // switch the entries
    for (long i = index; i < nbEntries; i++)
    {
        strcpy (entry[i], entry[i + 1]);
        check[i] = check [i + 1];
    }

    if (currentIndex >= index)
        currentIndex--;

    // delete the last one
    free (entry[nbEntries]);
    entry[nbEntries] = 0;
    check[nbEntries] = false;

    if (nbEntries == 0)
        currentIndex = -1;
    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::removeAllEntry ()
{
    long i;
    for (i = 0; i < nbEntries; i++)
    {
        free (entry[i]);
        entry[i] = 0;
        check[i] = false;
    }

    nbEntries = 0;
    currentIndex = -1;

    for (i = 0; i < nbSubMenus; i++)
    {
        submenuEntry[i]->forget ();
        submenuEntry[i] = 0;
    }
    nbSubMenus = 0;

    if (check)
        free (check);
    check = 0;
    if (entry)
        free (entry);
    entry = 0;
    if (submenuEntry)
        free (submenuEntry);
    submenuEntry = 0;
    nbAllocated = 0;
    nbSubMenuAllocated = 0;

    return true;
}

//------------------------------------------------------------------------
long COptionMenu::getIndex (char *txt) const
{
    if (!txt)
        return -1;

    // search entries
    for (long i = 0; i < nbEntries; i++)
        if (!strcmp (entry[i], txt))
            return i;

    // not found
    return -1;
}

//------------------------------------------------------------------------
bool COptionMenu::checkEntry (long index, bool state)
{
    if (index < 0 || index >= nbEntries)
        return false;

    check[index] = state;

    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::checkEntryAlone (long index)
{
    if (index < 0 || index >= nbEntries)
        return false;
    for (long i = 0; i < nbEntries; i++)
        check[i] = false;
    check[index] = true;

    return true;
}

//------------------------------------------------------------------------
bool COptionMenu::isCheckEntry (long index) const
{
    if (index < 0 || index >= nbEntries)
        return false;

    return check[index];
}

//------------------------------------------------------------------------
void COptionMenu::draw (CDrawContext *pContext)
{
//    DBG ("COptionMenu::draw");

    if (currentIndex >= 0 && nbEntries)
        drawText (pContext, entry[currentIndex] + prefixNumbers);
    else
        drawText (pContext, NULL);
}

//------------------------------------------------------------------------
void COptionMenu::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled || !getFrame () || !pContext)
        return;

    lastButton = (button != -1) ? button : pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (lastButton & (kLButton|kRButton|kApple))
    {
        if (bgWhenClick)
        {
            char string[256];
            if (currentIndex >= 0)
                sprintf (string, "%s", entry[currentIndex]);
            else
                string[0] = 0;

            drawText (pContext, string, bgWhenClick);
        }

        beginEdit();
        takeFocus (pContext);
    }
}

//------------------------------------------------------------------------
/*
#include <Xm/RowColumn.h>
#include <Xm/ToggleB.h>
#include <Xm/PushB.h>
#include <Xm/SeparatoG.h>

static void _unmapCallback (Widget item, XtPointer clientData, XtPointer callData);
static void _activateCallback (Widget item, XtPointer clientData, XtPointer callData);

//------------------------------------------------------------------------
static void _unmapCallback (Widget item, XtPointer clientData, XtPointer callData)
{
    COptionMenu *optionMenu= (COptionMenu*)clientData;
    optionMenu->looseFocus ();
}

//------------------------------------------------------------------------
static void _activateCallback (Widget item, XtPointer clientData, XtPointer callData)
{
    COptionMenu *optionMenu= (COptionMenu*)clientData;
    optionMenu->setCurrentSelected ((void*)item);
}
*/

//------------------------------------------------------------------------
COptionMenu *COptionMenu::getLastItemMenu (long &idxInMenu) const
{
    idxInMenu = lastMenu ? (long)lastMenu->getValue (): -1;
    return lastMenu;
}

//------------------------------------------------------------------------
COptionMenu *COptionMenu::getItemMenu (long idx, long &idxInMenu, long &offsetIdx)
{
    COptionMenu *menu = 0;
    for (long i = 0; i < nbSubMenus; i++)
    {
        menu = submenuEntry[i]->getItemMenu (idx, idxInMenu, offsetIdx);
        if (menu)
            break;
    }
    return menu;
}

//------------------------------------------------------------------------
void COptionMenu::removeItems ()
{
    for (long i = 0; i < nbSubMenus; i++)
        submenuEntry[i]->removeItems ();
}

//------------------------------------------------------------------------
void *COptionMenu::appendItems (long &offsetIdx)
{
    //bool multipleCheck = style & (kMultipleCheckStyle & ~kCheckStyle);
	return NULL;
}

//------------------------------------------------------------------------
void COptionMenu::setValue (float val)
{
    if ((long)val < 0 || (long)val >= nbEntries)
        return;

    currentIndex = (long)val;
    if (style & (kMultipleCheckStyle & ~kCheckStyle))
        check[currentIndex] = !check[currentIndex];
    CParamDisplay::setValue (val);

    // to force the redraw
    setDirty ();
}

//------------------------------------------------------------------------
void COptionMenu::takeFocus (CDrawContext *pContext)
{
    if (!getFrame ())
        return;

/*

    bool multipleCheck = style & (kMultipleCheckStyle & ~kCheckStyle);
    lastResult = -1;
    lastMenu = 0;

    Arg args[10];
    int n = 0;

    // get the position of the pParent
    CRect rect;
    getFrame ()->getSize (&rect);

    if (pContext)
    {
        rect.left += pContext->offset.h;
        rect.top  += pContext->offset.v;
    }

    // create a popup menu
    int offset;
    if (style & kPopupStyle)
        offset = (int)(rect.top + size.top);
    else
        offset = (int)(rect.top + size.bottom);

    XtSetArg (args[n], XmNx, rect.left + size.left); n++;
    XtSetArg (args[n], XmNy, offset); n++;
    XtSetArg (args[n], XmNmenuHistory, currentIndex); n++;
    XtSetArg (args[n], XmNtraversalOn, true); n++;

    platformControl = (void*)XmCreatePopupMenu ((Widget)(getFrame ()->getSystemWindow ()),
            "popup", args, n);

    XtAddCallback ((Widget)platformControl, XmNunmapCallback, _unmapCallback, this);

    // insert the menu items
    for (long i = 0; i < nbEntries; i++)
    {
        if (!strcmp (entry[i], kMenuSeparator))
        {
            itemWidget[i] = (void*)XtCreateManagedWidget ("separator",
                             xmSeparatorGadgetClass, (Widget)platformControl, 0, 0);
        }
        else
        {
            if (multipleCheck)
            {
                itemWidget[i] = (void*)XtVaCreateManagedWidget (entry[i],
                    xmToggleButtonWidgetClass, (Widget)platformControl,
                    XmNset, check[i], XmNvisibleWhenOff, false, 0);
                XtAddCallback ((Widget)itemWidget[i], XmNvalueChangedCallback, _activateCallback, this);
            }
            else if (style & kCheckStyle)
            {
                itemWidget[i] = (void*)XtVaCreateManagedWidget (entry[i],
                    xmToggleButtonWidgetClass, (Widget)platformControl,
                    XmNset, (i == currentIndex) ? true : false, XmNvisibleWhenOff, false, 0);
                XtAddCallback ((Widget)itemWidget[i], XmNvalueChangedCallback, _activateCallback, this);
            }
            else
            {
                itemWidget[i] = (void*)XtVaCreateManagedWidget (entry[i],
                    xmPushButtonWidgetClass, (Widget)platformControl, 0);
                XtAddCallback ((Widget)itemWidget[i], XmNactivateCallback, _activateCallback, this);
            }
        }
    }

    XtManageChild ((Widget)platformControl);
    getFrame ()->setFocusView (0);
    endEdit();
*/
}

//------------------------------------------------------------------------
void COptionMenu::looseFocus (CDrawContext *pContext)
{
    if (platformControl == 0)
        return;

/*
    for (long i = 0; i < nbEntries; i++)
        if (itemWidget[i])
            XtDestroyWidget ((Widget)itemWidget[i]);

    if (platformControl)
    {
        XtUnmanageChild ((Widget)platformControl);
        XtDestroyWidget ((Widget)platformControl);
    }
*/
    platformControl = 0;
}

//------------------------------------------------------------------------
void COptionMenu::setCurrentSelected (void *itemSelected)
{
    // retrieve the current index
    if (itemSelected != 0)
    {
        for (long i = 0; i < nbEntries; i++)
            if (itemWidget[i] == itemSelected)
            {
                currentIndex = i;
                break;
            }
    }

    // update dependency
    CDrawContext *pContext = new CDrawContext (getFrame (), (void*)getFrame ()->getGC (), (void*)getFrame ()->getBackBuffer ());

    setValue (currentIndex);

    if (listener)
        listener->valueChanged (pContext, this);
    delete pContext;
}


//------------------------------------------------------------------------
// CAnimKnob
//------------------------------------------------------------------------
/*! @class CAnimKnob
Such as a CKnob control object, but there is a unique pixmap which contains different views (subpixmaps) of this knob.
According to the value, a specific subpixmap is displayed. The different subpixmaps are stacked in the pixmap object.
*/
CAnimKnob::CAnimKnob (const CRect &size, CControlListener *listener, long tag,
                      CBitmap *background, CPoint &offset)
: CKnob (size, listener, tag, background, 0, offset), bInverseBitmap (false)
{
    heightOfOneImage = size.height ();
    subPixmaps = (short)(background->getHeight () / heightOfOneImage);
    inset = 0;
}

//------------------------------------------------------------------------
CAnimKnob::CAnimKnob (const CRect &size, CControlListener *listener, long tag,
                      long subPixmaps,         // number of subPixmaps
                      CCoord heightOfOneImage,   // height of one image in pixel
                      CBitmap *background, CPoint &offset)
: CKnob (size, listener, tag, background, 0, offset),
   subPixmaps (subPixmaps), heightOfOneImage (heightOfOneImage), bInverseBitmap (false)
{
    inset = 0;
}

//------------------------------------------------------------------------
CAnimKnob::~CAnimKnob ()
{}

//-----------------------------------------------------------------------------------------------
bool CAnimKnob::isDirty () const
{
    if (!bDirty)
    {
        CPoint p;
        valueToPoint (p);
        if (p == lastDrawnPoint)
            return false;
    }
    return CKnob::isDirty ();
}

//------------------------------------------------------------------------
void CAnimKnob::draw (CDrawContext *pContext)
{
//    DBG ("CAnimKnob::draw");

    CPoint where (0, 0);
    if (value >= 0.f)
    {
        CCoord tmp = heightOfOneImage * (subPixmaps - 1);
        if (bInverseBitmap)
            where.v = (long)((1 - value) * (float)tmp);
        else
            where.v = (long)(value * (float)tmp);
        for (CCoord realY = 0; realY <= tmp; realY += heightOfOneImage)
        {
            if (where.v < realY)
            {
                where.v = realY - heightOfOneImage;
                if (where.v < 0)
                    where.v = 0;
                break;
            }
        }
    }

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    valueToPoint (lastDrawnPoint);
    setDirty (false);
}

//------------------------------------------------------------------------
// CVerticalSwitch
//------------------------------------------------------------------------
/*! @class CVerticalSwitch
Define a switch with a given number of positions, the current position is defined by the position
of the last click on this object (the object is divided in its height by the number of position).
Each position has its subpixmap, each subpixmap is stacked in the given handle pixmap.
By clicking Alt+Left Mouse the default value is used.
*/
CVerticalSwitch::CVerticalSwitch (const CRect &size, CControlListener *listener, long tag,
                                  CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset)
{
    heightOfOneImage = size.height ();
    subPixmaps = (long)(background->getHeight () / heightOfOneImage);
    iMaxPositions = subPixmaps;

    setDefaultValue (0.f);
}

//------------------------------------------------------------------------
CVerticalSwitch::CVerticalSwitch (const CRect &size, CControlListener *listener, long tag,
                                  long subPixmaps,       // number of subPixmaps
                                  CCoord heightOfOneImage, // height of one image in pixel
                                  long iMaxPositions,
                                  CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset),
    subPixmaps (subPixmaps), heightOfOneImage (heightOfOneImage),
    iMaxPositions (iMaxPositions)
{
    setDefaultValue (0.f);
}

//------------------------------------------------------------------------
CVerticalSwitch::~CVerticalSwitch ()
{}

//------------------------------------------------------------------------
void CVerticalSwitch::draw (CDrawContext *pContext)
{
//    DBG ("CVerticalSwitch::draw");

    if (pBackground)
    {
        // source position in bitmap
        CPoint where (0, heightOfOneImage * ((long)(value * (iMaxPositions - 1) + 0.5f)));

        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void CVerticalSwitch::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();
    if (!(button & kLButton))
        return;

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    // check if default value wanted
    if (checkDefaultValue (pContext, button))
        return;

    double coef = (double)heightOfOneImage / (double)iMaxPositions;

    // begin of edit parameter
    beginEdit ();
    do
    {
        value = (long)((where.v - size.top) / coef) / (float)(iMaxPositions - 1);
        if (value > 1.f)
            value = 1.f;
        else if (value < 0.f)
            value = 0.f;

        if (isDirty () && listener)
            listener->valueChanged (pContext, this);

        getMouseLocation (pContext, where);

        doIdleStuff ();
    }
    while (pContext->getMouseButtons () == button);

    // end of edit parameter
    endEdit ();
}


//------------------------------------------------------------------------
// CHorizontalSwitch
//------------------------------------------------------------------------
/*! @class CHorizontalSwitch
Same as the CVerticalSwitch but horizontal.
*/
CHorizontalSwitch::CHorizontalSwitch (const CRect &size, CControlListener *listener, long tag,
                                  CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset)
{
    heightOfOneImage = size.width ();
    subPixmaps = (long)(background->getWidth () / heightOfOneImage);
    iMaxPositions = subPixmaps;

    setDefaultValue (0.f);
}

//------------------------------------------------------------------------
CHorizontalSwitch::CHorizontalSwitch (const CRect &size, CControlListener *listener, long tag,
                                  long subPixmaps,   // number of subPixmaps
                                  CCoord heightOfOneImage, // height of one image in pixel
                                  long iMaxPositions,
                                  CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset),
    subPixmaps (subPixmaps),
    iMaxPositions (iMaxPositions),
	heightOfOneImage (heightOfOneImage)
{
    setDefaultValue (0.f);
}

//------------------------------------------------------------------------
CHorizontalSwitch::~CHorizontalSwitch ()
{}

//------------------------------------------------------------------------
void CHorizontalSwitch::draw (CDrawContext *pContext)
{
//    DBG ("CHorizontalSwitch::draw");

    if (pBackground)
    {
        // source position in bitmap
        CPoint where (0, heightOfOneImage * ((long)(value * (iMaxPositions - 1) + 0.5f)));

        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void CHorizontalSwitch::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    // check if default value wanted
    if (checkDefaultValue (pContext, button))
        return;

    double coef = (double)pBackground->getWidth () / (double)iMaxPositions;

    // begin of edit parameter
    beginEdit ();
    do
    {
        value = (long)((where.h - size.left) / coef) / (float)(iMaxPositions - 1);
        if (value > 1.f)
            value = 1.f;
        else if (value < 0.f)
            value = 0.f;

        if (isDirty () && listener)
            listener->valueChanged (pContext, this);

        getMouseLocation (pContext, where);

        doIdleStuff ();
    }
    while (pContext->getMouseButtons () == button);

    // end of edit parameter
    endEdit ();
}


//------------------------------------------------------------------------
// CRockerSwitch
//------------------------------------------------------------------------
/*! @class CRockerSwitch
Define a rocker switch with 3 states using 3 subpixmaps.
One click on its leftside, then the first subpixmap is displayed.
One click on its rightside, then the third subpixmap is displayed.
When the mouse button is relaxed, the second subpixmap is framed.
*/
CRockerSwitch::CRockerSwitch (const CRect &size, CControlListener *listener, long tag,              // identifier tag (ID)
                              CBitmap *background, CPoint &offset, const long style)
:    CControl (size, listener, tag, background), offset (offset), style (style)
{
    heightOfOneImage = size.width ();
}

//------------------------------------------------------------------------
CRockerSwitch::CRockerSwitch (const CRect &size, CControlListener *listener, long tag,              // identifier tag (ID)
                              CCoord heightOfOneImage, // height of one image in pixel
                              CBitmap *background, CPoint &offset, const long style)
:    CControl (size, listener, tag, background), offset (offset),
    heightOfOneImage (heightOfOneImage), style (style)
{}

//------------------------------------------------------------------------
CRockerSwitch::~CRockerSwitch ()
{}

//------------------------------------------------------------------------
void CRockerSwitch::draw (CDrawContext *pContext)
{
//   DBG ("CRockerSwitch::draw");

    CPoint where (offset.h, offset.v);

    if (value == 1.f)
        where.v += 2 * heightOfOneImage;
    else if (value == 0.f)
        where.v += heightOfOneImage;

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void CRockerSwitch::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    float fEntryState = value;

    CCoord  width_2  = size.width () / 2;
    CCoord  height_2 = size.height () / 2;

    // begin of edit parameter
    beginEdit ();

    if (button)
    {
        do
        {
            if (style & kHorizontal)
            {
                if (where.h >= size.left && where.v >= size.top  &&
                    where.h <= (size.left + width_2) && where.v <= size.bottom)
                    value = -1.0f;
                else if (where.h >= (size.left + width_2) && where.v >= size.top  &&
                    where.h <= size.right && where.v <= size.bottom)
                    value = 1.0f;
                else
                    value = fEntryState;
            }
            else
            {
                if (where.h >= size.left && where.v >= size.top  &&
                    where.h <= size.right && where.v <= (size.top + height_2))
                    value = -1.0f;
                else if (where.h >= size.left && where.v >= (size.top + height_2) &&
                    where.h <= size.right && where.v <= size.bottom)
                    value = 1.0f;
                else
                    value = fEntryState;
            }

            if (isDirty () && listener)
                listener->valueChanged (pContext, this);

            getMouseLocation (pContext, where);

            doIdleStuff ();
        }
        while (pContext->getMouseButtons ());
    }
    else
    {
        if (where.h >= size.left && where.v >= size.top  &&
                where.h <= (size.left + width_2) && where.v <= size.bottom)
            value = -1.0f;
        else if (where.h >= (size.left + width_2) && where.v >= size.top  &&
                where.h <= size.right && where.v <= size.bottom)
            value = 1.0f;

        if (listener)
            listener->valueChanged (pContext, this);
    }

    value = 0.f;  // set button to UNSELECTED state
    if (listener)
        listener->valueChanged (pContext, this);

    // end of edit parameter
    endEdit ();
}

//------------------------------------------------------------------------
bool CRockerSwitch::onWheel (CDrawContext *pContext, const CPoint &where, float distance)
{
    if (!bMouseEnabled)
        return false;

    if (distance > 0)
        value = -1.0f;
    else
        value = 1.0f;

    // begin of edit parameter
    beginEdit ();

    if (isDirty () && listener)
        listener->valueChanged (pContext, this);

    value = 0.0f;  // set button to UNSELECTED state
    if (listener)
        listener->valueChanged (pContext, this);

    // end of edit parameter
    endEdit ();

    return true;
}


//------------------------------------------------------------------------
// CMovieBitmap
//------------------------------------------------------------------------
/*! @class CMovieBitmap
A movie pixmap allows to display different subpixmaps according to its current value.
*/
CMovieBitmap::CMovieBitmap (const CRect &size, CControlListener *listener, long tag,
                            CBitmap *background, CPoint &offset)
  :    CControl (size, listener, tag, background), offset (offset),
        subPixmaps (subPixmaps), heightOfOneImage (heightOfOneImage)
{
    heightOfOneImage = size.height ();
    subPixmaps = (long)(background->getHeight () / heightOfOneImage);
}

//------------------------------------------------------------------------
CMovieBitmap::CMovieBitmap (const CRect &size, CControlListener *listener, long tag,
                            long subPixmaps,        // number of subPixmaps
                            CCoord heightOfOneImage,  // height of one image in pixel
                            CBitmap *background, CPoint &offset)
  :    CControl (size, listener, tag, background), offset (offset),
        subPixmaps (subPixmaps), heightOfOneImage (heightOfOneImage)
{}

//------------------------------------------------------------------------
CMovieBitmap::~CMovieBitmap ()
{}

//------------------------------------------------------------------------
void CMovieBitmap::draw (CDrawContext *pContext)
{
 //   DBG ("CMovieBitmap::draw");

    CPoint where (offset.h, offset.v);

    if (value > 1.0f)
        value = 1.0f;

     if (value > 0.0f)
        where.v += heightOfOneImage * (int)(value * (subPixmaps - 1) + 0.5);

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    setDirty (false);
}


//------------------------------------------------------------------------
// CMovieButton
//------------------------------------------------------------------------
/*! @class CMovieButton
A movie button is a bi-states button with 2 subpixmaps. These subpixmaps are stacked in the pixmap.
*/
CMovieButton::CMovieButton (const CRect &size, CControlListener *listener, long tag,              // identifier tag (ID)
                            CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset), buttonState (value)
{
    heightOfOneImage = size.height ();
}

//------------------------------------------------------------------------
CMovieButton::CMovieButton (const CRect &size, CControlListener *listener, long tag,
                            CCoord heightOfOneImage, // height of one image in pixel
                            CBitmap *background, CPoint &offset)
    :    CControl (size, listener, tag, background), offset (offset),
        heightOfOneImage (heightOfOneImage), buttonState (value)
{}

//------------------------------------------------------------------------
CMovieButton::~CMovieButton ()
{}

//------------------------------------------------------------------------
void CMovieButton::draw (CDrawContext *pContext)
{
  //  DBG ("CMovieButton::draw");

    CPoint where;

    where.h = 0;

    bounceValue ();

    if (value)
        where.v = heightOfOneImage;
    else
        where.v = 0;

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    buttonState = value;

    setDirty (false);
}

//------------------------------------------------------------------------
void CMovieButton::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    // this simulates a real windows button
    float fEntryState = value;

    // begin of edit parameter
    beginEdit ();

    if (pContext->getMouseButtons ())
    {
        do
        {
            if (where.h >= size.left &&
                    where.v >= size.top  &&
                    where.h <= size.right &&
                    where.v <= size.bottom)
                value = !fEntryState;
            else
                value = fEntryState;

            if (isDirty () && listener)
                listener->valueChanged (pContext, this);

            getMouseLocation (pContext, where);

            doIdleStuff ();
        }
        while (pContext->getMouseButtons () == button);
    }
    else
    {
        value = !value;
        if (listener)
            listener->valueChanged (pContext, this);
    }

    // end of edit parameter
    endEdit ();

    buttonState = value;
}


//------------------------------------------------------------------------
// CAutoAnimation
//------------------------------------------------------------------------
/*! @class CAutoAnimation
An auto-animation control contains a given number of subpixmap which can be displayed in loop.
Two functions allows to get the previous or the next subpixmap (these functions increase or decrease the current value of this control).
*/
// displays bitmaps within a (child-) window
CAutoAnimation::CAutoAnimation (const CRect &size, CControlListener *listener, long tag,
                                CBitmap *background, CPoint &offset)
: CControl (size, listener, tag, background), offset (offset), bWindowOpened (false)
{
    heightOfOneImage = size.height ();
    subPixmaps = (long)(background->getHeight () / heightOfOneImage);

    totalHeightOfBitmap = heightOfOneImage * subPixmaps;
}

//------------------------------------------------------------------------
CAutoAnimation::CAutoAnimation (const CRect &size, CControlListener *listener, long tag,
                                long subPixmaps,     // number of subPixmaps...
                                CCoord heightOfOneImage, // height of one image in pixel
                                CBitmap *background, CPoint &offset)
    :    CControl (size, listener, tag, background), offset (offset),
        subPixmaps (subPixmaps), heightOfOneImage (heightOfOneImage),
        bWindowOpened (false)
{
    totalHeightOfBitmap = heightOfOneImage * subPixmaps;
}

//------------------------------------------------------------------------
CAutoAnimation::~CAutoAnimation ()
{}

//------------------------------------------------------------------------
void CAutoAnimation::draw (CDrawContext *pContext)
{
 //   DBG ("CAutoAnimation::draw");

    if (isWindowOpened ())
    {
        CPoint where;
        where.v = (long)value + offset.v;
        where.h = offset.h;

        if (pBackground)
        {
            if (bTransparencyEnabled)
                pBackground->drawTransparent (pContext, size, where);
            else
                pBackground->draw (pContext, size, where);
        }
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void CAutoAnimation::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    if (!isWindowOpened ())
    {
        value = 0;
        openWindow ();
        setDirty (); // force to redraw
        if (listener)
            listener->valueChanged (pContext, this);
    }
    else
    {
        // stop info animation
        value = 0; // draw first pic of bitmap
        setDirty ();
        closeWindow ();
    }
}

//------------------------------------------------------------------------
void CAutoAnimation::openWindow ()
{
    bWindowOpened = true;
}

//------------------------------------------------------------------------
void CAutoAnimation::closeWindow ()
{
    bWindowOpened = false;
}

//------------------------------------------------------------------------
void CAutoAnimation::nextPixmap ()
{
    value += heightOfOneImage;
    if (value >= (totalHeightOfBitmap - heightOfOneImage))
        value = 0;
}

//------------------------------------------------------------------------
void CAutoAnimation::previousPixmap ()
{
    value -= heightOfOneImage;
    if (value < 0.f)
        value = (float)(totalHeightOfBitmap - heightOfOneImage - 1);
}


//------------------------------------------------------------------------
// CSlider
//------------------------------------------------------------------------
/*! @class CSlider
Define a slider with a given background and handle.
The range of variation of the handle should be defined.
By default the handler is drawn with transparency (white color).
By clicking Alt+Left Mouse the default value is used.
*/
CSlider::CSlider (const CRect &rect, CControlListener *listener, long tag,
                  long      iMinPos, // min position in pixel
                  long      iMaxPos, // max position in pixel
                  CBitmap  *handle,  // bitmap of slider
                  CBitmap  *background, // bitmap of background
                  CPoint   &offset,  // offset in the background
                  const long style)  // style (kBottom,kRight,kTop,kLeft,kHorizontal,kVertical)
  :    CControl (rect, listener, tag, background),    offset (offset), pHandle (handle),
    pOScreen (0), style (style), bFreeClick (true)
{
    setDrawTransparentHandle (true);

    if (pHandle)
    {
        pHandle->remember ();
        widthOfSlider  = pHandle->getWidth ();
        heightOfSlider = pHandle->getHeight ();
    }
    else
    {
        widthOfSlider  = 1;
        heightOfSlider = 1;
    }

    widthControl  = size.width ();
    heightControl = size.height ();

    if (style & kHorizontal)
    {
        minPos = iMinPos - size.left;
        rangeHandle = iMaxPos - iMinPos;
        CPoint p (0, 0);
        setOffsetHandle (p);
    }
    else
    {
        minPos = iMinPos - size.top;
        rangeHandle = iMaxPos - iMinPos;
        CPoint p (0, 0);
        setOffsetHandle (p);
    }

    zoomFactor = 10.f;

    setWantsFocus (true);
}

//------------------------------------------------------------------------
CSlider::CSlider (const CRect &rect, CControlListener *listener, long tag,
                  CPoint   &offsetHandle,    // handle offset
                  long     _rangeHandle, // size of handle range
                  CBitmap  *handle,     // bitmap of slider
                  CBitmap  *background, // bitmap of background
                  CPoint   &offset,     // offset in the background
                  const long style)     // style (kBottom,kRight,kTop,kLeft,kHorizontal,kVertical)
:    CControl (rect, listener, tag, background), offset (offset), pHandle (handle),
    pOScreen (0), style (style), minPos (0), bFreeClick (true)
{
    setDrawTransparentHandle (true);

    if (pHandle)
    {
        pHandle->remember ();
        widthOfSlider  = pHandle->getWidth ();
        heightOfSlider = pHandle->getHeight ();
    }
    else
    {
        widthOfSlider  = 1;
        heightOfSlider = 1;
    }

    widthControl  = size.width ();
    heightControl = size.height ();
    if (style & kHorizontal)
        rangeHandle = _rangeHandle - widthOfSlider;
    else
        rangeHandle = _rangeHandle - heightOfSlider;

    setOffsetHandle (offsetHandle);

    zoomFactor = 10.f;

    setWantsFocus (true);
}

//------------------------------------------------------------------------
CSlider::~CSlider ()
{
    if (pHandle)
        pHandle->forget ();
}

//------------------------------------------------------------------------
void CSlider::setOffsetHandle (CPoint &val)
{
    offsetHandle = val;

    if (style & kHorizontal)
    {
        minTmp = offsetHandle.h + minPos;
        maxTmp = minTmp + rangeHandle + widthOfSlider;
    }
    else
    {
        minTmp = offsetHandle.v + minPos;
        maxTmp = minTmp + rangeHandle + heightOfSlider;
    }
}

//-----------------------------------------------------------------------------
bool CSlider::attached (CView *parent)
{
    if (pOScreen)
        delete pOScreen;

    pOScreen = 0; // @TODO - faking offscreen
//    pOScreen = new COffscreenContext (getFrame (), widthControl, heightControl, kBlackCColor);

    return CControl::attached (parent);
}

//-----------------------------------------------------------------------------
bool CSlider::removed (CView *parent)
{
    if (pOScreen)
    {
        delete pOScreen;
        pOScreen = 0;
    }
    return CControl::removed (parent);
}

//------------------------------------------------------------------------
void CSlider::draw (CDrawContext *pContext)
{
 //   DBG ("CSlider::draw");

    CDrawContext* drawContext = pOScreen ? pOScreen : pContext;

    if (pOScreen && bTransparencyEnabled)
        pOScreen->copyTo (pContext, size);

    float fValue;
    if (style & kLeft || style & kTop)
        fValue = value;
    else
        fValue = 1.f - value;

    // (re)draw background
    CRect rect (0, 0, widthControl, heightControl);
    if (!pOScreen)
        rect.offset (size.left, size.top);
    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (drawContext, rect, offset);
        else
            pBackground->draw (drawContext, rect, offset);
    }

    // calc new coords of slider
    CRect   rectNew;
    if (style & kHorizontal)
    {
        rectNew.top    = offsetHandle.v;
        rectNew.bottom = rectNew.top + heightOfSlider;

        rectNew.left   = offsetHandle.h + (int)(fValue * rangeHandle);
        rectNew.left   = (rectNew.left < minTmp) ? minTmp : rectNew.left;

        rectNew.right  = rectNew.left + widthOfSlider;
        rectNew.right  = (rectNew.right > maxTmp) ? maxTmp : rectNew.right;
    }
    else
    {
        rectNew.left   = offsetHandle.h;
        rectNew.right  = rectNew.left + widthOfSlider;

        rectNew.top    = offsetHandle.v + (int)(fValue * rangeHandle);
        rectNew.top    = (rectNew.top < minTmp) ? minTmp : rectNew.top;

        rectNew.bottom = rectNew.top + heightOfSlider;
        rectNew.bottom = (rectNew.bottom > maxTmp) ? maxTmp : rectNew.bottom;
    }
    if (!pOScreen)
        rectNew.offset (size.left, size.top);

    // draw slider at new position
    if (pHandle)
    {
        if (bDrawTransparentEnabled)
            pHandle->drawTransparent (drawContext, rectNew);
        else
            pHandle->draw (drawContext, rectNew);
    }

    if (pOScreen)
        pOScreen->copyFrom (pContext, size);

    setDirty (false);
}

//------------------------------------------------------------------------
void CSlider::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    // check if default value wanted
    if (checkDefaultValue (pContext, button))
        return;

    // allow left mousebutton only
    if (!(button & kLButton))
        return;

    CCoord delta;
    if (style & kHorizontal)
        delta = size.left + offsetHandle.h;
    else
        delta = size.top + offsetHandle.v;
    if (!bFreeClick)
    {
        float fValue;
        if (style & kLeft || style & kTop)
            fValue = value;
        else
            fValue = 1.f - value;
        CCoord actualPos;
        CRect rect;

        if (style & kHorizontal)
        {
            actualPos = offsetHandle.h + (int)(fValue * rangeHandle) + size.left;

            rect.left   = actualPos;
            rect.top    = size.top  + offsetHandle.v;
            rect.right  = rect.left + widthOfSlider;
            rect.bottom = rect.top  + heightOfSlider;

            if (!where.isInside (rect))
                return;
            else
                delta += where.h - actualPos;
        }
        else
        {
            actualPos = offsetHandle.v + (int)(fValue * rangeHandle) + size.top;

            rect.left   = size.left  + offsetHandle.h;
            rect.top    = actualPos;
            rect.right  = rect.left + widthOfSlider;
            rect.bottom = rect.top  + heightOfSlider;

            if (!where.isInside (rect))
                return;
            else
                delta += where.v - actualPos;
        }
    }
    else
    {
        if (style & kHorizontal)
            delta += widthOfSlider / 2 - 1;
        else
            delta += heightOfSlider / 2 - 1;
    }

    float oldVal    = value;
    long  oldButton = button;

    // begin of edit parameter
    beginEdit ();

    while (1)
    {
        button = pContext->getMouseButtons ();
        if (!(button & kLButton))
            break;

        if ((oldButton != button) && (button & kShift))
        {
            oldVal    = value;
            oldButton = button;
        }
        else if (!(button & kShift))
            oldVal = value;

        if (style & kHorizontal)
            value = (float)(where.h - delta) / (float)rangeHandle;
        else
            value = (float)(where.v - delta) / (float)rangeHandle;

        if (style & kRight || style & kBottom)
            value = 1.f - value;

        if (button & kShift)
            value = oldVal + ((value - oldVal) / zoomFactor);
        bounceValue ();

        if (isDirty () && listener)
            listener->valueChanged (pContext, this);

        getMouseLocation (pContext, where);

        doIdleStuff ();
    }

    // end of edit parameter
    endEdit ();
}

//------------------------------------------------------------------------
bool CSlider::onWheel (CDrawContext *pContext, const CPoint &where, float distance)
{
    if (!bMouseEnabled)
        return false;

    long buttons = pContext->getMouseButtons ();
    if (buttons & kShift)
        value += 0.1f * distance * wheelInc;
    else
        value += distance * wheelInc;
    bounceValue ();

    if (isDirty () && listener)
    {
        // begin of edit parameter
        beginEdit ();

        listener->valueChanged (pContext, this);

        // end of edit parameter
        endEdit ();
    }

    return true;
}

//------------------------------------------------------------------------
long CSlider::onKeyDown (VstKeyCode& keyCode)
{
    switch (keyCode.virt)
    {
    case VKEY_UP :
    case VKEY_RIGHT :
    case VKEY_DOWN :
    case VKEY_LEFT :
        {
            float distance = 1.f;
            if (keyCode.virt == VKEY_DOWN || keyCode.virt == VKEY_LEFT)
                distance = -distance;

            if (keyCode.modifier & MODIFIER_SHIFT)
                value += 0.1f * distance * wheelInc;
            else
                value += distance * wheelInc;
            bounceValue ();

            if (isDirty () && listener)
            {
                // begin of edit parameter
                beginEdit ();

                listener->valueChanged (0, this);

                // end of edit parameter
                endEdit ();
            }
        } return 1;
    }
    return -1;
}

//------------------------------------------------------------------------
void CSlider::setHandle (CBitmap *_pHandle)
{
    if (pHandle)
        pHandle->forget ();
    pHandle = _pHandle;
    if (pHandle)
    {
        pHandle->remember ();
        widthOfSlider  = pHandle->getWidth ();
        heightOfSlider = pHandle->getHeight ();
    }
}


//------------------------------------------------------------------------
// CVerticalSlider
//------------------------------------------------------------------------
/*! @class CVerticalSlider
This is the vertical slider. See CSlider.
*/
CVerticalSlider::CVerticalSlider (const CRect &rect, CControlListener *listener, long tag,
                                  long      iMinPos, // min position in pixel
                                  long      iMaxPos, // max position in pixel
                                  CBitmap  *handle,  // bitmap of slider
                                  CBitmap  *background, // bitmap of background
                                  CPoint   &offset,  // offset in the background
                                  const long style)  // style (kLeft, kRight)
  :    CSlider (rect, listener, tag, iMinPos, iMaxPos, handle, background, offset, style|kVertical)
{}

//------------------------------------------------------------------------
CVerticalSlider::CVerticalSlider (const CRect &rect, CControlListener *listener, long tag,
                          CPoint   &offsetHandle,    // handle offset
                          long     rangeHandle, // size of handle range
                          CBitmap  *handle,     // bitmap of slider
                          CBitmap  *background, // bitmap of background
                          CPoint   &offset,     // offset in the background
                          const long style)     // style (kLeft, kRight)
:    CSlider (rect, listener, tag, offsetHandle, rangeHandle, handle, background, offset, style|kVertical)
{}


//------------------------------------------------------------------------
// CHorizontalSlider
//------------------------------------------------------------------------
/*! @class CHorizontalSlider
This is the horizontal slider. See CSlider.
*/
CHorizontalSlider::CHorizontalSlider (const CRect &rect, CControlListener *listener, long tag,
                                  long      iMinPos, // min Y position in pixel
                                  long      iMaxPos, // max Y position in pixel
                                  CBitmap  *handle,  // bitmap of slider
                                  CBitmap  *background, // bitmap of background
                                  CPoint   &offset,  // offset in the background
                                  const long style)  // style (kLeft, kRight)
  :    CSlider (rect, listener, tag, iMinPos, iMaxPos, handle, background, offset, style|kHorizontal)
{}

//------------------------------------------------------------------------
CHorizontalSlider::CHorizontalSlider (const CRect &rect, CControlListener *listener, long tag,
                          CPoint   &offsetHandle,    // handle offset
                          long     rangeHandle, // size of handle range
                          CBitmap  *handle,     // bitmap of slider
                          CBitmap  *background, // bitmap of background
                          CPoint   &offset,     // offset in the background
                          const long style)     // style (kLeft, kRight)
:    CSlider (rect, listener, tag, offsetHandle, rangeHandle, handle, background, offset, style|kHorizontal)
{}


//------------------------------------------------------------------------
// CSpecialDigit
//------------------------------------------------------------------------
/*! @class CSpecialDigit
Can be used to display a counter with maximum 7 digits.
All digit have the same size and are stacked in height in the pixmap.
*/
CSpecialDigit::CSpecialDigit (const CRect &size,
                              CControlListener *listener,
                              long      tag,        // tag identifier
                              long     dwPos,      // actual value
                              long      iNumbers,   // amount of numbers (max 7)
                              long      *xpos,      // array of all XPOS
                              long      *ypos,      // array of all YPOS
                              long      width,      // width of ONE number
                              long      height,     // height of ONE number
                              CBitmap  *background)    // bitmap numbers
  :    CControl (size, listener, tag, background),
        iNumbers (iNumbers), width (width), height (height)
{
    setValue ((float)dwPos);          // actual value

    if (iNumbers > 7)
        iNumbers = 7;

    if (xpos == NULL)
    {
        // automatically init xpos/ypos if not provided by caller
        const int numw = (const int)background->getWidth();
        int x = (int)size.left;
        for (long i = 0; i < iNumbers; i++)
        {
            this->xpos[i] = x;
            this->ypos[i] = (long)size.top;
            x += numw;
        }
    }
    else
    {
        // store coordinates of x/y pos of each digit
        for (long i = 0; i < iNumbers; i++)
        {
            this->xpos[i] = xpos[i];
            this->ypos[i] = ypos[i];
        }
    }

    setMax ((float)pow (10., (double)iNumbers) - 1.0f);
    setMin (0.0f);
}

//------------------------------------------------------------------------
CSpecialDigit::~CSpecialDigit ()
{}

//------------------------------------------------------------------------
void CSpecialDigit::draw (CDrawContext *pContext)
{
//    DBG ("CSpecialDigit::draw");

    CPoint  where;
    CRect   rectDest;
    long    i, j;
    long    dwValue;
    long     one_digit[16];

    if ((long)value >= getMax ())
        dwValue = (long)getMax ();
    else if ((long)value < getMin ())
        dwValue = (long)getMin ();
    else
        dwValue = (long)value;

    for (i = 0, j = ((long)getMax () + 1) / 10; i < iNumbers; i++, j /= 10)
    {
        one_digit[i] = dwValue / j;
        dwValue -= (one_digit[i] * j);
    }

    where.h = 0;
    for (i = 0; i < iNumbers; i++)
    {
        j = one_digit[i];
        if (j > 9)
            j = 9;

        rectDest.left   = xpos[i];
        rectDest.top    = ypos[i];

        rectDest.right  = rectDest.left + width;
        rectDest.bottom = rectDest.top  + height;

        // where = src from bitmap
        where.v = j * height;
        if (pBackground)
        {
            if (bTransparencyEnabled)
                pBackground->drawTransparent (pContext, rectDest, where);
            else
                pBackground->draw (pContext, rectDest, where);
        }
    }

    setDirty (false);
}

//------------------------------------------------------------------------
float CSpecialDigit::getNormValue () const
{
    float fTemp;
    fTemp = value / getMax ();
    if (fTemp > 1.0f)
        fTemp = 1.0f;
    else if (fTemp < 0.0f)
        fTemp = 0.0f;

    return fTemp;
}


//------------------------------------------------------------------------
// CKickButton
//------------------------------------------------------------------------
/*! @class CKickButton
Define a button with 2 states using 2 subpixmaps.
One click on it, then the second subpixmap is displayed.
When the mouse button is relaxed, the first subpixmap is framed.
*/
CKickButton::CKickButton (const CRect &size, CControlListener *listener, long tag,
                          CBitmap *background, CPoint &offset)
:    CControl (size, listener, tag, background), offset (offset)
{
    heightOfOneImage = size.height ();
}

//------------------------------------------------------------------------
CKickButton::CKickButton (const CRect &size, CControlListener *listener, long tag,
                          CCoord heightOfOneImage, // height of one image in pixel
                          CBitmap *background, CPoint &offset)
:    CControl (size, listener, tag, background), offset (offset),
    heightOfOneImage (heightOfOneImage)
{}

//------------------------------------------------------------------------
CKickButton::~CKickButton ()
{}

//------------------------------------------------------------------------
void CKickButton::draw (CDrawContext *pContext)
{
 //   DBG ("CKickButton::draw");

    CPoint where (offset.h, offset.v);

    bounceValue ();

    if (value)
        where.v += heightOfOneImage;

    if (pBackground)
    {
        if (bTransparencyEnabled)
            pBackground->drawTransparent (pContext, size, where);
        else
            pBackground->draw (pContext, size, where);
    }
    setDirty (false);
}

//------------------------------------------------------------------------
void CKickButton::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    // this simulates a real windows button
    float fEntryState = value;

    // begin of edit parameter
    beginEdit ();

    if (pContext->getMouseButtons () == kLButton)
    {
        do
        {
            if (where.h >= size.left && where.v >= size.top  &&
                where.h <= size.right && where.v <= size.bottom)
                value = !fEntryState;
            else
                value = fEntryState;

            if (isDirty () && listener)
                listener->valueChanged (pContext, this);

            getMouseLocation (pContext, where);

            doIdleStuff ();
        }
        while (pContext->getMouseButtons () == kLButton);
    }
    else
    {
        value = !value;
        if (listener)
            listener->valueChanged (pContext, this);
    }

    value = 0.0f;  // set button to UNSELECTED state
    if (listener)
        listener->valueChanged (pContext, this);

    // end of edit parameter
    endEdit ();
}


//------------------------------------------------------------------------
// CSplashScreen
//------------------------------------------------------------------------
/*! @class CSplashScreen
One click on its activated region and its pixmap is displayed, in this state the other control can not be used,
an another click on the displayed area reinstalls the normal frame.
This can be used to display a help view over the other views.
*/
// one click draw its pixmap, an another click redraw its parent
CSplashScreen::CSplashScreen (const CRect &size, CControlListener *listener, long tag,
                              CBitmap *background,
                              CRect   &toDisplay,
                              CPoint  &offset)
:    CControl (size, listener, tag, background),
    toDisplay (toDisplay), offset (offset), bitmapTransparency (255)
{}

//------------------------------------------------------------------------
CSplashScreen::~CSplashScreen ()
{}

//------------------------------------------------------------------------
void CSplashScreen::setBitmapTransparency (unsigned char transparency)
{
    bitmapTransparency = transparency;
    setTransparency (bitmapTransparency != 255);
}

//------------------------------------------------------------------------
void CSplashScreen::draw (CDrawContext *pContext)
{
 //   DBG ("CSplashScreen::draw");

    if (value && pBackground)
    {
        if (bTransparencyEnabled)
        {
            if (bitmapTransparency)
                pBackground->drawAlphaBlend (pContext, toDisplay, offset, bitmapTransparency);
            else
            pBackground->drawTransparent (pContext, toDisplay, offset);
        }
        else
            pBackground->draw (pContext, toDisplay, offset);
    }
    setDirty (false);
}

//------------------------------------------------------------------------
bool CSplashScreen::hitTest (const CPoint& where, const long buttons)
{
    bool result = CView::hitTest (where, buttons);
    if (result && !(buttons & kLButton))
        return false;
    return result;
}

//------------------------------------------------------------------------
void CSplashScreen::mouse (CDrawContext *pContext, CPoint &where, long button)
{
    if (!bMouseEnabled)
        return;

    if (button == -1) button = pContext->getMouseButtons ();

    if (listener && button & (kAlt | kShift | kControl | kApple))
    {
        if (listener->controlModifierClicked (pContext, this, button) != 0)
            return;
    }

    if (!(button & kLButton))
        return;

    value = !value;
    if (value)
    {
        if (getFrame () && getFrame ()->setModalView (this))
        {
            keepSize = size;
            size = toDisplay;
            mouseableArea = size;
//            draw (pContext);
            if (listener)
                listener->valueChanged (pContext, this);
        }
        setDirty ();
    }
    else
    {
        size = keepSize;
        mouseableArea = size;
        if (listener)
            listener->valueChanged (pContext, this);
        if (getFrame ())
        {
            getFrame ()->setDirty (true);
            getFrame ()->setModalView (NULL);
        }
    }
}

//------------------------------------------------------------------------
void CSplashScreen::unSplash ()
{
    setDirty ();
    value = 0.f;

    size = keepSize;
    if (getFrame ())
    {
        if (getFrame ()->getModalView () == this)
        {
            getFrame ()->setModalView (NULL);
            getFrame ()->redraw ();
        }
    }
}

//------------------------------------------------------------------------
// CVuMeter
//------------------------------------------------------------------------
CVuMeter::CVuMeter (const CRect &size, CBitmap *onBitmap, CBitmap *offBitmap,
                    long nbLed, const long style)
    : CControl (size, 0, 0),
      onBitmap (onBitmap), offBitmap (offBitmap), pOScreen (0),
      nbLed (nbLed), style (style)
{
    bUseOffscreen = false;

    setDecreaseStepValue (0.1f);

    if (onBitmap)
        onBitmap->remember ();
    if (offBitmap)
        offBitmap->remember ();

    rectOn  (size.left, size.top, size.right, size.bottom);
    rectOff (size.left, size.top, size.right, size.bottom);
}

//------------------------------------------------------------------------
CVuMeter::~CVuMeter ()
{
    if (onBitmap)
        onBitmap->forget ();
    if (offBitmap)
        offBitmap->forget ();
}

//------------------------------------------------------------------------
void CVuMeter::setDirty (const bool val)
{
    CView::setDirty (val);
}

//-----------------------------------------------------------------------------
bool CVuMeter::attached (CView *parent)
{
    if (pOScreen)
        delete pOScreen;
/*
    if (bUseOffscreen)
    {
        pOScreen = new COffscreenContext (getFrame (), (long)size.width (), (long)size.height (), kBlackCColor);
        rectOn  (0, 0, size.width (), size.height ());
        rectOff (0, 0, size.width (), size.height ());
    }
    else
*/
    {
        rectOn  (size.left, size.top, size.right, size.bottom);
        rectOff (size.left, size.top, size.right, size.bottom);
    }

    return CControl::attached (parent);
}

//------------------------------------------------------------------------
void CVuMeter::setUseOffscreen (bool val)
{
//    bUseOffscreen = val; // @TODO - faking offscreen
    bUseOffscreen = false;
}

//-----------------------------------------------------------------------------
bool CVuMeter::removed (CView *parent)
{
    if (pOScreen)
    {
        delete pOScreen;
        pOScreen = 0;
    }
    return CControl::removed (parent);
}

//------------------------------------------------------------------------
void CVuMeter::draw (CDrawContext *_pContext)
{
 //   DBG ("CVuMeter::draw");

    if (!onBitmap)
        return;

    CPoint pointOn;
    CPoint pointOff;
    CDrawContext *pContext = _pContext;

    bounceValue ();

    float newValue = oldValue - decreaseValue;
    if (newValue < value)
        newValue = value;
    oldValue = newValue;

/*
    if (bUseOffscreen)
    {
        if (!pOScreen)
        {
            pOScreen = new COffscreenContext (getFrame (), (long)size.width (), (long)size.height (), kBlackCColor);
            rectOn  (0, 0, size.width (), size.height ());
            rectOff (0, 0, size.width (), size.height ());
        }
        pContext = pOScreen;
    }
*/

    if (style & kHorizontal)
    {
        CCoord tmp = (long)(((long)(nbLed * newValue + 0.5f) / (float)nbLed) * onBitmap->getWidth ());
        pointOff (tmp, 0);
        if (!bUseOffscreen)
        tmp += size.left;

        rectOff.left = tmp;
        rectOn.right = tmp;
    }
    else
    {
        CCoord tmp = (long)(((long)(nbLed * (getMax () - newValue) + 0.5f) / (float)nbLed) * onBitmap->getHeight ());
        pointOn (0, tmp);
        if (!bUseOffscreen)
        tmp += size.top;

        rectOff.bottom = tmp;
        rectOn.top     = tmp;
    }

    if (offBitmap)
    {
        if (bTransparencyEnabled)
            offBitmap->drawTransparent (pContext, rectOff, pointOff);
        else
            offBitmap->draw (pContext, rectOff, pointOff);
    }

    if (bTransparencyEnabled)
        onBitmap->drawTransparent (pContext, rectOn, pointOn);
    else
        onBitmap->draw (pContext, rectOn, pointOn);

    if (pOScreen)
        pOScreen->copyFrom (_pContext, size);

    setDirty (false);
}

} // namespace VSTGUI

//------------------------------------------------------------------------
// END.
//------------------------------------------------------------------------
