//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  GCUI: Golded+ Character-oriented User Interface.
//  Class gwinput: Input form and field editing.
//  ------------------------------------------------------------------

#include <cstring>
#include <gkbdcode.h>
#include <gmemdbg.h>
#include <gstrall.h>
#include <gwinall.h>
#include <gwinhelp.h>
#include <gwinput.h>
#include <gutlclip.h>


//  ------------------------------------------------------------------

gwinput::gwinput(gwindow &w) : window(w)
{

    first_field = current = NULL;
    fill_acs = false;
    idle_attr = active_attr = edit_attr = LGREY_|_BLACK;
    idle_fill = active_fill = edit_fill = ' ';
    insert_mode = true;
    done = dropped = false;
    start_id = 0;
}


//  ------------------------------------------------------------------

gwinput::~gwinput()
{

    field* current = first_field;
    if(current)
    {
        do
        {
            field* junk = current;
            current = current->next;
            delete junk;
        }
        while(current);
    }
}


//  ------------------------------------------------------------------

void gwinput::setup(vattr i_attr, vattr a_attr, vattr e_attr, vchar fill, bool f_acs)
{

    idle_attr = i_attr;
    active_attr = a_attr;
    edit_attr = e_attr;
    fill_acs = f_acs;
    active_fill = edit_fill = fill;
}


//  ------------------------------------------------------------------

bool gwinput::validate()
{

    return true;
}


//  ------------------------------------------------------------------

void gwinput::before()
{

    current->activate();
}


//  ------------------------------------------------------------------

void gwinput::after()
{

    current->deactivate();
}


//  ------------------------------------------------------------------

void gwinput::add_field(int idnum, int wrow, int wcol, int field_width, std::string& dest, int dest_size, int cvt, int mode)
{

    field* fld = new field(this, idnum, wrow, wcol, field_width, dest, dest_size, cvt, mode);
    throw_new(fld);
    if(current)
    {
        current->next = fld;
        fld->prev = current;
        current = fld;
    }
    else
    {
        first_field = current = fld;
    }
}


//  ------------------------------------------------------------------

