If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// gui_inputbox.cpp

#include "library.h"
#include "hal.h"

#include "gui_inputbox.h"

/*
==============
GInputBox::GInputBox

Set default parameters and make focussable
==============
*/
GInputBox::GInputBox(GPanel *pParent, int x, int y, int w, int h,
                     const char *text, int align)
    : GLabel(pParent, x, y, w, h, text, align)
{
    SetCanFocus(true);

    m_iMaxLen = -1;
    m_iCursorPos = 0;
    m_iCursorTime = 0;

    m_iMode = im_string;

    SetBorder(3, 3);
}

/*
==============
GInputBox::IsValid

Returns true if the input matches all criterias
==============
*/
bool GInputBox::IsValid()
{
    if (m_iMode == im_string) {
        if (m_pszText && (int)strlen(m_pszText) > m_iMaxLen)
            return false;
    } else {
        char *p;
        int i;

        if (!m_pszText)
            return false;

        i = strtol(m_pszText, &p, 0);
        if (*p)
            return false;

        if (i < m_iMin || i > m_iMax)
            return false;
    }

    return true;
}

/*
==============
GInputBox::SetMaxLen

Automatically cut the current text if needed
==============
*/
void GInputBox::SetMaxLen(int iMaxLen)
{
    if (m_pszText && (int)strlen(m_pszText) > iMaxLen) {
        char *buf;

        buf = (char *)L_Malloc(iMaxLen+1, TAG_GUI);
        xstrcpy(buf, iMaxLen+1, m_pszText);

        SetText(buf);

        L_Free(buf);
    }

    m_iMaxLen = iMaxLen;
}

/*
==============
GInputBox::SetModeInteger

Changes the mode to integer and sets the min/max value(s)
==============
*/
void GInputBox::SetModeInteger(int min, int max)
{
    m_iMode = im_integer;
    m_iMin = min;
    m_iMax = max;
}

/*
==============
GInputBox::Draw

Draw the input box text first, then the cursor and outline
==============
*/
void GInputBox::Draw()
{
    byte *color;

    color = m_color;

    if (m_pszText)
        doDraw(color[0], color[1], color[2], color[3]);

    if (IsFocussed()) {
        m_iCursorTime += hal->frametime;
        if (!((m_iCursorTime / 500) % 2)) {
            LRect rc;

            GetCharRect(m_iCursorPos, &rc);

            hal->FillRect(rc.pos[0], rc.pos[1], 2, rc.size[1],
                        color[0], color[1], color[2], color[3]);
        }
    }

    hal->DrawLine(0, 0, m_rc.size[0], 0, color[0], color[1], color[2], color[3]);
    hal->DrawLine(m_rc.size[0], 0, m_rc.size[0], m_rc.size[1], color[0], color[1], color[2], color[3]);
    hal->DrawLine(m_rc.size[0], m_rc.size[1], 0, m_rc.size[1], color[0], color[1], color[2], color[3]);
    hal->DrawLine(0, m_rc.size[1], 0, 0, color[0], color[1], color[2], color[3]);
}

/*
==============
GInputBox::BoundCursor

Ensure a valid cursorpos
==============
*/
void GInputBox::BoundCursor()
{
    int len;

    if (m_iCursorPos <= 0)
        m_iCursorPos = 0;
    else {
        len = GetTextLength();
        if (m_iCursorPos > len)
            m_iCursorPos = len;
    }
}

/*
==============
GInput::ScrollToCursor

Update scroll position so that the cursor is just visible
==============
*/
void GInputBox::ScrollToCursor()
{
    LRect rc;

    GetCharRect(m_iCursorPos, &rc);

    if (rc.pos[0] < m_iBorderX) {
        m_iScrollX -= m_iBorderX - rc.pos[0] + 48;
        if (m_iScrollX < 0)
            m_iScrollX = 0;
    } else if (rc.pos[0]+2 >= m_rc.size[0]-m_iBorderX)
        m_iScrollX += rc.pos[0]+2 - (m_rc.size[0]-m_iBorderX) + 48;

    if (rc.pos[1] < m_iBorderY)
        m_iScrollY -= m_iBorderY - rc.pos[1];
    else if (rc.pos[1]+rc.size[1] >= m_rc.size[1]-m_iBorderY)
        m_iScrollY += rc.pos[1]+rc.size[1] - (m_rc.size[1]-m_iBorderY);
}

/*
==============
GInputBox::InputChar

Adds the given char at the current cursorpos
==============
*/
void GInputBox::InputChar(char c)
{
    int len;
    char *buf;

    BoundCursor();

    len = GetTextLength();
    if (m_iMaxLen > 0 && len >= m_iMaxLen)
        return;

    buf = (char *)L_Malloc(len+2, TAG_GUI);
    memcpy(buf, m_pszText, m_iCursorPos);
    memcpy(buf+m_iCursorPos+1, m_pszText+m_iCursorPos, len-m_iCursorPos);
    buf[m_iCursorPos] = c;
    buf[len+1] = 0;

    if (m_iMode == im_integer) {
        char *p;
        int i;

        i = strtol(buf, &p, 0);
        if (*p || i < m_iMin || i > m_iMax) {
            L_Free(buf);
            return;
        }
    }

    SetText(buf);
    L_Free(buf);

    m_iCursorPos++;
    m_iCursorTime = 0;
    ScrollToCursor();

    Changed.Emit();
}

/*
==============
GInputBox::DeleteChar

Delete the char to the cursor's right
==============
*/
void GInputBox::DeleteChar()
{
    int len;

    len = GetTextLength();
    if (m_iCursorPos < 0 || m_iCursorPos >= len)
        return;

    if (len == 1)
        SetText(0);
    else {
        m_pszText = (char *)L_Realloc(m_pszText, len, TAG_GUI);
        memmove(m_pszText+m_iCursorPos, m_pszText+m_iCursorPos+1, len-m_iCursorPos-1);
        m_pszText[len-1] = 0;
    }

    m_iCursorTime = 0;
    ScrollToCursor();

    Changed.Emit();
}

/*
==============
GInputBox::SetCursorPos

Sets the cursor to the given location
==============
*/
void GInputBox::SetCursorPos(int pos)
{
    m_iCursorPos = pos;
    m_iCursorTime = 0;

    BoundCursor();
    ScrollToCursor();
}

/*
==============
GInputBox::MoveCursor

Moves the cursor n characters forward or backward
==============
*/
void GInputBox::MoveCursor(int n)
{
    m_iCursorPos += n;
    m_iCursorTime = 0;

    BoundCursor();
    ScrollToCursor();
}

/*
==============
GInputBox::GainFocus

Purely cosmetic: show the cursor immediately
==============
*/
void GInputBox::GainFocus(bool bGain)
{
    m_iCursorTime = 0;
}

/*
==============
GInputBox::Key

Handle input etc..
==============
*/
bool GInputBox::Key(int code, char c, bool down)
{
    if (down)
    {
        if (c >= 32) {
            InputChar(c);
            return true;
        }

        switch(code) {
        case KEY_LEFT:
            MoveCursor(-1);
            return true;
        case KEY_RIGHT:
            MoveCursor(1);
            return true;
        case KEY_BACKSPACE:
            if (m_iCursorPos > 0) {
                MoveCursor(-1);
                DeleteChar();
            }
            return true;
        case KEY_RETURN:
            if (m_pParent) {
                m_pParent->FocusNext();
                return true;
            }
            break;

        case KEY_DELETE:
            DeleteChar();
            return true;
        }
    }

    return GLabel::Key(code, c, down);
}