//      $Id: maskfld.cpp,v 1.53 1994-11-28 11:01:17 guy Exp $
#include <xvt.h>

#include <applicat.h>
#include <defmask.h>
#include <execp.h>
#include <mailbox.h>
#include <mask.h>
#include <relation.h>
#include <sheet.h>
#include <tabutil.h>
#include <urldefid.h>
#include <utility.h>
#include <validate.h>
#include <xvtility.h>

#if XVT_OS == XVT_OS_WIN
#include <windows.h>
#endif

HIDDEN const int MAXSTR = 128;
HIDDEN char __fpark[MAXSTR];            // Temporary for get/set window data
HIDDEN TFixed_string fpark(__fpark, MAXSTR);

///////////////////////////////////////////////////////////
// Field Flags
///////////////////////////////////////////////////////////

// Certified 100%
TMask_field::TField_Flags::TField_Flags()
{
  automagic = persistent   = FALSE;
  enabled = enable_default = TRUE;
  showed  = show_default   = TRUE;
  uppercase = rightjust    = FALSE;
  zerofilled               = FALSE;
  dirty = focusdirty       = FALSE;
  roman = exchange         = FALSE;
  firm = ghost             = FALSE;
  password                 = FALSE;
}

// Certified 100%
char TMask_field::TField_Flags::update(const char* s)
{
  const char* kk = s;
  for (; *s; s++)
    switch(toupper(*s))
    {
    case '*':
      password = TRUE; break;
    case 'A':
      automagic = persistent = TRUE; break;
    case 'D': 
      enable_default = enabled = FALSE; break;
    case 'F': 
      firm = persistent = TRUE; break;
    case 'G': 
      ghost = TRUE; break;
    case 'H': 
      show_default  = showed = FALSE; break;
    case 'M': 
      roman = TRUE; break;
    case 'P': 
      persistent = TRUE; break;
    case 'R': 
      rightjust = TRUE; break;
    case 'U': 
      uppercase = TRUE; break;
    case 'V': 
      exchange = TRUE; break;
    case 'Z': 
      zerofilled = TRUE; break;
      default :  
#ifdef DBG                      
      yesnofatal_box("FLAG sconosciuto in %s: %c", kk, *s);
#endif                          
      break;
    }
  return *s;
}


///////////////////////////////////////////////////////////
// TMask_field
///////////////////////////////////////////////////////////

int TMask_field::_x;          // Position of the field
int TMask_field::_y;
int TMask_field::_width;
TFixed_string TMask_field::_prompt(__fpark, MAXSTR);           // Prompt of the field

TMask_field::TMask_field(TMask* m)
: _mask(m), _win(NULL_WIN), _promptwin(NULL_WIN), _dlg(0),
  _keys(0), _groups(0), _help(0), _handler(NULL),
  _validate_func(-1), _validate_parms(1), _field(NULL)
{}

// Certified 100%
TMask_field::~TMask_field()
{
  if (_field) delete _field;
}


// Certified 100%
bool TMask_field::is_edit() const
{
  const word c = class_id();
  return c == CLASS_EDIT_FIELD || c == CLASS_REAL_FIELD || c == CLASS_DATE_FIELD;
}


// Certified 100%
const char* TMask_field::class_name() const
{ return "Field"; }


// Certified 100%
word TMask_field::class_id() const
{ return CLASS_FIELD; }


// Certified 100%
bool TMask_field::ok() const
{ return win() != NULL_WIN && dlg() >= -1; }


// Certified 100%
void TMask_field::parse_head(TScanner&)
{}


// Certified 100%
short TMask_field::atodlg(const char* s) const
{
  short d = s ? atoi(s) : 0;

#ifdef DBG
  if (d == 0 || d < -1 || d > 1000)
  {
    yesnofatal_box("Identificatore non valido nel campo %d: '%s'", dlg(), s);
    d = -1;
  }
#endif  

  return d;
}

void TMask_field::construct(short id, const char* prompt, int x, int y,
                            int len, WINDOW parent, const char* flags, int width)
{
  _x = x; _y = y;
  _prompt = prompt;
  _size = len;
  if (class_id() == CLASS_REAL_FIELD)
  {
    ((TReal_field*)this)->set_decimals(width);
    _width = _size;
  }
  else _width = width < 1 ? _size : width;
  _dlg = id;
  _flags.update(flags);

  create(parent);
}


void TMask_field::construct(TScanner& scanner, WINDOW parent)
{
  _dlg = atodlg(scanner.pop());
  parse_head(scanner);
  _prompt.cut(0);
  
  scanner.popkey();                 // BEGIN
#ifdef DBG  
  if (scanner.key() != "BE")          
  {
    yesnofatal_box("Testata errata o BEGIN mancante nel campo %d", _dlg);
    scanner.push();
  }
#endif          

  while(scanner.popkey() != "EN")       // END of control
    parse_item(scanner);
  create(parent);
}

bool TMask_field::parse_item(TScanner& scanner)
{
  if (scanner.key() == "PR")                    // PROMPT
  {
    _x = scanner.integer();
    _y  = scanner.integer();
    _prompt = scanner.string();
    return TRUE;
  }

  if (scanner.key() == "FL")                    // FLAG
  {
    const char* f = scanner.string();
    return _flags.update(f) == '\0';
  }

  if (scanner.key() == "FI")                    // FIELD
  {
    CHECKD(_field == NULL, "Only one FIELD, please: ", dlg());
    _field = new TFieldref(scanner.line(), 0);
    return TRUE;
  }

  if (scanner.key() == "HE")                    // HELP
  {
    _help = scanner.string();
    return TRUE;
  }

  if (scanner.key() == "KE")                    // KEY
  {
    _keys.set(scanner.line());
    _keys.set(0L);
    return TRUE;
  }

  if (scanner.key() == "ME")
  {
    if (_message.objptr(0) == 0)
      _message.add(new TToken_string(64), 0);
    TToken_string& ts = (TToken_string&)_message[0];
    ts.add(scanner.line().strip_spaces());
    return TRUE;
  }

  if (scanner.key() == "GR")
  {
    _groups.set(scanner.line());
    return TRUE;
  }

  return FALSE;
}


long TMask_field::default_flags() const
{
  long f = CTL_FLAG_NATIVE_JUST;

  if (_flags.show_default == FALSE)   f |= CTL_FLAG_INVISIBLE;
  if (_flags.enable_default == FALSE) f |= CTL_FLAG_DISABLED;

  return f;
}

// Certified 100%
WINDOW TMask_field::wincreate(WIN_TYPE ct, short dx, short dy,
                              const char* title, WINDOW parent,
                              long flags)
{
  _win = xvt_create_control(ct,
                            _x, _y, dx, dy,
                            (char*)title,
                            parent,
                            flags | default_flags(),
                            PTR_LONG(this),
                            _dlg);

  return _win;
}


// Certified 100%
WINDOW TMask_field::parent() const
{ return get_parent(win()); }


// Certified 90%
int TMask_field::create_prompt(WINDOW parent, int width, int heigth)
{
  const WIN_TYPE wt = (heigth < 3) ? WC_TEXT : WC_GROUPBOX;
  if (width < 1) width = strlen(_prompt);
  _prompt.rtrim();              // Could save some bytes

  if (width)
  {
    // Static controls shouldn't be grayed
    const long flags = default_flags() & (~CTL_FLAG_DISABLED);
#if XVT_OS == XVT_OS_WIN
    char* k = strchr(_prompt, '~');
    if (k != NULL) *k = '&';
#endif
    _promptwin = xvt_create_control
      (
       wt,
       _x, _y, width, heigth,
       _prompt,
       parent,
       flags,
       0L,
       -1
       );
  }
  return width;
}


// Certified 100%
void TMask_field::destroy()
{
  if (_win)
  { close_window(_win); _win = NULL_WIN; }
  if (_promptwin)
  { close_window(_promptwin); _promptwin = NULL_WIN; }
}


// Certified 100%
void TMask_field::create(WINDOW parent)
{
  _width = strlen(_prompt);
  if (_width)
    wincreate(WC_TEXT, _width, 1, _prompt, parent, CTL_FLAG_LEFT_JUST);
}


// Certified 100%
void TMask_field::enable(bool on)
{
  const word c = class_id();
  if (c != CLASS_FIELD)
  {
    enable_window(_win, on);
    _flags.enabled = on;
  }
}


// Certified 100%
void TMask_field::enable_default()
{
  const bool ed = _flags.enable_default;
  enable(ed);
}


// Certified 100%
void TMask_field::show(bool on)
{
  show_window(_win, on);
  if (_promptwin != NULL_WIN)
    show_window(_promptwin, on);
  _flags.showed = on;
}


// Certified 100%
void TMask_field::show_default()
{          
  const bool sd = _flags.show_default;
  show(sd);
}


// Certified 100%
bool TMask_field::active() const
{
  return enabled() && showed() && class_id() != CLASS_FIELD;
};


// Certified 90%
word TMask_field::last_key() const
{
  long u = _keys.last_one();
  if (u < 0) u = 0;
  return (word)u;
}