bool gwinput::move_to(int wrow, int wcol)
{

    field* f = field_at(wrow, wcol);
    if(f)
    {
        after();
        current = f;
        before();
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

gwinput::field* gwinput::field_at(int wrow, int wcol)
{

    field* here = first_field;
    do
    {
        if(here->row == wrow)
            if(in_range(wcol, here->column, here->max_column))
                return here;
        here = here->next;
    }
    while(here);
    return NULL;
}


//  ------------------------------------------------------------------

gwinput::field* gwinput::get_field(int id)
{

    field* here = first_field;
    do
    {
        if(here->id == id)
            return here;
        here = here->next;
    }
    while(here);
    return NULL;
}


//  ------------------------------------------------------------------

void gwinput::draw_all()
{

    first();
    do
    {
        current->draw();
    }
    while(next());
}


//  ------------------------------------------------------------------

void gwinput::reload_all()
{

    first();
    do
    {
        if(current->entry == gwinput::entry_new)
            *current->buf = NUL;
        else
            strxcpy(current->buf, current->destination.c_str(), current->buf_len+1);
        current->convert();
        current->buf_end_pos = strlen(current->buf);
        current->draw();
    }
    while(next());
}


//  ------------------------------------------------------------------

bool gwinput::first_visible()
{

    if(first())
    {
        if(not current->visible())
            if(next_visible())
                return true;
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::next_visible()
{

    field* here = current;
    while(here->next)
    {
        here = here->next;
        if(here->visible())
        {
            current = here;
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::previous_visible()
{

    field* here = current;
    while(here->prev)
    {
        here = here->prev;
        if(here->visible())
        {
            current = here;
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::last_visible()
{

    if(last())
    {
        if(not current->visible())
            if(previous_visible())
                return true;
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::first(int id)
{

    if(first_field)
    {
        current = first_field;
        if(id)
            while(current->id != id)
                next();
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::next()
{

    if(current->next)
    {
        current = current->next;
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::previous()
{

    if(current->prev)
    {
        current = current->prev;
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::last()
{

    if(first_field)
    {
        current = first_field;
        while(next())
            ;
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

void gwinput::show_cursor()
{

    if(insert_mode)
        vcursmall();
    else
        vcurlarge();
}


//  ------------------------------------------------------------------

void gwinput::drop_form()
{

    after();
    done = true;
    dropped = true;
}


//  ------------------------------------------------------------------

void gwinput::form_complete()
{

    after();
    done = true;
}


//  ------------------------------------------------------------------

void gwinput::field_complete()
{

    if(validate())
    {
        after();
        if(not next_visible())
        {
            done = true;
            return;
        }
        before();
    }
}


//  ------------------------------------------------------------------

void gwinput::go_next_field()
{

    after();
    if(not next_visible())
        first_visible();
    before();
}


//  ------------------------------------------------------------------

void gwinput::go_previous_field()
{

    after();
    if(not previous_visible())
        last_visible();
    before();
}


//  ------------------------------------------------------------------

void gwinput::go_up()
{

    int min_column = current->column;
    int max_column = current->max_column;
    int check_row = current->row - 1;
    int check_column = current->column + current->pos;
    for(int r=check_row; r>=0; r--)
    {
        int c;
        for(c=check_column; c<=max_column; c++)
        {
            if(move_to(r, c))
                return;
        }
        for(c=check_column-1; c>=min_column; c--)
        {
            if(move_to(r, c))
                return;
        }
    }
}


//  ------------------------------------------------------------------

void gwinput::go_down()
{

    int max_row = window.height() - (window.has_border() ? 2 : 0) - 1;
    int min_column = current->column;
    int max_column = current->max_column;
    int check_row = current->row + 1;
    int check_column = current->column + current->pos;
    for(int r=check_row; r<=max_row; r++)
    {
        int c;
        for(c=check_column; c<=max_column; c++)
        {
            if(move_to(r, c))
                return;
        }
        for(c=check_column-1; c>=min_column; c--)
        {
            if(move_to(r, c))
                return;
        }
    }
}


//  ------------------------------------------------------------------

void gwinput::go_left()
{

    if(not current->left())
        go_previous_field();
}


//  ------------------------------------------------------------------

void gwinput::go_right()
{

    if(not current->right())
        go_next_field();
}


//  ------------------------------------------------------------------

void gwinput::delete_left()
{

    current->delete_left();
}


//  ------------------------------------------------------------------

void gwinput::delete_char()
{

    current->delete_char();
}


//  ------------------------------------------------------------------

void gwinput::go_field_begin()
{

    current->home();
}


//  ------------------------------------------------------------------

void gwinput::go_field_end()
{

    current->end();
}


//  ------------------------------------------------------------------

void gwinput::go_form_begin()
{

    after();
    first_visible();
    before();
    current->home();
}


//  ------------------------------------------------------------------

void gwinput::go_form_end()
{

    after();
    last_visible();
    before();
    current->end();
}


//  ------------------------------------------------------------------

void gwinput::toggle_insert()
{

    insert_mode ^= true;
    show_cursor();
}


//  ------------------------------------------------------------------

void gwinput::restore_field()
{

    current->restore();
}


//  ------------------------------------------------------------------

void gwinput::delete_left_word()
{

    current->delete_word(true);
}


//  ------------------------------------------------------------------

void gwinput::delete_right_word()
{

    current->delete_word(false);
}


//  ------------------------------------------------------------------

void gwinput::go_left_word()
{

    current->left_word();
}


//  ------------------------------------------------------------------

void gwinput::go_right_word()
{

    current->right_word();
}


//  ------------------------------------------------------------------

void gwinput::enter_char(char ch)
{

    if(ch)
    {
        if(insert_mode)
            current->insert_char(ch);
        else
            current->overwrite_char(ch);
    }
}


//  ------------------------------------------------------------------

void gwinput::prepare_form()
{

    cursor_was_hidden = vcurhidden();
    draw_all();
    first(start_id);
    before();
    show_cursor();
}


//  ------------------------------------------------------------------

void gwinput::finish_form()
{

    if(not dropped)
    {
        first();
        do
        {
            current->commit();
        }
        while(next());
    }

    if(cursor_was_hidden)
        vcurhide();
}


//  ------------------------------------------------------------------

void gwinput::clear_field()
{

    current->clear_field();
}


//  ------------------------------------------------------------------

void gwinput::clipboard_cut()
{

    current->clipboard_copy();
    current->clear_field();
}


//  ------------------------------------------------------------------

void gwinput::clipboard_paste()
{

    if(insert_mode)
        current->clipboard_paste();
    else
    {
        current->clear_field();
        current->clipboard_paste();
    }
}


//  ------------------------------------------------------------------

void gwinput::clipboard_copy()
{

    current->clipboard_copy();
}


//  ------------------------------------------------------------------

bool gwinput::handle_other_keys(gkey&)
{

    return false;
}


//  ------------------------------------------------------------------

bool gwinput::handle_key(gkey key)
{

    switch(key)
    {
    case Key_Esc:
        drop_form();
        break;
    case Key_C_Ent:
        form_complete();
        break;
    case Key_Ent:
        field_complete();
        break;
    case Key_Tab:
        go_next_field();
        break;
    case Key_S_Tab:
        go_previous_field();
        break;
    case Key_Up:
        go_up();
        break;
    case Key_Dwn:
        go_down();
        break;
    case Key_Lft:
        go_left();
        break;
    case Key_Rgt:
        go_right();
        break;
    case Key_BS:
        delete_left();
        break;
    case Key_Del:
        delete_char();
        break;
    case Key_Home:
        go_field_begin();
        break;
    case Key_End:
        go_field_end();
        break;
    case Key_C_Home:
        go_form_begin();
        break;
    case Key_C_End:
        go_form_end();
        break;
    case Key_Ins:
        toggle_insert();
        break;
    case Key_A_BS:          // fall through
    case Key_C_R:
        restore_field();
        break;
    case Key_C_BS:
        delete_left_word();
        break;
    case Key_C_T:
        delete_right_word();
        break;
    case Key_C_Lft:
        go_left_word();
        break;
    case Key_C_Rgt:
        go_right_word();
        break;
#if !defined(__UNIX__) || defined(__USE_NCURSES__)
    case Key_S_Ins:         // fall through
#endif
    case Key_C_V:
        clipboard_paste();
        break;
#if !defined(__UNIX__) || defined(__USE_NCURSES__)
    case Key_S_Del:         // fall through
#endif
    case Key_C_X:
        clipboard_cut();
        break;
#if !defined(__UNIX__) || defined(__USE_NCURSES__)
    case Key_C_Ins:         // fall through
#endif
    case Key_C_C:
        clipboard_copy();
        break;
#if !defined(__UNIX__) || defined(__USE_NCURSES__)
    case Key_C_Del:         // fall through
#endif
    case Key_C_Y:           // fall through
    case Key_C_D:
        clear_field();
        break;
    default:
        if(not handle_other_keys(key))
            enter_char(KCodAsc(key));
    }

    return not done;
}


//  ------------------------------------------------------------------

gwinput::field::field(gwinput* iform, int idnum, int wrow, int wcol, int field_width, std::string& dest, int dest_size, int cvt, int mode)
    : destination(dest)
{
    prev = next = NULL;
    pos = buf_pos = buf_left_pos = 0;

    form = iform;
    id = idnum;
    row = wrow;
    column = wcol;
    max_pos = field_width - 1;
    max_column = wcol + max_pos;
    buf_len = dest_size - 1;
    buf = new char[dest_size];
    throw_new(buf);
    conversion = cvt;
    entry = entry_mode = mode;
    attr = form->idle_attr;
    fill = form->idle_fill;
    fill_acs = form->fill_acs;

    if(entry == gwinput::entry_new)
        *buf = NUL;
    else
        strxcpy(buf, dest.c_str(), dest_size);
    convert();
    buf_end_pos = strlen(buf);
}


//  ------------------------------------------------------------------

gwinput::field::~field()
{

    delete[] buf;
}


//  ------------------------------------------------------------------

bool gwinput::field::visible()
{

    return max_pos != -1;
}


//  ------------------------------------------------------------------

void gwinput::field::convert()
{

    switch(conversion)
    {
    case gwinput::cvt_lowercase:
        strlwr(buf);
        break;
    case gwinput::cvt_uppercase:
        strupr(buf);
        break;
    case gwinput::cvt_mixedcase:
        struplow(buf);
        break;
    }
}


//  ------------------------------------------------------------------

void gwinput::field::update()
{

    buf_end_pos = strlen(buf);
    end();
}


//  ------------------------------------------------------------------

void gwinput::field::activate()
{

    buf_end_pos = strlen(buf);
    entry = entry_mode;
    if(entry == gwinput::entry_conditional or entry == gwinput::entry_noedit)
    {
        attr = form->active_attr;
        fill = form->active_fill;
        int entry_bak = entry;
        entry = gwinput::entry_update;   // cheat adjust_mode() in end()
        end();
        entry = entry_bak;
    }
    else
    {
        // 0 == entry_new, 1 == entry_update
        entry ? end() : home();
    }
}


//  ------------------------------------------------------------------

void gwinput::field::deactivate()
{

    fill = form->idle_fill;
    attr = form->idle_attr;
    draw();
}


//  ------------------------------------------------------------------

void gwinput::field::restore()
{

    std::string tmp(buf);
    strxcpy(buf, destination.c_str(), buf_len+1);
    destination = tmp;
    convert();
    activate();
}


//  ------------------------------------------------------------------

void gwinput::field::commit()
{

    destination = buf;
}


//  ------------------------------------------------------------------

void gwinput::field::move_cursor()
{

    form->window.move_cursor(row, column+pos);
}


//  ------------------------------------------------------------------

void gwinput::field::draw(int from_pos)
{

    if(visible())
        form->window.printns(row, column+from_pos, attr, buf+buf_left_pos+from_pos, 1+max_pos-from_pos, fill, attr | (fill_acs ? ACSET : 0));
}


//  ------------------------------------------------------------------

bool gwinput::field::adjust_mode()
{

    if(entry != gwinput::entry_update and entry != gwinput::entry_noedit)
    {
        entry = gwinput::entry_update;
        attr = form->edit_attr;
        fill = form->edit_fill;
        return true;
    }
    return false;
}


//  ------------------------------------------------------------------

void gwinput::field::conditional()
{

    if(entry == gwinput::entry_conditional)
    {
        clear_field();
    }
}


//  ------------------------------------------------------------------

void gwinput::field::move_left()
{

    buf_pos--;
    if(pos > 0)
    {
        pos--;
        move_cursor();
    }
    else
    {
        buf_left_pos--;
        draw();
    }
}


//  ------------------------------------------------------------------

void gwinput::field::move_right()
{

    buf_pos++;
    if(pos < max_pos)
    {
        pos++;
        move_cursor();
    }
    else
    {
        buf_left_pos++;
        draw();
    }
}


//  ------------------------------------------------------------------

bool gwinput::field::left()
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos > 0)
        {
            move_left();
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::right()
{

    if(adjust_mode())
    {
        draw();
        return true;
    }

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos < buf_end_pos)
        {
            move_right();
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::left_word()
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos > 0)
        {
            move_left();
            if(not isxalnum(buf[buf_pos]))
            {
                while(not isxalnum(buf[buf_pos]) and (buf_pos > 0))
                    move_left();
                while(isxalnum(buf[buf_pos]) and (buf_pos > 0))
                    move_left();
            }
            else
            {
                while(isxalnum(buf[buf_pos]) and (buf_pos > 0))
                    move_left();
            }

            if(buf_pos != 0)
                move_right();
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::right_word()
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos < buf_end_pos)
        {
            move_right();
            if(not isxalnum(buf[buf_pos]))
            {
                while(not isxalnum(buf[buf_pos]) and ((buf_pos+1) <= buf_end_pos))
                    move_right();
            }
            else
            {
                while(isxalnum(buf[buf_pos]) and ((buf_pos+1) <= buf_end_pos))
                    move_right();
                while(not isxalnum(buf[buf_pos]) and ((buf_pos+1) <= buf_end_pos))
                    move_right();
            }
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::delete_left()
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos > 0)
        {
            left();
            return delete_char();
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::delete_char()
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {
        if(buf_pos < buf_end_pos)
        {
            buf_end_pos--;
            memmove(buf+buf_pos, buf+buf_pos+1, buf_len-buf_pos);
            draw(pos);
            move_cursor();
            return true;
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::delete_word(bool left)
{

    if(adjust_mode())
        draw();

    if(entry != gwinput::entry_noedit)
    {

        bool state = make_bool(isspace(buf[buf_pos-((int) left)]));

        while(left ? buf_pos > 0 : buf_pos < buf_end_pos)
        {
            left ? delete_left() : delete_char();

            if(make_bool(isspace(buf[buf_pos-((int) left)])) != state)
                break;
        }
        return true;

    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::insert_char(char ch)
{

    if(entry != gwinput::entry_noedit)
    {

        conditional();

        if(buf_end_pos < buf_len)
        {
            int len = buf_end_pos - buf_pos;
            memmove(buf+buf_pos+1, buf+buf_pos, len+1);
            buf_end_pos++;
            return overwrite_char(ch);
        }
    }
    return false;
}


//  ------------------------------------------------------------------

bool gwinput::field::overwrite_char(char ch)
{

    if(entry != gwinput::entry_noedit)
    {

        conditional();

        switch(conversion)
        {
        case gwinput::cvt_lowercase:
            ch = (char)g_tolower(ch);
            break;
        case gwinput::cvt_uppercase:
            ch = (char)g_toupper(ch);
            break;
        }

        buf[buf_pos] = ch;
        if(buf_pos == buf_end_pos)
        {
            buf_end_pos++;
            buf[buf_end_pos] = NUL;
        }

        if(conversion == gwinput::cvt_mixedcase)
        {
            struplow(buf);
            draw();
        }
        else
        {
            draw(pos);
        }

        right();
    }

    return true;
}


//  ------------------------------------------------------------------

bool gwinput::field::home()
{

    adjust_mode();

    pos = buf_pos = buf_left_pos = 0;
    draw();
    move_cursor();
    return true;
}


//  ------------------------------------------------------------------

bool gwinput::field::end()
{

    adjust_mode();

    buf_pos = buf_end_pos;
    if(buf_pos == buf_len)
        buf_pos--;
    pos = minimum_of_two(max_pos, buf_pos);
    buf_left_pos = buf_pos - pos;
    draw();
    move_cursor();
    return true;
}


//  ------------------------------------------------------------------

void gwinput::field::clear_field()
{

    if(entry != gwinput::entry_noedit)
    {

        pos = buf_pos = buf_left_pos = buf_end_pos = 0;
        *buf = NUL;
        adjust_mode();
        draw();
        move_cursor();
    }
}


//  ------------------------------------------------------------------

void gwinput::field::clipboard_paste()
{

    if(entry != gwinput::entry_noedit)
    {

        conditional();

        gclipbrd clipbrd;

        if(not clipbrd.openread())
            return;

        char *clpbuf = (char *)throw_malloc(buf_len + 1);

        if(clipbrd.read(clpbuf, buf_len + 1))
        {

            size_t len = strlen(clpbuf);
            if((len != 0) and (clpbuf[len - 1] == '\n'))
            {
                clpbuf[--len] = NUL;

                switch(conversion)
                {
                case gwinput::cvt_lowercase:
                    strlwr(clpbuf);
                    break;
                case gwinput::cvt_uppercase:
                    strupr(clpbuf);
                    break;
                }
            }

            if((buf_pos == buf_end_pos) or ((buf_pos + len) >= buf_len))
            {
                strxcat(buf, clpbuf, buf_len + 1);
                buf_end_pos = strlen(buf);
                end();
            }
            else
            {
                strxcat(clpbuf, buf + buf_pos, buf_len + 1);
                buf[buf_pos] = NUL;
                strxcat(buf, clpbuf, buf_len + 1);
                buf_end_pos = strlen(buf);
                for(int i = 0; i < len; i++)
                    move_right();
            }

            if(conversion == gwinput::cvt_mixedcase)
            {
                struplow(buf);
                draw();
            }
            else
            {
                draw();
            }
        }

        throw_free(clpbuf);

        clipbrd.close();
    }
}


//  ------------------------------------------------------------------

void gwinput::field::clipboard_copy()
{

    if(entry != gwinput::entry_noedit)
    {

        gclipbrd clipbrd;

        clipbrd.writeclipbrd(buf);
    }
}


//  ------------------------------------------------------------------

bool gwinput2::run(int helpcat)
{

    prepare_form();
    whelppcat(helpcat);

    while(handle_key(getxch()));

    whelpop();
    finish_form();

    return not dropped;
}


//  ------------------------------------------------------------------

