//  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;
}


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