void TMask_field::set_dirty(bool d) 
{ 
#ifdef DBG
  if (dlg() == 103)
    d *= 1;
#endif

  if (_flags.dirty > TRUE && d == FALSE)
    return;
  _flags.dirty = d; 
  set_focusdirty(d); 
}



// Certified 99%
const char* TMask_field::get_window_data() const
{
  get_title(win(), __fpark, MAXSTR);
  return __fpark;
}


// Certified 99%
void TMask_field::set_window_data(const char* data)
{
  if (data != NULL)
    set_title(win(), (char*)data);
}


// Certified 100%
void TMask_field::set_field_data(const char*)
{}


// Certified 100%
const char* TMask_field::get_field_data() const
{ return NULL; }


const char* TMask_field::picture_data(const char* data, bool video)
{            
  fpark = data;
  if (video) 
  {
    fpark.trim();
    if (_flags.uppercase) fpark.upper();
  }  
  return fpark;
}


// Certified 90%
const char* TMask_field::prompt() const
{
  if (_promptwin != NULL_WIN)
    _prompt = xvt_get_title(_promptwin);
  else
    _prompt = "";

  return _prompt;
}


// Certified 100%
void TMask_field::reset()
{
  if (!_flags.persistent && class_id() != CLASS_FIELD)
    set("");
}


// Certified 100%
void TMask_field::set_prompt(const char* p)
{
  if (_promptwin != NULL_WIN)
    set_title(_promptwin, (char*) p);
}


void TMask_field::set(const char* s)
{
  if (mask().is_running())
  {
    set_window_data(s);
    set_dirty();
  }
  else
    set_field_data(s);
}

TString& TMask_field::get() const
{
  static TString80 gpark;

  if (mask().is_running())
    gpark = get_window_data();
  else
    gpark = get_field_data();

  return gpark.trim();
}


void TMask_field::undo()
{
  set_window_data(get_field_data());
}


bool TMask_field::autoload(const TRelation* r)
{
  if (_field)
  {
    set(_field->read(r));
    return TRUE;
  }
  return FALSE;
}


bool TMask_field::autosave(TRelation* r) const
{
  if (_field)
  {
    _field->write(get(), r);
    return TRUE;
  }
  return FALSE;
}


// Certified 50%
HIDDEN void modify_list(bool add, TMask_field& f, TToken_string& msg)
{
#ifdef DBG
  if (f.class_id() != CLASS_LIST_FIELD)
  {
    error_box("Can't add/delete items of non list-box field %d", f.dlg());
    return;
  }  
#endif  
  TList_field& l = (TList_field&)f;
  
  TToken_string item(16); 
  item = msg.get(); 
  if (add) item.add(msg.get()); 
  item.strip("\"'");
  if (add)
    l.add_item(item);
  else
    l.delete_item(item);  
}


// Certified 90%
HIDDEN const char* copy_value(TToken_string& msg, const TString& val)
{
  int from = msg.get_int()-1;
  int to = -1;
  if (from < 0) from = 0;
  else to = msg.get_int();
  return val.sub(from, to);
}

void TMask_field::send_key(KEY k, short to) 
{ 
  mask().send_key(k, to, this); 
}


// Certified 90%
bool TMask_field::do_message(int num)
{               
  const int MAX_CMD = 14;
  static const char* commands[MAX_CMD] =
  {
    "ADD",         // 0
    "CLEAR",       // 1
    "CO",          // 2
    "DEL",         // 3
    "DIRTY",       // 4
    "DISABLE",     // 5
    "ENABLE",      // 6
    "ENABLEDEF",   // 7 
    "EXIT",        // 8
    "HIDE",        // 9
    "PUSH",        // 10
    "RESET",       // 11
    "SHOW",        // 12
    "UNDO"         // 13
    };

  TToken_string* message = (TToken_string*)_message.objptr(num);
  if (message == NULL || message->empty()) return FALSE;

  TToken_string msg(16, ',');
  TString80 value;

  for (const char* m = message->get(0); m && *m; m = message->get())
  {
    KEY key = 0;
    msg = m;
    value = msg.get();
    value.trim();
    const char* dlg = msg.get();
    
    int cmd = -1;
    if (isalpha(value[0]))                       // binary search
    {         
      int f = 0, l = MAX_CMD-1;                    
      while (TRUE)
      {
        cmd = (f+l)>>1;                          
        const int cmp = strcmp(value, commands[cmd]);
        if (cmp == 0) break;
        if (cmp > 0) f = cmd+1;
        else         l = cmd-1;
        if (f > l) 
        {
          cmd = -1;
          break;
        }  
      }
    }

    if (cmd == 8)
    {
      mask().stop_run(atoi(dlg));
      continue;
    }

    short fld = (dlg && dlg[0] > ' ') ? atodlg(dlg) : 0;
    bool broadcast = dlg && strchr(dlg, '@');
    if (value[0] == '"') value = value.strip("\"'");
    else switch (cmd)
    {
    case 0:
      modify_list(TRUE, mask().field(fld), msg); continue;
    case 1:
      key = 11000+'c'; break;
    case 2:
      value = copy_value(msg, get()); break;
    case 3:
      modify_list(FALSE, mask().field(fld), msg); continue;
    case 4:
      mask().field(fld).set_dirty(); continue;
    case 5:
      key = 11000+'d'; break;
    case 6:
      key = 11000+'e'; break;
    case 7:  
      key = 11000+'E'; break;
    case 9:
      key = 11000+'h'; break;
    case 10:
      key = K_SPACE; break;
    case 11:
      key = K_F2; break;
    case 12:
      key = 11000+'s'; break;
    case 13:
      key = K_F3; break;
    default:                        
      key = atoi(value);
      break;
    }

    if (key)
    {
      if (key > 0)
      {
        if (broadcast) fld = -fld;
        send_key(key, fld);
      }  
    }
    else
    {
      // Setta a value il campo fld solo se ha un valore diverso da value
      if (broadcast)
      {
        for (int i = 0; i < mask().fields(); i++)
        {
          TMask_field& f = mask().fld(i);
          if (f.in_group((int)fld))
          {
            const char* prev = f.get();
            if (value != prev)
            {
              f.set(value);
              if (f.showed() || f.ghost())
                f.on_hit();
            }
          }
        }
      }
      else
      {
        TMask_field& f = mask().field(fld);
        const char* prev = f.get();
        if (value != prev)
        {
          f.set(value);
          if (f.showed() || f.ghost())
            f.on_hit();
        }
      }
    }
  }

  return TRUE;
}


// Certified 90%
bool TMask_field::on_hit()
{
  if (_handler)
  {
    bool ok = _handler(*this, is_edit() ? K_TAB : K_SPACE);
    if (!ok) return FALSE;
  }
  do_message(0);
  return TRUE;
}


bool TMask_field::to_check(KEY k, bool checkrun) const
{   
  bool yes = (k == K_TAB && focusdirty()) || (k == K_ENTER && dirty());
  
  if (!yes && checkrun)
    yes = k == K_TAB && !mask().is_running();
  
  return yes;
}

// Certified 90%
bool TMask_field::on_key(KEY key)
{
  if (key > 11000)
  {
    switch(key-11000)
    {
    case 'E':
      enable_default(); break;
    case 'c':
      reset(); on_hit();
    case 'd':
      disable(); break;
    case 'e':
      enable(); break;
    case 'h':
      hide(); break;
    case 's':
      show(); break;
#ifdef DBG              
      default :
      return yesnofatal_box("Invalid key sent to field %d: %d", dlg(), key);
#endif          
    }
    return TRUE;
  }

  switch(key)
  {
  case K_SPACE:
    set_dirty();
    break;
  case K_PREV:
  case K_NEXT:
    send_key(key, 0);
    break;
  case K_F1:
    if (_help.not_empty())
      message_box(_help);
    else 
      beep();
    break;
  case K_F2:
    if (is_edit()) set("");
    else reset();
    set_dirty();
    break;
  case K_F3:
    undo();
    set_dirty();
    break;
  default:
    break;
  }

  if (_handler)
    return _handler(*this, key);

  return TRUE;
}

void TMask_field::highlight() const
{
  set_front_window(win());
}

void TMask_field::set_focus() const
{                                                
  const bool force = mask().is_running();
  mask().set_focus_win(win(), force);
}

HIDDEN char* const _msg = &__tmp_string[512];
#define build_msg() va_list argptr;va_start(argptr,fmt);vsprintf(_msg,fmt,argptr);va_end(argptr)

bool TMask_field::error_box(const char* fmt, ...) const
{
  build_msg();
  if (mask().is_sheetmask() && !mask().is_running())
  {                       
    xvt_statbar_set(_msg);
    beep();
  }  
  else
  {
    set_focus();
    ::error_box("%s", _msg);
    set_focus();
  }
  return FALSE;
}

bool TMask_field::message_box(const char* fmt, ...) const
{                         
  set_focus();
  build_msg();
  ::message_box("%s", _msg);
  set_focus();
  return FALSE;
}

bool TMask_field::warning_box(const char* fmt, ...) const
{                         
  build_msg();
  
  if (mask().is_sheetmask() && !mask().is_running())
  {
    xvt_statbar_set(_msg);
    beep();
  }  
  else
  {
    set_focus();
    ::warning_box("%s", _msg);
    set_focus();
  }
  return FALSE;
}

bool TMask_field::yesno_box(const char* fmt, ...) const
{                         
  set_focus();
  build_msg();
  const bool yes = ::yesno_box("%s", _msg);
  set_focus();
  return yes;
}

KEY TMask_field::yesnocancel_box(const char* fmt, ...) const
{                         
  set_focus();
  build_msg();
  const KEY k = ::yesnocancel_box("%s", _msg);
  set_focus();
  return k;
}

///////////////////////////////////////////////////////////
// TList_sheet
///////////////////////////////////////////////////////////

// Certified 100%
TList_sheet::TList_sheet(TEdit_field* f, const char* caption, const char* head)
: _fld(f), _row(-1)
{
  _sheet = new TArray_sheet(-1, -1, 0, 0, caption, head); 
}

// Certified 100%
TList_sheet::~TList_sheet()
{
  delete _sheet;
}

// Certified 100%
TMask_field& TList_sheet::field(short id) const
{ return field().mask().field(id); }


// Certified 100%
void TList_sheet::read_item(TScanner& scanner)
{
  TToken_string ts(scanner.string());
  _sheet->add(ts);
}


// Certified 100%
void TList_sheet::parse_input(TScanner& scanner)
{
  _inp_id.add(scanner.pop());
}


// Certified 100%
void TList_sheet::parse_output(TScanner& scanner)
{
  _out_id.add(scanner.pop());
}

// il numero di riga selezionata
int TList_sheet::do_input()
{
  if (_inp_id.empty()) return -2;          // List empty!

  _inp_id.restart();
  TToken_string rowsel(80);

  for (const char* fld = _inp_id.get(); fld; fld = _inp_id.get())
  {
    if (*fld == '"')
    {
      rowsel.add(fld+1);
      if (rowsel.not_empty()) rowsel.cut(rowsel.len()-1);
    }
    else
    {
      const short id  =  _fld->atodlg(fld);
      if (id > 0) rowsel.add(field().mask().get(id));
      else rowsel.add("");
    }
  }

  TString80 fd, it;
  for (int i = 0 ; i < _sheet->items(); i++)
  {
    TToken_string& ts =_sheet->row(i);

    ts.restart();
    for ( const char* item = rowsel.get(0); item ; item = rowsel.get())
    {
      it = item; it.trim();
      fd = ts.get(); fd.trim();
      if (fd != it) break;
    }
    if (!item) return i;
  }

  return -1;                 // Value not found!
}


// Certified 50%
void TList_sheet::do_output(CheckTime t)
{
  if (_row < 0 || t == FINAL_CHECK) 
    return;
  
  _out_id.restart();
  TToken_string& rowsel = _sheet->row(_row);
  rowsel.restart();
  for (const char* fld = _out_id.get(); fld; fld = _out_id.get())
  {
    const short id  = _fld->atodlg(fld);
    if (t != STARTING_CHECK || field().field() == NULL)
    {
      TMask_field& f = field(id);
      f.set(rowsel.get());
      if (field().dlg() != id)
        f.on_hit();
    }
  }
}


// Certified 100%
KEY TList_sheet::run()
{
  _row = do_input();

  _sheet->select(_row);

  const KEY k = _sheet->run();

  switch (k)
  {
  case K_ENTER:
    _row = (int)_sheet->selected();
    do_output();
    break;
  default:
    break;
  }

  return k;
}


// Certified 100%
bool TList_sheet::check(CheckTime t)
{
  const bool passed = (_row = do_input()) != -1;
  if (passed) do_output(t);
  return passed;
}


///////////////////////////////////////////////////////////
// TBrowse
///////////////////////////////////////////////////////////

// Certified 100%
TBrowse::TBrowse(TEdit_field* f, TRelation* r, int key, const char* filter)
: _relation(r), _cursor(new TCursor (r, filter, key)),
  _fld(f), _filter(filter), _secondary(FALSE), _checked(FALSE)
{}


// Certified 100%
TBrowse::TBrowse(TEdit_field* f, TCursor* c)
: _relation(NULL), _cursor(c), _fld(f), _secondary(FALSE), _checked(FALSE)
{}


// Certified 100%
TBrowse::~TBrowse()
{
  if (_relation)
  {
    delete _relation;
    delete _cursor;
  }
}


// Certified 100%
void TBrowse::parse_display(TScanner& scanner)
{              
  const char* s;
  s = scanner.string();
  _head.add(s);
  s = scanner.line();
  _items.add(s);
}


void TBrowse::parse_input(TScanner& scanner)
{
  const char* s = scanner.pop();
  _inp_fn.add(s);

  s = scanner.pop();
  if (*s == '"')        // Constant string
  {
    scanner.push();
    _inp_id.add(scanner.line());
  }
  else            // Field on the mask
  {
    TString80 str(s);
    if (scanner.popkey() == "SE") str << '@';       // Special FILTERing field
    else scanner.push();
    _inp_id.add(str);
  }
}


void TBrowse::parse_output(TScanner& scanner)
{
  const char* s = scanner.pop();
#ifdef DBG      
  field().atodlg(s);
#endif  
  _out_id.add(s);
  s = scanner.pop();
  _out_fn.add(s);  
}


bool TBrowse::parse_copy(const TString& what, const TBrowse& b)
{
  const bool all = what == "AL";
  if (all || what == "US")
  {
    set_insert(b.get_insert());
    _filter = b.get_filter();
    if (field()._warning.empty()) 
      field()._warning = b.field()._warning;
    if (!all) return TRUE;
  }
  if (all || what == "IN")
  {
    _inp_id = b._inp_id;
    _inp_fn = b._inp_fn;
    if (!all) return TRUE;
  }
  if (all || what == "DI")
  {
    _head = b._head;
    _items = b._items;
    if (!all) return TRUE;
  }
  if (all || what == "OU")
  {
    _out_id = b._out_id;
    _out_fn = b._out_fn;
    _secondary = TRUE;
  }
  return TRUE;
}


void TBrowse::parse_join(TScanner& scanner)
{
  TString80 j(scanner.pop());             // File or table

  CHECKS(_relation, "Can't join to NULL relation ", (const char*)j);

  int to;
  if (scanner.popkey() == "TO")         // TO keyword
  {
    const char* t = scanner.pop();
    to = name2log(t);
  }
  else
  {
    to = 0;                  // _relation->lfile()->num();
    scanner.push();
  }

  int key = 1;
  if (scanner.popkey() == "KE")
    key = scanner.integer();
  else scanner.push();

  byte alias = 0;
  if (scanner.popkey() == "AL")
    alias = scanner.integer();
  else scanner.push();

  TToken_string exp(80);
  if (scanner.pop() == "INTO")
  {
    const char* r = scanner.pop();
    while (strchr(r, '=') != NULL)
    {
      exp.add(r);
      r = scanner.pop();
    }
  }
  scanner.push();

#ifdef DBG      
  if (exp.empty()) yesnofatal_box("JOIN senza espressioni INTO");
#endif  

  if (isdigit(j[0]))
    _relation->add(atoi(j), exp, key, to, alias);   // join file
  else
  {
#ifdef DBG      
    if (j.len() > 4)
      yesnofatal_box("'%s' non e' una tabella valida: %d", (const char*)j);
    else  
#endif          
      _relation->add(j, exp, key, to, alias);      // join table
  }
}


void TBrowse::parse_insert(TScanner& scanner)
{
  const TString16 key(scanner.popkey());

  if (key == "NO") _insert = "";
  else
  {
    if (key == "RU")
    {
      _insert = "R";
      _insert << scanner.line();
    }
    else
    {
      _insert = "M";
      _insert << scanner.line();
    }
    _insert.trim();
  }
}


// Certified 100%
TMask_field& TBrowse::field(short n) const
{ return _fld->mask().field(n); }


// Ritorna il numero di inputs senza contare quelli che funzionano solo da filtro
int TBrowse::inputs()
{
  int inp = 0;
  for (const char* fld = _inp_id.get(0); fld; fld = _inp_id.get())
  {
    if (*fld != '"' && strchr(fld, '@') == NULL)
      inp++;
  }
  return inp;    
}

// Ritorna il numero di campi non vuoti e non filtrati
int TBrowse::do_input(bool filter)
{
  int ne = 0;
  if (_inp_id.empty()) return ne;

  TRectype& cur = _cursor->curr();

  cur.zero();
  TRectype filtrec(cur);

  _inp_id.restart();
  _inp_fn.restart();

  TString80 val;                  // Value to output
  bool tofilter;

  for (const char* fld = _inp_id.get(); fld; fld = _inp_id.get())
  {
    if (*fld == '"')
    {
      val = (fld+1);
      if (val.not_empty()) val.rtrim(1);
      tofilter = filter;
    }
    else
    {
      const short id  = _fld->atodlg(fld);
      const bool filter_flag = strchr(fld, '@') != NULL;
      tofilter = filter && filter_flag;
      val = field(id).get();
      if (field(id).is_edit() && val.not_empty() && !filter_flag) 
        ne++;          // Increment not empty fields count
    }

    TFieldref fldref(_inp_fn.get(), 0); // Output field
    fldref.write(val, _cursor->relation());
    if (tofilter)
    {
      if (val.empty()) val.fill('~', fldref.len(cur));
      fldref.write(val, filtrec);
    }
  }

  if (!filter) return ne;

  TString work(_filter.size());
  for (int i = 0; _filter[i]; i++)
  {
    if (_filter[i] == '"')
    { 
      do
      {
        work << _filter[i++];
      } while (_filter[i] && _filter[i] != '"');
      work << '"';
      if (!_filter[i]) break;
    }
    else
      if (_filter[i] == '#')
      {
        work << '"' << field(atoi(&_filter[++i])).get() << '"';
        while (isspace(_filter[i])) i++;
        while (isdigit(_filter[i])) i++;
        i--;
      }
      else work << _filter[i];
  }

  _cursor->setfilter(work);
  _cursor->setregion(filtrec, filtrec);

  return ne;
}


void TBrowse::do_output(CheckTime t)
{   
  if (t == FINAL_CHECK) 
    return;

  TString80 sum;
  TToken_string flds(24, '+');

  _out_fn.restart();
  for (const char* fld = _out_id.get(0); fld && *fld; fld = _out_id.get())
  {
    const short id = field().atodlg(fld);
    TMask_field& f = field(id);

    flds = _out_fn.get();
    if (t != STARTING_CHECK || f.field() == NULL || f.mask().mode() == MODE_INS)
    {
      sum.cut(0);
      for(const char* fr = flds.get(0); fr; fr = flds.get())
      {  
        const char* val;         
        if (*fr == '"')
        { 
          const TString80 v(fr);
          val = v.mid(1, v.len()-2);
        }
        else
        {
          const TFieldref fld(fr, 0);
          val = fld.read(_cursor->relation());
        }  
        sum << val;
      }

      f.set(sum);
      if (field().dlg() != id)
        f.on_hit();
    }
  }
}


void TBrowse::do_clear()
{                  
  for (TString16 fld = _out_id.get(0); fld.not_empty(); fld = _out_id.get())
  {
    TMask_field& f = field(atoi(fld));
    if (f.field() == NULL && _inp_id.get_pos(fld) < 0) 
      f.reset();
  }
}



bool TBrowse::do_insert()
{
  bool ok = FALSE;
  TString80 app;

  if (_insert[0] == 'M')
  {
    TString80 nm(_insert.mid(1));
    if (strncmp(nm, "BATB", 4) == 0)    // Programma gestione tabelle
      app = format("ba3 -0 %s", (const char*)nm.mid(4));
    // Obbligatorio usare la format globale
    else                              // Programma generico di browse/edit
      app = format("ba3 -3 %s %d", (const char*)nm, _cursor->file().num());
    // Obbligatorio usare la format globale
  }
  else
  {
    app = _insert.mid(1);
  }

#if XVT_OS == XVT_OS_WIN
  if (strnicmp(app, main_app().name(), 3) == 0)
    app.insert("a", 3);
#endif

  TMailbox mail;
  TMessage msg(cmd2name(app), MSG_AI, "");
  mail.send(msg);

  TExternal_app a(app);
  a.run();

  if (mail.check())
  {
    TMessage* rcv = mail.next_s(MSG_AI);
    if (rcv != NULL) _rec = atoi(rcv->body());
    if (_rec > 0)
    {
      _cursor->file().readat(_rec);
      ok = _cursor->ok();
      if (ok) do_output();
#ifdef DBG      
      else error_box("Selezione da programma esterno errata");
#endif      
    }
  }
  return ok;
}


TToken_string& TBrowse::create_siblings(TToken_string& siblings)
{
  const TMask& mask = field().mask();
  siblings = "";                      // Azzera la lista dei campi associati
  
  //  if (!mask.is_running()) return siblings;
  
  TBit_array key(4);                  // Elenco delle chiavi gia' utilizzate
  key.set(_cursor->key());
  
  // Scorre la lista dei campi di output
  int n = 0;  
  for (const char* i = _out_id.get(0); i; i = _out_id.get(), n++)
  {
    const short id = _fld->atodlg(i);
    const TMask_field& f = mask.field(id);
    if (!f.showed() || !f.is_edit())  // Scarta i campi non editabili
      continue;
    const TEdit_field& e = (const TEdit_field&)f;
    const TBrowse* b = e.browse();
    if (b == NULL) 
      continue;                       // Scarta i campi senza ricerca
    
    const TCursor* c = b->cursor();  
    
    // Considera ricerche sullo stesso file ma con chiave diversa
    if (c->file().num() == _cursor->file().num() && 
        (key[c->key()] == FALSE || id == _fld->dlg()))      
    {
      TString16 fn(_out_fn.get(n));           // Legge nome del campo su file          
      int pos = _items.get_pos(fn);           // Determina header corrispondente
      if (pos < 0)                            // Se non lo trova identico ...
      {
        const int q = fn.find('[');
        if (q > 0)
        {
          fn.cut(q);
          pos = _items.get_pos(fn);           // ... ritenta senza parentesi
        }
      }
      if (pos >= 0)
      {
        siblings.add(id);
        const char* h = _head.get(pos); 
        siblings.add(h);
        const int et = siblings.find('@');
        if (et > 0) siblings.cut(et);
        key.set(c->key());                    // Marca la chiave come usata
      }  
    }
  }
  
  return siblings;
}


KEY TBrowse::run()
{
  do_input(TRUE);
  _cursor->read(_isgteq);

  TString80 caption;
  const TLocalisamfile& f = _cursor->file();
  if (f.tab())
  {
    TFilename name("batb"); 
    name << _cursor->file().name() << ".msk";
    if (fexist(name.lower()))
    {
      TScanner m(name);
      while (m.line().left(2) != "PA");      // Find PAGE
      const int apicia = m.token().find('"')+1;
      const int apicic = m.token().find('"', apicia);
      caption = m.token().sub(apicia, apicic);
    }
    else 
    {
      caption = name.mid(4);
      caption.cut(caption.find('.'));
      caption.insert("Tabella ", 0);
    }  
  }
  else
    caption = f.description();
  if (!isalnum(caption[0])) 
    caption = "Selezione";
  
  KEY k = K_ESC;
  long selected = 0;

  TToken_string siblings; 
  create_siblings(siblings);                            
  
{
  TToken_string* sib = siblings.empty() ? NULL : &siblings;
  byte buttons = 0;
  if (_insert.not_empty())
  {   
    TFilename path;
    if (_insert[0] == 'M')
      path = "ba3 -0";
    else
      path = _insert.mid(1);

    if (main_app().name() != cmd2name(path))           // Se non lancerei me stesso
      buttons = 1;         
  }
  
  TBrowse_sheet s(_cursor, _items, caption, _head, buttons, _fld, sib);
  k = s.run();
  selected = s.selected();
}

switch (k)
{
 case K_INS:
  k = do_insert() ? K_ENTER : K_ESC;
  break;
 case K_ENTER:
  *_cursor = selected;
  do_output();
  break;
default:
  if (k >= K_CTRL)
  {
    const short id = siblings.get_int((k - K_CTRL) << 1);
    TEdit_field& ef = (TEdit_field&)_fld->mask().field(id);
    ef.set_focus();
    ef.send_key(k = K_F9, 0);
  }
  break;
}

return k;
}

bool TBrowse::check(CheckTime t)
{
  bool passed = TRUE;

  if (_secondary == TRUE && t != RUNNING_CHECK)
    return TRUE;

  //    if (_checked && t == FINAL_CHECK)
  //      return TRUE;
  //    _checked = TRUE;

  if (_fld->check_type() != CHECK_NONE)
  {
    const TMaskmode mode = (TMaskmode)field().mask().mode();

    CheckType chk = _fld->check_type();
    const int ne = do_input(TRUE);
    if (t == STARTING_CHECK || mode == MODE_QUERY) chk = CHECK_NORMAL;
    if (ne || chk == CHECK_REQUIRED)
    {
      _cursor->setkey();
      _cursor->file().read(_isequal);
      passed = _cursor->ok();

      if (t != FINAL_CHECK)
      {
        if (passed)
        {
          _cursor->repos();
          do_output(t);
        }
        else 
        {
          do_clear();
          _fld->set_dirty(3);
        }  
      }
    }
    else
      if (t != FINAL_CHECK) do_clear();
  }
  //    _checked = passed;
  return passed;
}

bool TBrowse::empty_check()
{
  const TMaskmode mode = (TMaskmode)field().mask().mode();
  const bool no_check = mode == MODE_SEARCH || field().mask().query_mode();

  if ( no_check || _fld->check_type() == CHECK_NONE ||
      _fld->check_type() == CHECK_NORMAL)
    return TRUE;
  else
    return do_input() > 0;
}


///////////////////////////////////////////////////////////
// TEdit_field
///////////////////////////////////////////////////////////

TEdit_field::TEdit_field(TMask* mask)
: TMask_field(mask), _browse(NULL), _sheet(NULL),
  _buttonwin(NULL_WIN), _check(CHECK_NONE), _check_enabled(TRUE),
  _forced(FALSE)
{}

TEdit_field::~TEdit_field()
{
  if (_browse) delete _browse; else
    if (_sheet) delete _sheet;
}

word TEdit_field::class_id() const
{ return CLASS_EDIT_FIELD; }


void TEdit_field::enable(bool on)
{
  TMask_field::enable(on);
  if (_buttonwin != NULL_WIN)
    show_window(_buttonwin, on && check_enabled() && showed());
}

void TEdit_field::show(bool on)
{
  TMask_field::show(on);
  if (_buttonwin != NULL_WIN)
    show_window(_buttonwin, on && check_enabled() && enabled());
}


void TEdit_field::parse_head(TScanner& scanner)
{
  _size  = scanner.integer();
#ifdef DBG  
  if (_size < 1)
  {
    _size = 8;
    yesnofatal_box("Il campo %d ha dimensione nulla (uso %d)", dlg(), _size);
  }
#endif
  _width = scanner.integer();
  if (_width == 0) _width = _size;
}


const TBrowse* TEdit_field::get_browse(TScanner& scanner) const
{
  const int id = scanner.integer();
  const TEdit_field& f = mask().efield(id);
  const TBrowse* b = (const TBrowse*)f.browse();
#ifdef DBG    
  if (b == NULL)
    yesnofatal_box("La USE del campo %d e' nulla e non puo' essere copiata nel campo %d", id, dlg());
#endif    
  return b;
}


bool TEdit_field::parse_item(TScanner& scanner)
{
  if (scanner.key() == "PI")            // PICTURE
  {
    _picture = scanner.string();
#ifdef DBG    
    if (_picture == "." && _size > 9 && _size != 15) 
      ::warning_box("Guy propone una dimensione di 15 per il campo %d: %s\nMa probabilmente ha toppato ...", 
                    dlg(), (const char*)_prompt);
#endif    
    return TRUE;
  }

  if (scanner.key() == "CH")
  {
    scanner.pop();
    if (scanner.key() == "NO") _check = CHECK_NORMAL;
    else if (scanner.key() == "RE") _check = CHECK_REQUIRED;
    else if (scanner.key() == "FO") {_check = CHECK_REQUIRED; _forced = TRUE;}
    else _check = CHECK_NONE;
    return TRUE;
  }

  if (scanner.key() == "US")            // USE
  {
#ifdef DBG  
    if (_browse != NULL)
      return error_box("USE duplicata nel campo %d", dlg());
#endif
    
    int key = 1;
    TRelation* r;

    const int logicnum = scanner.integer();
    TString16 tabmaskname;

    if (logicnum > 0)
      r = new TRelation(logicnum);
    else
    {
      tabmaskname  = scanner.pop();
#ifdef DBG  
      if (tabmaskname.len() > 4)
        return error_box("'%s' non e' una tabella valida: %d",
                         (const char*)tabmaskname, dlg());
#endif                    
      r = new TRelation(tabmaskname);
    }

    if (scanner.popkey() == "KE")
    {
      key = scanner.integer();
#ifdef DBG  
      if (key < 1)
      {
        yesnofatal_box("Chiave %d non valida nella USE del campo %d", key, dlg());
        key = 1;
      }
#endif      
    }
    else scanner.push();

    const char* filter = "";
    if (scanner.popkey() == "SE")
      filter = (const char*)scanner.line();
    else
      scanner.push();

    _browse = new TBrowse(this, r, key, filter);

    if (tabmaskname.not_empty())
    {
      if (strncmp(main_app().name(), "ba3", 3) != 0)
      {
        tabmaskname.insert("MBATB", 0);
        _browse->set_insert(tabmaskname);
      }
    }

    return TRUE;
  }

  if (scanner.key() == "CO")                    // Copyuse
  {
    const TString16 what(scanner.popkey());

    const TBrowse* b = get_browse(scanner);
    if (b == NULL) return FALSE;

    if (what == "US" || what == "AL")
      _browse = new TBrowse(this, b->cursor());

    if (_browse)
      return _browse->parse_copy(what, *b);

#ifdef DBG
    return yesnofatal_box("Impossibile COPY senza USE nel campo %d", dlg());
#endif
  }

  if (scanner.key() == "JO")
  {
#ifdef DBG      
    if(!_browse) return yesnofatal_box("JOIN senza USE nel campo %d", dlg());
#endif
    _browse->parse_join(scanner);
    return TRUE;
  }

  if (scanner.key() == "SH")            // SHEET
  {
#ifdef DBG    
    if (_sheet) return error_box("SHEET duplicato nel campo %d", dlg());
#endif
    _sheet = new TList_sheet(this, _prompt, scanner.string());
    return TRUE;
  }

  if (scanner.key() == "IT")            // ITEM
  {
#ifdef DBG    
    if (_sheet == NULL) return error_box("ITEM senza SHEET nel campo %d", dlg());
#endif    
    _sheet->read_item(scanner);
    return TRUE;
  }

  if (scanner.key() == "IN")
  {
    if (_browse) _browse->parse_input(scanner); else
      if (_sheet) _sheet->parse_input(scanner);
#ifdef DBG    
      else error_box("INPUT senza USE o SHEET nel campo %d", dlg());
#endif      
    return TRUE;
  }

  if (scanner.key() == "DI")
  {
#ifdef DBG    
    if(!_browse) return error_box("DISPLAY senza USE nel campo %d", dlg());
#endif
    _browse->parse_display(scanner);
    return TRUE;
  }

  if (scanner.key() == "OU")
  {
    if (_browse) _browse->parse_output(scanner);
    else if (_sheet)  _sheet->parse_output(scanner);
#ifdef DBG    
    else return error_box("OUTPUT senza USE nel campo %d", dlg());
#endif    
    return TRUE;
  }

  if (scanner.key() == "AD")
  {
#ifdef DBG    
    if(!_browse) return error_box("ADD senza USE nel campo %d", dlg());
#endif    
    _browse->parse_insert(scanner);
    return TRUE;
  }

  if (scanner.key() == "VA")
  {                    
    const char* n = scanner.pop();
    _validate_func = isdigit(*n) ? atoi(n) : -1;
#ifdef DBG    
    if (_validate_func < 0)
      return yesnofatal_box("Funzione di validazione '%s' errata nel campo %d", n, dlg());
#endif

    const int _nparms = scanner.integer();
#ifdef DBG    
    if (_nparms < 0)
      return yesnofatal_box("Numero di parametri VALIDATE errato nel campo %d", dlg());
#endif

    for(int i = 0; i < _nparms; i++)
      _validate_parms.add(scanner.operand());

    return TRUE;
  }

  if (scanner.key() == "WA")
  {
    _warning = scanner.string();
    return TRUE;
  }
  if (scanner.key() == "ME")
  {                
    TFixed_string l(scanner.line().strip_spaces());
    int m = 0;
    if (l[0] == '0')
    {            
      l.ltrim(1);
      l.ltrim();    
      m = 1;
    }                      
    if (_message.objptr(m) == 0)
      _message.add(new TToken_string(64), m);
    TToken_string& ts = (TToken_string&)_message[m];
    ts.add(l);
    return TRUE;
  }

  return TMask_field::parse_item(scanner);
}


void TEdit_field::create(WINDOW parent)
{
  const int len = create_prompt(parent);

  long bandiere = _flags.rightjust ? CTL_FLAG_RIGHT_JUST : CTL_FLAG_LEFT_JUST;
  if (_flags.uppercase) bandiere |= CTL_FLAG_MAC_GENEVA9;
  if (_flags.password) bandiere |= CTL_FLAG_MAC_MONACO9;
  
  _x += len;

#if XVTWS == WMWS
  const int delta = 2;
#else
  const int delta = 1;
#endif

  wincreate(WC_EDIT, _width+delta, 1, _str, parent, bandiere);

#if XVT_OS == XVT_OS_WIN
  HWND hwnd = (HWND)get_value(win(), ATTR_NATIVE_WINDOW);
  SendMessage(hwnd, EM_LIMITTEXT, _size, 0L);    // Limita il testo
#endif

  if (_browse || _sheet)
  {
    long flags = default_flags();
    if (flags & CTL_FLAG_DISABLED)
    {
      flags &= ~CTL_FLAG_DISABLED;
      flags |= CTL_FLAG_INVISIBLE;
    }
    _buttonwin = xvt_create_control(WC_PUSHBUTTON, _x+_width+delta, _y,
                                    2, 1,       "*", parent, flags, PTR_LONG(this), DLG_F9);
  }
}

void TEdit_field::destroy()
{
  if (_buttonwin)
  { close_window(_buttonwin); _buttonwin = NULL_WIN; }
  TMask_field::destroy();
}


void TEdit_field::set_window_data(const char* data)
{
  TMask_field::set_window_data(format(data));
}


void TEdit_field::set_field_data(const char* data)
{ _str = data; }

const char* TEdit_field::get_field_data() const
{ return _str; }


const char* TEdit_field::format(const char* d)
{
  fpark = d;
  fpark.trim();

  if (fpark.not_empty())
  {              
    if (fpark.len() > _size)
    {
#ifdef DBG      
      yesnofatal_box("Campo %d troppo lungo: %d > %d", dlg(), fpark.len(), _size);
#endif      
      fpark.cut(_size);
    }

    if (_flags.uppercase)
      fpark.upper();

    if (_flags.zerofilled)
      fpark.right_just(_size, '0');
    else  
      if (_flags.rightjust)
        fpark.right_just(_size);
  }

  return fpark;
}


const char* TEdit_field::picture_data(const char* data, bool video)
{
  if (video) 
  { 
    data = format(data);
    set_title(win(), (char*)data); 
    return get_window_data(); 
  }
  
  set_window_data(data);
  TMask_field::get_window_data();
  fpark.trim();
  return fpark; 
}


bool TEdit_field::validate(KEY k)
{
  return ::validate(_validate_func, *this, k, _validate_parms);
}

// Certified 90%
bool TEdit_field::on_hit()
{
  if (_handler)
  {
    bool ok = _handler(*this, is_edit() ? K_TAB : K_SPACE);
    if (!ok) return FALSE;
  }
  if (_message.objptr(1) && get() == "") do_message(1);
  else do_message(0);
  return TRUE;
}

bool TEdit_field::on_key(KEY key)
{
  switch(key)
  {
  case K_TAB:
    if (_validate_func == AUTOEXIT_FUNC || 
        _validate_func == NUMCALC_FUNC ||
        _validate_func == STRCALC_FUNC)
      set_focusdirty();            // Forza validate
    if (to_check(K_TAB, TRUE))
    {
      set(get());
      bool ok = validate(key);     // Check validation expression

      if (!ok)
      {
        if (_warning.not_empty()) error_box(_warning);
        return FALSE;
      }

      TMask& m = mask();
      const bool query = m.query_mode();

      if (_sheet) ok = query || _sheet->check();  // Check consistency
      else
        if (check_enabled() && _browse && (!query || forced()))
          ok = _browse->check();
      
      if (!ok)
      {
        if (_warning.not_empty()) error_box(_warning);
        else 
#ifdef DBG
          error_box("Valore del campo %d non valido: %s", dlg(), (const char*)get());
#else
        error_box("Valore non valido: %s", (const char*)get());
#endif        
        return FALSE;
      }
      
      ok = on_hit();
      if (!ok)
        return FALSE;

      if (query && required() && in_key(0))
      {
        const byte keys = m.num_keys();

        for (int i = 1; i <= keys; i++)
          if (in_key(i) && m.key_valid(i))
          {
            for (int fld = m.get_key_field(i, TRUE); fld != -1; fld = m.get_key_field(i, FALSE))
              m.field(fld).set_dirty(FALSE);
            //            dispatch_e_char(get_parent(win()), K_AUTO_ENTER);
            mask().stop_run(K_AUTO_ENTER);
            break;
          }
      }
      return TRUE;
    }
    break;
  case K_ENTER:
    if (field() != NULL || mask().mode() == NO_MODE)
    {
      if (focusdirty()) set(get());

      bool ok = validate(K_ENTER);                           // Check validation expression
      if (!ok)
      {
        if (_warning.not_empty()) error_box(_warning);
        return FALSE;
      }
      
      const bool query = mask().query_mode();

      // check consistency
      if (_sheet) ok = query || _sheet->check(FINAL_CHECK);
      else if (_browse && check_enabled())
      {
        if (!query || forced())
        {
          if (dirty()) ok = _browse->check(FINAL_CHECK);     // Check consistency
          else ok = _browse->empty_check();
        }
      }
      else 
        ok = query || !(check_type() == CHECK_REQUIRED && get().empty());

      if (!ok)
      {
        if (_warning.not_empty()) error_box(_warning);
        else error_box("Valore del campo %d non valido: '%s'", dlg(), (const char*)get());
        return FALSE;
      }
    }
    break;
  case K_F9:
    if (check_enabled())
    {
      if (dirty()) set(get());
      KEY k = K_ESC;
      if (_browse) k = _browse->run();
      else if (_sheet) k = _sheet->run();
      else beep();
      if (mask().is_running() && k != K_F9) set_focus();
      if (k == K_ENTER)
      {
        set_dirty();
        if (mask().is_running())   
          send_key(K_TAB, 0);
        else
          on_hit();
        return TRUE;
      } 
      else 
        return FALSE;
    }
    break;
  default:
    break;
  }

  return TMask_field::on_key(key);
}


bool TEdit_field::has_check() const
{
  if (_browse) return check_type() != CHECK_NONE;
  return _sheet != NULL;
}

bool TEdit_field::check(CheckTime t)
{
  if (check_enabled() || (t == STARTING_CHECK && showed()))
  {
    if (_browse) return _browse->check(t); else
      if (_sheet) return _sheet->check(t);
  }
  return TRUE;
}

void TEdit_field::enable_check(bool on)
{
  _check_enabled = on;
  if (_buttonwin != NULL_WIN)
    show_window(_buttonwin, on);
}


///////////////////////////////////////////////////////////
// Boolean_field
///////////////////////////////////////////////////////////


TBoolean_field::TBoolean_field(TMask* m)
: TMask_field(m), _on(FALSE)
{}


word TBoolean_field::class_id() const
{ return CLASS_BOOLEAN_FIELD; }


void TBoolean_field::create(WINDOW parent)
{
  wincreate(WC_CHECKBOX, strlen(_prompt)+4, 1, _prompt, parent, 0);
}


const char* TBoolean_field::get_window_data() const
{
  CHECK(win(), "Control window not initialized");
  fpark[0] = xvt_get_checked_state(win()) ? 'X' : ' ';
  fpark[1] = '\0';
  return fpark;
}


void TBoolean_field::set_window_data(const char* data)
{
  CHECK(win(), "Control window not initialized");

  if (data == NULL) data = "";
  const bool b = toupper(*data) == 'X';
  xvt_check_box(win(), b);
}


void TBoolean_field::set_field_data(const char* data)
{
  if (data == NULL) data = "";
  _on = toupper(*data) == 'X';
}

const char* TBoolean_field::get_field_data() const
{
  return _on ? "X" : " ";
}


bool TBoolean_field::parse_item(TScanner& scanner)
{
  if (scanner.key() == "ME")
  {
    const bool tf = scanner.integer() ? TRUE : FALSE;   // Message TRUE or FALSE

    if (_message.items() == 0)
    {
      _message.add(new TToken_string(16));
      _message.add(new TToken_string(16));
    }
    TToken_string& ts = (TToken_string&)_message[tf];
    ts.add(scanner.line().strip_spaces());

    return TRUE;
  }

  return TMask_field::parse_item(scanner);
}

void TBoolean_field::enable(bool on)
{
  _flags.enabled = on;
  xvt_enable_control(_win, on);
}


bool TBoolean_field::on_hit()
{                          
  if (_handler)
  {
    bool ok = _handler(*this, K_SPACE);
    if (!ok) return FALSE;
  }
  const int n = mask().is_running() ? xvt_get_checked_state(win()) : _on;
  do_message(n);
  return TRUE;
}

bool TBoolean_field::on_key(KEY key)
{
  if (key == K_SPACE) 
  {  
    on_hit();
    set_dirty();
    return TRUE;
  }  
  return TMask_field::on_key(key);
}

///////////////////////////////////////////////////////////
// Button_field
///////////////////////////////////////////////////////////

TButton_field::TButton_field(TMask* m)
: TMask_field(m)
{
  _flags.persistent = TRUE;
}


word TButton_field::class_id() const
{ return CLASS_BUTTON_FIELD; }

void TButton_field::parse_head(TScanner& scanner)
{
  _width = scanner.integer();
  if (_width < 1) _width = 9;
  _size  = scanner.integer(); // Height
  if (_size < 1) _size = 1;
}


bool TButton_field::parse_item(TScanner& scanner)
{
  return TMask_field::parse_item(scanner);
}


void TButton_field::create(WINDOW parent)
{
  long flags = CTL_FLAG_CENTER_JUST;

  switch (dlg())
  {
  case DLG_OK:
    if (_prompt.empty())
      _prompt = "Conferma";   
    _virtual_key = _exit_key = K_ENTER;
    flags |= CTL_FLAG_DEFAULT;
    break;
  case DLG_CANCEL:
    if (_prompt.empty())
      _prompt = "Annulla";
    _virtual_key = _exit_key = K_ESC;
    break;
  case DLG_DELREC:  
    _virtual_key = 'E';
    _exit_key = K_DEL;
    break;
  case DLG_PRINT:
    if (_prompt.empty())
      _prompt = "Stampa"; 
    _virtual_key = 'S';
    _exit_key = K_ENTER;
    break;
  case DLG_QUIT:
    if (_prompt.empty())
      _prompt = "Fine"; 
    _virtual_key = K_F4;
    _exit_key = K_QUIT;
    break;
  default:
  { 
    _exit_key = 0;
    TToken_string* message = (TToken_string*)_message.objptr(0);
    if (message != NULL)
    {
      TToken_string msg(message->get(0), ','); 
      const TFixed_string m(msg.get(0));
      if (m == "EXIT") 
        _exit_key = msg.get_int();
      else 
        if (msg.get_int() == 0) _exit_key = atoi(m);
    }
    const int n = _prompt.find('~');
    _virtual_key = (n >= 0) ? toupper(_prompt[n+1]) : _exit_key;
  }  
    break;
  }


#if XWTWS == WMWS
  _prompt.center_just(_width);
#endif          
  
  wincreate(WC_PUSHBUTTON, _width + 2, _size, _prompt, parent, flags);
}

void TButton_field::enable(bool on)
{
  _flags.enabled = on;
  xvt_enable_control(_win, on);
}

void TButton_field::show(bool on)
{
  TMask_field::show(on);
}


bool TButton_field::on_key(KEY key)

{
  if (key == K_SPACE)   
  {
    on_hit();
    return TRUE;
  }  
  return TMask_field::on_key(key);
}

///////////////////////////////////////////////////////////
// Date_field
///////////////////////////////////////////////////////////

word TDate_field::class_id() const
{ return CLASS_DATE_FIELD; }

void TDate_field::create(WINDOW w)
{
  TEdit_field::create(w);
  if (automagic())
  {
    TDate d(TODAY);
    set(d.string());
  }
}

void TDate_field::parse_head(TScanner&) {}

TDate_field::TDate_field(TMask* m) : TEdit_field(m)
{
  _size = _width = 10;
}


bool TDate_field::on_key(KEY key)
{
  if (to_check(key))
  {
    TFixed_string data(get_window_data(), 15);
    data.trim();
    if (data.not_empty())
    {
      bool changed = FALSE;
      if (isdigit(data[0]))
      {
        if (data.len() == 6)      // Fix century (for this millenium only)
        {
          data.insert("19", 4);
          changed = TRUE;
        }
        for (int meno = 2; meno <= 5; meno += 3)
          if (data[meno] != '-')
          {
            data.insert("-", meno);
            changed = TRUE;
          }
        if (data.len() == 8)      // Fix century (for this millenium only)
        {
          data.insert("19", 6);
          changed = TRUE;
        }
      }
      else
      {
        TDate g(TODAY);
        data.upper();
        if (data == "IERI")
        {
          --g;
          changed = TRUE;
        }
        else
          if (data == "DOMANI")
          {
            ++g;
            changed = TRUE;
          }
        TString16 gstring(g.string());
        if (data == "PRIMO") 
        {
          gstring.overwrite("01-01", 0);
          changed = TRUE;
        }
        else
          if (data == "ULTIMO")
          {
            gstring.overwrite("31-12", 0);
            changed = TRUE;
          }
          else
            if (data == "OGGI") changed = TRUE;
        if (changed) data = gstring;
      }

      TDate d(data);
      if (!d.ok())
      {
        //                              error_box("La data deve essere nel formato gg-mm-aaaa");
        error_box("Data errata o formato non valido");
        return FALSE;
      }
      else
        if (changed)
          TMask_field::set_window_data(d.string());
    }
  }

  return TEdit_field::on_key(key);
}

const char * TDate_field::get_window_data() const
{
  const char * data = TEdit_field::get_window_data();
  if (roman())
  {
    const TDate d(data);
    data = d.string(ANSI);
  }
  return data;
}


void TDate_field::set_window_data(const char * data)
{
  if (roman())
  {
    const TDate d(data);
    data = d.string();
  }
  TEdit_field::set_window_data(data);
}

///////////////////////////////////////////////////////////
// Real_field
///////////////////////////////////////////////////////////

TReal_field::TReal_field(TMask* m) : TEdit_field(m)
{}

word TReal_field::class_id() const
{ return CLASS_REAL_FIELD; }

void TReal_field::create(WINDOW w)
{
  TEdit_field::create(w);

  if (_flags.firm)
    set(::format("%ld", main_app().get_firm())); else
      if (automagic())
      {
        TDate d(TODAY);
        set(::format("%d", d.year()));
      }
}

bool TReal_field::on_key(KEY key)
{
  if (to_check(key))
  {
    if (roman())
    {
      const int r = atoi(get_window_data());
      if (r < 0) return error_box("Numero romano errato");
    }
    else
    {
      const char* n = get();
      if (*n && !real::is_real(n))
        return error_box("Valore numerico non valido");
      if (_flags.uppercase && real(n).sign() < 0)
        return error_box("Il numero deve essere positivo");
    }
  }

  return TEdit_field::on_key(key);
}

void TReal_field::parse_head(TScanner& scanner)
{
  _size  = scanner.integer();

#ifdef DBG      
  if (_size < 1)
  {
    _size = 9;
    yesnofatal_box("Il campo %d ha dimensione nulla (uso %d)", dlg(), _size);
  }
#endif
  
  _width = _size;
  _decimals = scanner.integer();
}

void TReal_field::set_window_data(const char* data)
{
  if (data == NULL) data = "";

  if (roman())
  {
    data = itor(atoi(data));
    TMask_field::set_window_data(data);
  }
  else
  { 
    real n(data);
    if (!n.is_zero())
    {
      if (exchangeable() && decimals())
      {
        const real& e = mask().exchange();
        n /= e;
      }
      if (_picture.empty())
        data = n.stringa(_size, _decimals);
      else
        data = n.string(_picture);
    } 
    else data = "";
    TEdit_field::set_window_data(data);
  }
}

const char* TReal_field::get_window_data() const
{
  TEdit_field::get_window_data();
  if (roman())
  {
    int r = atoi(fpark);
    if (r == 0) r = rtoi(fpark);
    if (r > 0)
    {
      int s = decimals();
      if (s < 1) s = 4;
      if (_flags.zerofilled)
        fpark.format("%0*d", s, r);
      else
        fpark.format("%*d", s, r);
    }
    else fpark.cut(0);
  }
  else
  {
    fpark = real::ita2eng(fpark);
    if (exchangeable() && decimals())
    {      
      real n(fpark);
      const real& e = mask().exchange();
      n *= e;
      if (n.is_zero()) fpark.cut(0);
      else fpark = n.string();
    }
  }     

  return fpark;
}

void TReal_field::set_decimals(int d) 
{ 
  _decimals = d; 
  if (_picture[0] == '.')
  {
    if (d > 0) _picture.format(".%d", d);
    else _picture = ".";
  }
}


void TReal_field::exchange(bool show_value, const real& nuo)
{                                
  const int dec = show_value ? 2 : 0;            // Quanti decimali voglio
  const bool value = decimals() != 0;            // Sono attualmente in valuta ?
  
  if (show_value == value && show_value) return; // Se cambio da valuta a valuta esco subito! 
  
  if (mask().is_running())
  {
    const char* n = get_window_data();
    if (*n)
    {
      const real& vec = mask().exchange();
      real r(n); 
      if (!show_value) 
        r *= nuo;
      r /= vec;
      r.round(dec);
      set_decimals(dec);
      TEdit_field::set_window_data(r.string(_picture));
    }  
  }  
  
  if (decimals() != dec)
    set_decimals(dec);
}

///////////////////////////////////////////////////////////
// List_field
///////////////////////////////////////////////////////////

TList_field::TList_field(TMask* m) : TMask_field(m)
{}

word TList_field::class_id() const
{
  return CLASS_LIST_FIELD;
}


void TList_field::read_item(TScanner& scanner)
{
  TToken_string ts(scanner.string());
  _codes.add(ts.get());
  _values.add(ts.get());

  ts = "";
  while (scanner.popkey() == "ME")
    ts.add(scanner.line().strip_spaces());
  scanner.push();

  _message.add(ts);
}


void TList_field::parse_head(TScanner& scanner)
{
  _size = scanner.integer();
  _width = scanner.integer();
  if (_width < 1) _width = _size+3;
}


bool TList_field::parse_item(TScanner& scanner)
{
  if (scanner.key() == "IT")            // ITEM
  {
    read_item(scanner);
    return TRUE;
  }

  if (scanner.key() == "LI")            // LISTITEM
  {
    TScanner sc(scanner.pop());
    while (sc.popkey() == "IT")         // ITEM
      read_item(sc);
    return TRUE;
  }

  return TMask_field::parse_item(scanner);
}


int TList_field::items() const
{
  return _codes.items();
}

void TList_field::add_item(const char* s)
{
  TToken_string t(s);         
  const TString16 item(t.get());
  const int pos = _codes.get_pos(item);
  
  if (pos < 0 )
  {                                         
    _codes.add(item);
    win_list_add(win(), -1, (char*)t.get());
  }     
}


void TList_field::delete_item(const char* s)
{
  TString16 t(s);         
  const int pos = _codes.get_pos(t);
  
  if (pos >= 0 )
  {                                         
    _codes.destroy(pos);
    win_list_delete(win(), pos);
    if (mask().is_running())
    {
      win_list_set_sel(win(), 0, TRUE);
      if (showed())     on_hit();
    }
  }     
}


void TList_field::add_list()
{
  if (roman() && _codes.items() < 12)
  {
    TString csafe, vsafe;
    if (atoi(_codes) > 0)
    {
      csafe = _codes; _codes = "";
      vsafe = _values; _values = "";
    }
    _codes.add("01|02|03|04|05|06|07|08|09|10|11|12");
    _values.add("Gennaio|Febbraio|Marzo|Aprile|Maggio|Giugno");
    _values.add("Luglio|Agosto|Settembre|Ottobre|Novembre|Dicembre");
    if (atoi(csafe) > 0)
    {
      _codes.add(csafe);
      _values.add(vsafe);
      if (_message.objptr(0))
      {
        _message.add(_message[0], _codes.items()-1);
        _message.add(NULL, 0);
      }
    }
  }

  SLIST lst = slist_new();
  for (const char* item = _values.get(0); item; item = _values.get())
    slist_add(lst, (SLIST_ELT)NULL, (char*)item, 0L);
  win_list_add(win(), -1, (char*)lst);
  slist_dispose(lst);

  const char* init = "";
  if (roman() && automagic())
    init = format("%02d", TDate(TODAY).month());
  set_field_data(init);
}


void TList_field::replace_items(const char* codes, const char* values)
{
  _codes = codes;
  _values = values;

  if (win() != NULL_WIN)
  {
    win_list_clear(win());
    add_list();
    current(0);
  }
}


void TList_field::create(WINDOW parent)
{
  const int len = create_prompt(parent);
  _x += len;

  wincreate(WC_LISTBUTTON, _width, 5, "", parent,0);
  add_list();
}


int TList_field::str2curr(const char* data)
{
  TString16 str(data);

  if (roman() && str.len() < 2)
    str.insert("0",0);
  if (_flags.uppercase)
    str.upper();        

  int i = str.not_empty() ? _codes.get_pos(str) : 0;

  if (i < 0)      // Se non trova il codice ritenta dopo trim
  {
    for (i = 0; str[i] == '0' || str[i] == ' '; i++);
    if (i > 0)
    {
      str.ltrim(i);
      i = _codes.get_pos(str);
    }
  }

  if (i < 0)
  {
    if (items() && str.not_empty())
      yesnofatal_box("'%s' non e' un valore valido per il campo %s: %d",
                     data, prompt(), dlg());
    i = 0;
  }
  return i;
}


void TList_field::set_window_data(const char* data)
{
  CHECKD(win(), "Control window not initialized ", dlg());
  const int i = str2curr(data);
  current(i);
}


void TList_field::current(int n)
{
  win_list_set_sel(win(), n, TRUE);
}

int TList_field::current() const
{
  const int sel = win_list_get_sel_index(win());
#ifdef DBG
  if (sel < 0 && items() > 0)
    error_box("Lista senza selezione nel campo %d", dlg());
#endif
  return sel;
}

const char* TList_field::get_window_data() const
{                                    
  const int c = current();
  const char* v = ((TList_field*)this)->_codes.get(c);
  return v;
}

void TList_field::set_field_data(const char* data)
{
  _str = data;
  if (_str.not_empty()) 
  {
    const int i = _codes.get_pos(_str);
    if (i < 0) 
      _str = _codes.get(0);
  }
  else
    _str = _codes.get(0);    
}

bool TList_field::on_hit()
{
  if (_handler)
  {
    bool ok = _handler(*this, K_SPACE);
    if (!ok) return FALSE;
  }
  const int n = mask().is_running() ? current() : str2curr(_str);
  do_message(n);
  return TRUE;
}

const char* TList_field::get_field_data() const
{
  return _str;
}

bool TList_field::on_key(KEY key)
{
  if (key >= '0' && key <= 'z')
  {
    const int index = win_list_get_sel_index(win());
    CHECK(index >= 0, "List with no selection!");
    int newindex = -1;

#if XVTWS == WMWS
    if (key >= 'A' && key <= 'z')
    {
      for (int i = index+1; i != index; i++)
      {
        char item[16];
        bool flag;

        do
        {
          flag = win_list_get_elt(win(), i, item, 16);
          if (!flag)
          {
            CHECK(i, "La lista e' vuota!");
            i = -1;
            break;
          }
        } while (!flag);
        if (flag && toupper(*item) == toupper(key))
        {
          newindex = i;
          break;
        }
      } // for
    } // alphabetic
#endif

    if (key >= '0' && key <= '9')
    {
      newindex = (key == '0') ? 10 : key - '1';
      if (newindex > items())
      {
        newindex = -1;
        beep();
      }
    }

    if (newindex >= 0)
    {
      win_list_suspend(win());
      win_list_set_sel(win(), index, FALSE);
      win_list_set_sel(win(), newindex, TRUE);
      win_list_resume(win());
    }
  } // alphanumeric

#if XVTWS == VMWS
  if (key == K_TAB && class_id() == CLASS_LIST_FIELD)
    dispacth_e_char(win(), K_F9);
#endif

  if (key == K_SPACE) on_hit();

  return TMask_field::on_key(key);
}


///////////////////////////////////////////////////////////
// TRadio_field
///////////////////////////////////////////////////////////

TRadio_field::TRadio_field(TMask* mask)
: TList_field(mask), _nitems(0), _active_item(0)
{}


word TRadio_field::class_id() const
{
  return CLASS_RADIO_FIELD;
}


void TRadio_field::create(WINDOW parent)
{
  const short id = dlg();     // Salva il control id
  const int items = _codes.items();

  if (_prompt.not_empty())
  {
    const int dy = _flags.zerofilled ? 3 : items+2;
    create_prompt(parent, _width, dy);
  }
  _x++; _y++;
  _values.restart();

  const char* s;
  
  const int width = _flags.zerofilled ? (_width-2)/items-1 : _width-2;
  for(_nitems = 0; (s = _values.get()) != NULL; _nitems++)
  {
    CHECKD(_nitems < MAX_RADIO, "Too many items in radio button ", id);

    wincreate(WC_RADIOBUTTON, width, 1, s, parent,0);
    _radio_ctl_win[_nitems] = _win;
    _dlg += 1000;
    
    if (_flags.zerofilled)
      _x += width+1;
    else
      _y++;
  }
  _radio_ctl_win[_nitems] = NULL_WIN;   // Comodo per debug

  _dlg = id;  // Ripristina control id

  set_field_data("");
}

void TRadio_field::destroy()
{
  if (_promptwin)
  {     close_window(_promptwin); _promptwin = NULL_WIN; }

  for(int i = 0; i < _nitems; i++)
  {
    close_window(_radio_ctl_win[i]);
    _radio_ctl_win[i] = NULL_WIN;
  }
}


int TRadio_field::current() const
{
  const int c = xvt_get_checked_radio(_radio_ctl_win, _nitems);
  return c;
}


void TRadio_field::current(int n)
{
  _active_item = n;
  xvt_check_radio_button(win(), _radio_ctl_win, _nitems);
}


void TRadio_field::check_radiobutton(WINDOW checked)
{
  for(int i = 0; i < _nitems && checked != _radio_ctl_win[i]; i++);
  CHECK(i < _nitems, "Trying to check an invalid radio button");
  current(i);
}


void TRadio_field::enable(bool on)
{
  _flags.enabled = on;
  for(int i = 0; i < _nitems; i++)
    xvt_enable_control(_radio_ctl_win[i], on);
}


void TRadio_field::show(bool on)
{
  if (_promptwin)
    show_window(_promptwin, on);

  for(int i = 0; i < _nitems; i++)
    show_window(_radio_ctl_win[i], on);
  _flags.showed = on;
}

// Return TRUE if focus has left the radio
bool TRadio_field::move_focus(int d)
{
  const int act = _active_item + d;
  if (act >= _nitems || act < 0)
  {
    _active_item = current();
    return TRUE;
  }
  xvt_set_front_control(_radio_ctl_win[_active_item = act]);
  return FALSE;
}


///////////////////////////////////////////////////////////
// TGroup_field
///////////////////////////////////////////////////////////

TGroup_field::TGroup_field(TMask* mask) : TMask_field(mask)
{
  _flags.persistent = TRUE;
}

// _size means _heigth
void TGroup_field::parse_head(TScanner& scanner)
{
  _width = scanner.integer();
  _size  = scanner.integer();
}


void TGroup_field::create(WINDOW parent)
{
  const long f = _flags.rightjust ? CTL_FLAG_MULTIPLE : 0;
  wincreate(WC_GROUPBOX, _width, _size, _prompt, parent, f);
}

