#include <ctype.h>
#include <stdlib.h>     

#define STRICT
#define XVT_INCL_NATIVE

#include <applicat.h>
#include <form.h>
#include <msksheet.h>
#include <printer.h>
#include <relation.h>
#include <sheet.h>
#include <utility.h> 
#include <spool.h>
#include <expr.h>
#include <tabutil.h>

#ifndef __DEFMASK_H
#include <defmask.h>
#endif

#include "../ba/bafrm.h"
      
// per lo sheet di edit campi      
HIDDEN const int idt_id = 101;
HIDDEN const int dsc_id = 102;
HIDDEN const int prn_id = 103;    
HIDDEN const int yps_id = 104;
HIDDEN const int xps_id = 105;
HIDDEN const int len_id = 106;
HIDDEN const int col_id = 107;
HIDDEN const int int_id = 108;
HIDDEN const int spc_id = 109;
HIDDEN const int fnl_id = 110;
HIDDEN const int fnr_id = 111;
HIDDEN const int typ_id = 112;
HIDDEN const int frm_id = 113;    

///////////////////////////////////////////////////////////
// Utility functions
///////////////////////////////////////////////////////////


// Current form (edit, print)                           
HIDDEN TForm*          _cur_form = NULL;
HIDDEN TPrint_section* _cur_sect = NULL;
HIDDEN TMask*          _special_mask = NULL;

HIDDEN TForm& form()
{
  CHECK(_cur_form, "Can't print NULL form");
  return *_cur_form;
}

HIDDEN TPrint_section& section()
{
  CHECK(_cur_sect, "Can't print NULL section");
  return *_cur_sect;
}

HIDDEN TMask& special_mask()
{
  CHECK(_special_mask, "Can't access NULL mask");
  return *_special_mask;
}


// @doc INTERNAL

// @func Funzione che converte dalla notazione carattere al corrispondente
//       enum <t pagetype>
//
// @rdesc Ritorna il <t pagetype> corrispondente
pagetype char2page(
         char c)  // @parm Notazione carattere del tipo di pagina
         
{  
  pagetype pt;
  switch(c)
  {
  case '1':
  case 'E':
    pt = even_page; break;
  case '2':
  case 'F':
    pt = first_page; break;
  case '3':
  case 'L':
    pt = last_page; break;
  default:
    pt = odd_page; break;   
  }
  return pt;
}

///////////////////////////////////////////////////////////
// Gestione TFieldref su maschera
// ID   CAMPO                                       ATTIVO  
// xx0  Stringa completa del TFieldref                 
// xx1  Descrizione file 
// xx2  Bottone selezione file                         X
// xx3  Descrizione campo
// xx4  Bottone selezione campo                        X
// xx5  Primo carattere campo                          X
// xx6  Ultimo carattere campo                         X
///////////////////////////////////////////////////////////

HIDDEN void put_fieldref(const TFieldref& fr, TMask_field& f)
{             
  TRelation_description& rd = form().rel_desc();
  rd.set_cur_file(fr.file());

  TString80 desc; desc << fr;
  f.set(desc);
  
  TMask& m = f.mask();
  const short id = f.dlg(); // Campo contenente il TFieldref

  m.set(id+1, rd.file_desc());
  m.set(id+3, rd.get_field_description(fr.name()));
  m.set(id+5, (fr.from() > 0 || fr.to() > 0) ? fr.from()+1 : 0);
  m.set(id+6, fr.to() > fr.from() ? fr.to() : 0);
}


// Handler of F_BUT_FILE field on mask
HIDDEN bool but_file_handler(TMask_field& f, KEY k)                      
{
  if (k == K_F9)
  {                          
    TRelation_description& r = form().rel_desc();                                   
    
    TEdit_field& e = f.mask().efield(f.dlg()-1);
    TFieldref ref; ref = e.get();
    if (r.choose_file(ref.file()))
    {
      ref.set_file(r.file_num());
      put_fieldref(ref, e);
    }  
  }
  return TRUE;
}                      

HIDDEN bool but_file_handler_sub(TMask_field& f, KEY k)                      
{
  if (k == K_SPACE)
  {                          
    TRelation_description& r = form().rel_desc();                                   
    
    TEdit_field& e = f.mask().efield(f.dlg()-2);
    TFieldref ref; ref = e.get();
    if (r.choose_file(ref.file()))
    {
      ref.set_file(r.file_num());
      f.mask().set(F_FILE1,r.file_desc());  
    }
  }
  return TRUE;
}                      


// Handler of F_BUT_FIELD field on mask
HIDDEN bool but_field_handler(TMask_field& f, KEY k)                      
{
  if (k == K_F9)
  {                          
    TRelation_description& r = form().rel_desc();                                   
    TEdit_field& e = f.mask().efield(f.dlg()-3); // TBC
    TFieldref ref; ref = e.get();
    if (r.choose_field(ref.name()))
    {
      ref.set_name(r.field_name());
      put_fieldref(ref, e);
    }  
  }
  return TRUE;
}                      

// Handler of F_FROM field on mask
HIDDEN bool from_handler(TMask_field& f, KEY k)                      
{
  if (f.to_check(k))
  {
    TEdit_field& e = f.mask().efield(f.dlg()-5);
    TFieldref ref; ref = e.get();
    ref.set_from(atoi(f.get()));
    put_fieldref(ref, e);
  }
  return TRUE;
}

// Handler of F_TO field on mask
HIDDEN bool to_handler(TMask_field& f, KEY k)                      
{
  if (f.to_check(k))
  {
    TEdit_field& e = f.mask().efield(f.dlg()-6);
    TFieldref ref; ref = e.get();
    ref.set_to(atoi(f.get()));
    put_fieldref(ref, e);
  }
  return TRUE;
}

HIDDEN bool dateformat_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE) 
  {
    TMask& m = f.mask();
    
    char fmt[8]; 
    fmt[0] = m.get(F_DFORMAT)[0];
    fmt[1] = m.get(F_DDAY)[0];
    fmt[2] = m.get(F_DMONTH)[0];
    fmt[3] = m.get(F_DYEAR)[0];
    fmt[4] = m.get(F_DSEP)[0];
    fmt[5] = '\0';
    
    const TDate d(TODAY);
    const TFormatted_date ex(d,fmt);
    m.set(F_DEXAMPLE, ex.string());
  }
  return TRUE;
}

HIDDEN bool fmt_handler(TMask_field& f, KEY k)
{              
  if (k==K_ENTER || k==K_TAB)
  {                           
    TMask& m = f.mask();
    TPrint_section& s = ::section();
    if (s.columnwise())
    {
      const int fmt_len = m.get(F_PICTURE).len();       // length of picture
      m.set(F_LENFMT,fmt_len);
      m.set(F_NUMCOL,s.fields());
    }
    else
      m.set(F_NUMCOL,999);
  }
  return TRUE;    
}

///////////////////////////////////////////////////////////
// TForm_flags
///////////////////////////////////////////////////////////

TForm_flags::TForm_flags()
{
  automagic = dirty = finkr = finkl = memo = newpage = FALSE;
  shown = enabled  = TRUE;
}

// Read from string
// Certified 100%
bool TForm_flags::update(const char* s)
{
  CHECK(s, "NULL flags string");
  for (; *s; s++) switch(toupper(*s))
  {
  case 'A': 
    automagic = TRUE; break;
  case 'D': 
    enabled = FALSE; break;
  case 'H': 
    shown = FALSE; break;
  case 'F': 
    finkl = TRUE; break;
  case 'K': 
    finkr = TRUE; break;
  case 'M':
    memo = TRUE; break;
  case 'N':
    newpage = TRUE; break;
  default : 
    error_box("Unknown form item flag '%c'", *s); break;
  }
  return TRUE;
}


// Print on stream
// Certified 100%
void TForm_flags::print_on(ostream& out) const
{
  TString16 s;

  if (automagic) s << "A";
  if (!enabled) s << "D";
  if (!shown) s << "H";
  if (memo) s << "M";
  if (newpage) s << "N";
  if (finkl) s << "F";
  if (finkr) s << "K";

  if (s.not_empty()) 
    out << " FLAGS \"" << s << '"' << endl;
}

// Set mask fields
// Certified 100%
void TForm_flags::print_on(TMask& m)
{
  m.set(F_DISABLED,  enabled   ? " " : "X");
  m.set(F_HIDDEN,    shown     ? " " : "X");
  m.set(F_AUTOMAGIC, automagic ? "X" : " ");
  m.set(F_FINKL,     finkl     ? " " : "X");
  m.set(F_FINKR,     finkr     ? " " : "X");
}


// Get mask fields
// Certified 100%
void TForm_flags::read_from(const TMask& m)
{
  shown     = !m.get_bool(F_HIDDEN);
  enabled   = !m.get_bool(F_DISABLED);
  automagic = m.get_bool(F_AUTOMAGIC);
  finkl     = !m.get_bool(F_FINKL);
  finkr     = !m.get_bool(F_FINKR);
}

///////////////////////////////////////////////////////////
// TForm_item
///////////////////////////////////////////////////////////
TForm_item::TForm_item(TPrint_section* section)
: _section(section), _x(-1), _y(-1), _width(0), _height(0), _id(0), _ofs(0),
  _temp(FALSE)
{}

void TForm_item::copy_to_form_item(TForm_item* fi) const
{
  fi->_flag = _flag; fi->_group = _group;
  // come copiarlo facendo una cosa orrenda...
  fi->_special.destroy();
  specials().restart();
  int items = special_items();
  for (int k = 0; k < items; k++)
  {
    THash_object*  ho = specials().get_hashobj();
    fi->_special.add(ho->key(),ho->obj());
  }
  // fi->_special = _special;     sarebbe utile avere un "operator =" per i TAssoc_array

  fi->_temp = _temp; fi->_id = _id;
  fi->_x = _x; fi->_y = _y;
  fi->_width = _width; fi->_height = _height;
  fi->_effective_height = _effective_height;
  fi->_ofs = _ofs;  fi->_prompt = _prompt;
  fi->_desc = _desc; fi->_col_head = _col_head;
  // Anche qui... copia uno alla volta
  items = _message.items();
  for (k = 0; k < items; k++)
    fi->_message.add(_message.row(k),k);
  // fi->_message = _message;     sarebbe utile avere un "operator =" per i TString_array
}

TObject* TForm_item::dup() const
{
  TForm_item * fi = new TForm_item(_section);
  copy_to_form_item(fi);
  return fi;
}

bool TForm_item::parse_head(TScanner& scanner)
{
  _id = scanner.integer();

  if (_id == 0)                             // Temporary
    _id = _section->fields()+1;

  _width  = scanner.integer();
  if (_width > 0)
    _height = scanner.integer();
  return TRUE;
}


void TForm_item::print_on(ostream& out) const
{
  out << class_name() << ' ' << id();
  if (_width > 0)
  {
    out << ' ' << _width;
    if (_height > 0)
      out << ' ' << _height;
  }
  out << "\nBEGIN\n";

  print_body(out);

  out << "END\n" << endl;
}


void TForm_item::print_body(ostream& out) const
{
  out << " KEY \"" << _desc << "\"\n";

  if (_y >= 0)
    out << " PROMPT " << _x << ' ' << _y << " \"" << _prompt << "\"\n";

  if (_group.ones())
    out << " GROUP " << _group << "\n";

  out << _flag;

  if (_message.items() == 1)
  {
    const TToken_string& m = _message.row(0);
    if (!m.empty_items())
      out << " MESSAGE " << m << endl;
  }

  if (_special.items() > 0)
  {
    TAssoc_array& aa = specials();
    aa.restart();

    THash_object* op;

    while ((op = aa.get_hashobj()) != NULL)
    {
      TToken_string& t = (TToken_string&)op->obj();
      TString typ(t.get(0));
      TString val(t.get(1));
      TString des(t.get(2));
      out << " SPECIAL " << typ << " " << op->key()
          << " \"" << val << "\" \"" << des << "\"\n";
    }
  }
}


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

  if (scanner.key() == "FL")
    return _flag.update(scanner.string());

  if (scanner.key() == "ME")
  {
    TFixed_string m(scanner.line());
    m.strip_spaces();
    int n = 0;
    if (m.left(5) == "EMPTY")
    {
      n = 1;
      m.ltrim(5);
    }
    if (!m.blank())
      message(n).add(m);
    return TRUE;
  }

  if (scanner.key() == "KE")
  {
    _desc = scanner.string();
    return TRUE;
  }

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

  if (scanner.key() == "SP")
  {
    TToken_string val(scanner.pop());
    TString16 var = scanner.pop();
    val.add(scanner.string());
    val.add(scanner.string());

    _special.add(var,val);
    return TRUE;
  }

  yesnofatal_box("Unknown symbol in item '%s': '%s'",
                 (const char*)key(), (const char*)scanner.token());

  return FALSE;
}

bool TForm_item::parse(TScanner& scanner)
{
  bool ok = parse_head(scanner);

  if (ok && scanner.popkey() != "BE")
    ok = yesnofatal_box("Missing BEGIN in form item %s", (const char*)key());

  while (ok && scanner.popkey() != "EN")
    ok = parse_item(scanner);

  return ok;
}

bool TForm_item::read_from(const TRectype& prof)
{
  CHECK(prof.num() == LF_RFORM, "Il record deve essere del file LF_RFORM");
  bool changed = FALSE;

  int i = prof.get_int("X");
  if (_x != i)
  {
    _x = i;
    changed = TRUE;
  }
  i = prof.get_int("Y");
  if (_y != i)
  {
    _y = i;
    changed = TRUE;
  }
  i = prof.get_int("LEN");
  if (_width != i)
  {
    _width = i;
    changed = TRUE;
  }
  i = prof.get_int("HGT");
  if (_height != i)
  {
    _height = i;
    changed = TRUE;
  }
  TString p(prof.get("PROMPT"));
  if (p.not_empty())
  {
    if (p[0] == '\xFE') p.cut(0);
    const int l = p.len();
    if (l > 0 && p[l-1] == '\xFF')
    {
      p[l-1] = ' ';
      p << '\0';
    }
    _prompt = p;
    changed = TRUE;
  }

  const bool s = prof.get_bool("ATTIVO");
  if (_flag.shown != s)
  {
    _flag.shown = s;
    changed = TRUE;
  }

  TToken_string special(prof.get("SPECIAL"),'\n');
  special.rtrim();
  const int sp_items = special.items();
  for (i = 0; i < sp_items; i++)
  {
    TToken_string sp(special.get(i), '$');
    TString key(sp.get(0));
    TString val(sp.get(1));

    if (!_special.is_key(key))
    {
      error_box("Variabile speciale non presente nel profilo: %s",
                 (const char*)key);
      continue;
    }
    TToken_string& tt = (TToken_string&)_special[key];
    tt.add(val,1);
    // forza riscrittura su memo
    if (tt.items() == 3) tt.add("X");
  }

  return TRUE;
}

void TForm_item::print_on(TRectype& prof)
{
  CHECK(prof.num() == LF_RFORM, "Il record deve essere del file LF_RFORM");
  prof.put("ID", id());
  prof.put("X", _x);
  prof.put("Y", _y);
  prof.put("LEN", width());
  prof.put("HGT", height());
  if (_prompt.empty()) _prompt << '\xFE' << '\0';
  const int l = _prompt.len();
  const char c = _prompt[l-1];
  if (c==' ') _prompt[l-1]='\xFF';
  prof.put("PROMPT", _prompt);
  prof.put("ATTIVO", shown() ? "X" : " ");

  // specials: se e' stato cambiato, la tokenstring del valore contiene
  // una X alla fine (campo 3)
  {
    TToken_string special(128,'\n');
    _special.restart();

    for (int i = 0; i < _special.items(); i++)
    {
      THash_object* o = _special.get_hashobj();

      TString key(o->key());
      TToken_string& tt = (TToken_string&)o->obj();

      if (tt.items() == 4)
      {
        TToken_string sp(key,'$');
        TString val(tt.get(1));
        sp.add(val);
        special.add(sp);
      }
    }
    special.rtrim();
    prof.put("SPECIAL", special);
  }
}

// @doc EXTERNAL

// @mfunc Abilita/Disabilita il campo
void TForm_item::enable(
     bool on) // @parm Operazione da svolgere sul campo:
    // @flag TRUE | Il campo viene abiliato
    // @flag FALSE | Il campo viene disabiliato

// @comm Viene automaticamente setta se il campo diventi visibile o nascosto (chiama <mf TForm_item::show>)
{
  _flag.enabled = on;
  show(on);
}

void TForm_item::set_special_value(const char* s, const char* val)
{
  TToken_string& tt = (TToken_string&) _special[s];
  tt.add(val,1);
  if (tt.items()==3) tt.add("X");
}

const char* TForm_item::get_special_item(const char* s, int n) const
{
  TAssoc_array& sp = (TAssoc_array&)_special;
  if (sp.is_key(s))
  {
    TToken_string& tt = (TToken_string&) sp[s];
    return tt.get(n);
  } else return "";
}

void TForm_item::string_at(int x, int y, const char* s)
{
  if (shown())
  {
    if (section().columnwise()) x += _ofs;
    TPrintrow& row = section().row(y-1);         // Seleziona riga di stampa

    if (_width > 0 && strlen(s) > (word)_width)  // Tronca testo se necessario
    {
      strncpy(__tmp_string, s, width());
      __tmp_string[_width] = '\0';
      s = __tmp_string;
    }
    row.put(s, x-1);                             // Stampa testo
  }
}


TToken_string& TForm_item::message(int m)
{
  TToken_string* t = (TToken_string*)_message.objptr(m);
  if (t == NULL)
  {
    t = new TToken_string(16);
    _message.add(t, m);
  }
  return *t;
}

// @doc EXTERNAL

// @mfunc Manda il messaggio al campo <p dest>
void TForm_item::send_message(
     const TString& cmd,  // @parm Messaggio di comando
     TForm_item& des) const // @parm Campo a cui destinare il messaggio
{
  if (cmd == "ADD" || cmd == "INC")
  {
    if (form().message_add_enabled())
    {
      const real n((cmd[0] == 'I') ? "1.0" : get());
      real r(des.get());
      r += n;
      des.set(r.string());
    }
  } else if (cmd == "COPY") {
    des.set(get());
  } else if (cmd == "APPEND") {
    TString256 val = des.get();
    if (val.not_empty()) val << ' ';
      val << get();
      des.set(val);
  } else if (cmd == "DISABLE") {
    des.disable();
  } else if (cmd == "ENABLE") {
    des.enable();
  } else if (cmd == "HIDE") {
    des.hide();
  } else if (cmd == "RESET") {
    des.set("");
  } else if (cmd == "SHOW") {
    des.show();
  } else if (cmd == "SUB") {
    const real n(get());
    real r(des.get());
    r -= n;
    des.set(r.string());
  } else if (cmd[0] == '"') {
    TString256 val(cmd);
    val.strip("\"");
    des.set(val);
  } else
    error_box("Unknown message in item '%s': '%s'",
                  (const char*)key(), (const char*)cmd);
}


TForm_item& TForm_item::find_field(const TString& id) const
{
  if (isdigit(id[0]))              // Field in the same section
  {
    TForm_item& des = section().find_field(atoi(id));
    return des;
  }

  const int freccia = id.find("->");
  const pagetype pt = (freccia==2) ? char2page(id[1]) : section().page_type() ;
  char se;
  int id_num;
  int pos_id;
  if (freccia)
  {
    pos_id=freccia+2;
    id_num=atoi(id.mid(pos_id));
    se = id[0];
  } else {
    id_num=0;
    se = section().section_type();
    pos_id=0;
  }
  if (id_num)  // id numerico: campo semplice
  {
    TForm_item& des = form().find_field(se, pt, id_num);
    return des;
  } else {  // id stringa : campo sezione
    TForm_item& des = form().find_field(se, pt, id.mid(pos_id));
    return des;
  }
}

// il parametro num serve per identificare il messaggio nelle listbox...
bool TForm_item::do_message(int num)
{
  TToken_string& messaggio = message(num);
  if (messaggio.empty_items()) return FALSE;

  TToken_string msg(16, ',');
  for (const char* m = messaggio.get(0); m; m = messaggio.get())
  {
    msg = m;
    if (*m == '_')
      form().validate(*this, msg);
    else
    {
      const TString80 cmd(msg.get());      // Get command
      const TString80 id = msg.get();      // Get destination

      if (id.right(1) == "@")
      {
        const byte group = atoi(id);
        // Send the message to all fields in local and upper sections with the given group
        send_message_to_group(cmd,group,section(),&section());
      }
      else
      {
        TForm_item& des = find_field(id);
        send_message(cmd, des);
      }
    }
  }

  return TRUE;
}

void TForm_item::send_message_to_group(const char * cmd,byte id,const TPrint_section & section, const TPrint_section *starting_section) 
{
  TForm_item * f ;
  // look into local section
  for(word i = 0; i < section.fields(); i++)
  {
    f = &section.field(i);
    if (f->in_group(id))
      send_message(cmd,*f);
  }
  // look into the section above ("father" )
  if (section.section_above() && section.section_above()!=starting_section) {
    send_message_to_group(cmd,id,*section.section_above(),&section);
  }   
  // look into sections beside ("brothers" )
  for (int j = 0; j < section.subsections(); j++) {
    const TPrint_section &bs=section.subsection(j)->subsection();
    if (&bs!=starting_section) {
      send_message_to_group(cmd,id,section.subsection(j)->subsection(),&section);
    }
  }
}


bool TForm_item::update()
{
  if (_prompt.right(1) == "#")
  {
    TString prompt(_prompt);
    for (int i = prompt.len()-2; i >= 0; i--)
      if (prompt[i] != '#') break;
    prompt.cut(i+1);
    string_at(x(), _y, prompt);
  }
  else string_at(x(), _y, _prompt);

  return TRUE;
}

void TForm_item::print_on(TToken_string& row)  const
{
  row.cut(0);
  row.add(id());
  row.add(key());
  row.add(_y);
  row.add(_x);
  row.add(shown() ? " " : "X");

  if (form().edit_level() > 1)
  {
    const long fu = _group.first_one();
    if (fu > 0) row.add(fu);
    else row.add(" ");
  }
}

void TForm_item::print_on_sheet_row(TToken_string& tt) const
{
  TString tmp(80);
  tt.add(_id, idt_id - 101);
  tt.add(_desc, dsc_id - 101);
  tt.add(_x, _section->columnwise() ? col_id - 101 : xps_id - 101);
  tt.add(_width, len_id - 101);
  if (!_section->columnwise())
    tt.add(_y, yps_id - 101);
  tt.add(class_name(), typ_id - 101);
  tt.add(shown() ? " " : "X", prn_id - 101);
  tmp = example(); tt.add(tmp, frm_id - 101);

  if (_section->columnwise())
  {
    tt.add(_ofs, spc_id -101);
    tt.add(finkl() ? " " : "X", fnl_id -101);
    tt.add(finkr() ? " " : "X", fnr_id -101);
    tt.add(_col_head, int_id - 101);
  }
}

void TForm_item::print_on(TMask& m)
{
  m.set(F_CLASS, class_name());
  m.set(F_ID, id());
  m.set(F_KEY, key());
  m.set(F_X, _x);
  m.set(F_Y, _y);
  m.set(F_PROMPT, _prompt);
  m.set(F_WIDTH, _width);
  if (_section->columnwise())
    m.set(F_INTEST, _col_head);
  m.set(F_HEIGHT, _height);
  m.set(F_SPACES, _ofs);

  _flag.print_on(m);

  for (int g = 1; g <= 24; g++)
    m.set(F_GROUP+g, _group[g] ? "X" : " ");
}

void TForm_item::read_from(const TMask& m)
{
  _desc = m.get(F_KEY);
  _x = atoi(m.get(F_X));
  _y = atoi(m.get(F_Y));
  _prompt = m.get(F_PROMPT);
  _width = atoi(m.get(F_WIDTH));
  if (_section->columnwise())
    _col_head = m.get(F_INTEST);
  _height = atoi(m.get(F_HEIGHT));
  _id     = atoi(m.get(F_ID));
  _ofs    = atoi(m.get(F_SPACES));

  _flag.read_from(m);

  _group.reset();
  for (int g = 1; g <= 24; g++)
    _group.set(g, m.get_bool(F_GROUP+g));
}

void TForm_item::read_from(TToken_string& s)
{
  _id     = s.get_int(idt_id - 101);
  _desc   = s.get(dsc_id - 101);
  _x      = _section->columnwise() ? s.get_int(col_id - 101) : s.get_int(xps_id - 101);
  _width = s.get_int(len_id - 101);
  if (!_section->columnwise())
    _y      = s.get_int(yps_id - 101);
  _flag.set_shown(s.get(prn_id - 101)[0] != 'X');

  if (_section->columnwise())
  {
    _ofs    = s.get_int(spc_id - 101);
    _flag.set_finkl(s.get(fnl_id - 101)[0] != 'X');
    _flag.set_finkr(s.get(fnr_id - 101)[0] != 'X');
    _col_head = s.get(int_id - 101);
  }
  set_dirty();
}

bool TForm_item::edit(TMask& m)
{
  m.enable(F_CLASS, m.insert_mode());
  m.reset();

  if (m.insert_mode())
  {
    short id = 0;
    for (word i = 0; i < section().fields(); i++)
    {
      const TForm_item& f = section().field(i);
      if (f.id() > id) id = f.id();
    }
    _id = id+1;
  }

  print_on(m);

  const bool godmode = form().edit_level() > 1;
  m.enable_page(1, godmode);
  m.enable(-7, godmode);
  m.enable(F_ID, godmode);
  m.enable(F_KEY,godmode);
  m.enable(F_Y,!_section->columnwise());
  m.enable(F_INTEST,_section->columnwise());
  if (_flag.memo)
  {
    m.disable(F_PROMPT);
    m.hide(F_PROMPT);
    m.enable(F_MEMO);
    m.show(F_MEMO);
    m.enable(F_HEIGHT);
    m.show(F_HEIGHT);
  }
  else
  {
    m.hide(F_MEMO);
    m.disable(F_MEMO);
  }
  const bool dirty = m.run() == K_ENTER;
  if (dirty)
  {
    read_from(m);
    set_dirty();
  }
  return dirty;
}

const TString& TForm_item::picture() const
{
  CHECK(0, "Can't get the picture of a generic form item!");
  return _prompt;
}

void TForm_item::set_picture(const char*)
{
  CHECK(0, "Can't set the picture of a generic form item!");
}

TToken_string& TForm_item::memo_info()
{
  CHECK(0, "Can't get a memo of a generic form item!");
  return TToken_string();
}

short TForm_item::x()
{
  if (_section == NULL || !_section->columnwise())
    return _x;
  return _section->tab(_x-1) + _section->ofspc();
}


///////////////////////////////////////////////////////////
// TForm_subsection
///////////////////////////////////////////////////////////


TForm_subsection::TForm_subsection(TPrint_section* s, const char* nm) 
                : TForm_item(s), _ssec(&(s->form()), s->section_type(), s->page_type()), 
                  _file_id(-1), _name(nm), _condexpr(NULL)
{
  _ssec.set_subsection_above(this);
}

TForm_subsection::~TForm_subsection()
{
  if (_condexpr) delete _condexpr;
}

TObject* TForm_subsection::dup() const
{
  TForm_subsection* fs = new TForm_subsection(_section);
  copy_to_form_item(fs);
  fs->_ssec = _ssec;
  fs->_name = _name;
  fs->_file_id = _file_id;
  fs->_condexpr=new TExpression((const char *)_condexpr);
  return fs;
}

int TForm_subsection::fileid()
{
  return _file_id;
}
// restituisce la condizione della sottosezione
const char * TForm_subsection::condition()
{
  if (_condexpr)
    return (const char *)_condexpr->string();
  else  
    return NULL;
}

// imposta la condizione della sottosezione
void TForm_subsection::setcondition(const char * cond,TTypeexp type)
{
  if (_condexpr) {
    if (_file_id!=-1)
      subsection().set_subs_cond(_file_id,cond,_condexpr->string());
    delete _condexpr;
  }
  _condexpr=new TExpression(cond,type); 
}

void TPrint_section::set_subs_cond(int file,const char *newcond,const char * oldcond)
{
  TForm_subsection * s;
  s=(TForm_subsection * )(_subsections.first_item());
  while (s) {
    TString newsubcond(s->condition());
    if (file!=-1 && s->fileid()==file && newsubcond.not_empty()) {
      // there's another file group subsection below....
      newsubcond=newsubcond.mid(strlen(oldcond));
      newsubcond.insert(newcond);
      s->setcondition(newsubcond,_strexpr);
    }
    s=(TForm_subsection * )(_subsections.succ_item()) ;
  } 
}

// ritorna l'indirizzo della prima sottosezione condizionale di livello superiore alla corrente
TForm_subsection *TForm_subsection::upper_conditional() const 
{
  if (section().subsection_above()) 
  {
    if (section().subsection_above()->_condexpr && section().subsection_above()->_file_id>0)
      return section().subsection_above();
    else 
      return section().subsection_above()->upper_conditional();
  } else
    return NULL;
}


bool TForm_subsection::parse(TScanner& s)
{
  _section->add_subsection(this);

  name(s.pop());
  //_width  = s.integer();  // ...unused
  //_x      = s.integer();  // ...unused
  _y      = s.integer();  // posizione rispetto ai campi della sezione "padre"
  _height = s.integer();  // minima altezza (primo "header")

  _ssec.parse_head(s);
  s.popkey();
  if (s.key() == "FI")     // FI_LE su cui iterare con next_match
  {
    _file_id = name2log(s.pop());
    if (s.popkey() == "GR" || s.key() == "BY") 
    {  
      // GR_OUP or BY group : gestione dei raggruppamenti o big skip
      TString e;
      TForm_subsection * uppersec=upper_conditional();
      if (uppersec && uppersec->_file_id==_file_id)
        // se anche la sezione soprastante 
        e << uppersec->_condexpr->string() << "+" ;
      e << s.line();
      _condexpr = new TExpression(e,_strexpr);   
      _bigskip = s.key() == "BY";
    } else
      s.push();
  } else {
    if (s.key() == "NU")     // NU_MERIC CONDITION di gestione della sezione
      _condexpr = new TExpression(s.line(),_numexpr);   // 
    else if (s.key() == "ST")     // ST_RING CONDITION di gestione della sezione
      _condexpr = new TExpression(s.line(),_strexpr);   // 
    else if (s.key() != "GR")     // GR_OUP section (raggruppamento puro)
      s.push();
  } 
  if (s.popkey() == "FL")
    _flag.update(s.string());
  else s.push();
  return(_ssec.parse_body(s)) ;
}

// nel caso di subsection viene chiamata la set_body anzich� la update
bool TForm_subsection::update()
{ return TRUE;}


bool TForm_subsection::set_body(bool showfields)
{
  bool ok = FALSE;
  TCursor* cur = form().cursor();
  TRelation* rel = (TRelation* )form().relation();

  if (!enabled()) return FALSE;
  
  if (cur == NULL || (_file_id == -1 && _condexpr==NULL)) // ...unused
    ok = _ssec.update_and_print(showfields && shown(),atnewpage());
  else if (_file_id == -1 && _condexpr!=NULL) // subsection condizionale 
    {
      if ((bool)(_section->eval_expr(*_condexpr,_file_id)))
        ok = _ssec.update_and_print(showfields && shown(),atnewpage());
    } 
  else  
  {
    if (_condexpr) { // sottosezione di raggruppamenti su file
      int i = 0;
      TString group_expr((const char *)(_section->eval_expr(*_condexpr,_file_id)));
      bool again=TRUE;
      while (again && group_expr==(const char *)(_section->eval_expr(*_condexpr,_file_id)))
      {
        form().match_result(_file_id);
        if (!_bigskip || i==0)
        {
          if (!(ok = _ssec.update_and_print(showfields && shown(),atnewpage())))
            break;
          _ssec.set_repeat_count(++i);
        } 
        if (form().next_match_done(_file_id))
        {
          again=form().last_match_result(_file_id); // continue if there WAS a match
        } else  {
          again=cur->next_match(_file_id) ;       
          form().match_result(_file_id,again);      // store if a match occoured
        }
       }
      _ssec.set_repeat_count(0);
    } else {  // sottosezione di file 
      int i = 0;
      if (cur->is_first_match(_file_id))
      {
        bool again;
        do {
          form().match_result(_file_id);
          if (!(ok = _ssec.update_and_print(showfields && shown(),atnewpage())))
            break;
          _ssec.set_repeat_count(++i);
          if (form().next_match_done(_file_id))
            again=form().last_match_result(_file_id);
          else 
            again= cur->next_match(_file_id);
        }  while (again);
      }
      _ssec.set_repeat_count(0);
    }
  }
  return ok;
}


bool TForm_subsection::edit(TMask& m)
{
  // mask con nome e bottone edit contents / annulla
  TMask mu("ba2100u");
  mu.set(F_CAPTION, _name);
  mu.set(F_WIDTH,   _width);
  mu.set(F_HEIGHT,  _height);
  mu.set(F_X,       _x);
  mu.set(F_Y,       _y);

  mu.set_handler(F_BUT_FILE1, but_file_handler_sub);

  if (_file_id != -1)
  {
    // set file description
    form().rel_desc().set_cur_file(_file_id);
    TString80 desc; desc << form().rel_desc().file_desc();
    mu.set(F_FILE1, desc);
  }

  KEY k;

  // vedere se e' nuova etc.
  // gestire aggiunta / modifica menu

  while ((k = mu.run()) != K_ESC)
  {
    if (mu.field(F_CAPTION).dirty())
      _name = mu.get(F_CAPTION);

    if (mu.field(F_WIDTH).dirty())
      _width = mu.get_int(F_WIDTH);

    if (mu.field(F_HEIGHT).dirty())
      _height = mu.get_int(F_HEIGHT);

    if (mu.field(F_X).dirty())
      _x = mu.get_int(F_X);

    if (mu.field(F_Y).dirty())
      _y = mu.get_int(F_Y);

    if (mu.field(F_FILE1).dirty())
    {
      if (mu.get(F_FILE1).empty())
        _file_id = -1;
      else
        _file_id = form().rel_desc().file_num();
    }

    if (k == K_INS)
      _ssec.edit(_name);
    else if (k == K_DEL)
    {
      // remove myself

    }
    else if (k == K_ENTER)
      break;
  }

  return k != K_ESC;
}


void TForm_subsection::print_on(ostream& out) const
{
  out << "SEZIONE " << _name << ' ' << _width << ' ' << _height
    << ' ' << _x << ' ' << _y;

  if (_file_id != -1)
    out << " FILE " << _file_id;
  out << "\n";

  for (word i = 0; i < _ssec.fields(); i++)
    out << _ssec.field(i);
  out << "\nEND" << "\n";
}

// @mfunc Abilita (default) o disabilita la stampa di tutti i campi della sottosezione
/*void TForm_subsection::show(bool on)
{
  TForm_item::show(on);
  for (unsigned int i = 0; i < _ssec.fields(); i++)
    _ssec.field(i).show(on);
}

// @mfunc Abilita (default) o disabilita la valutazione e la stampa di tutti i campi della sottosezione
void TForm_subsection::enable(bool on)
{
  TForm_item::enable(on);
  for (unsigned int i = 0; i < _ssec.fields(); i++)
    _ssec.field(i).enable(on);
} */

///////////////////////////////////////////////////////////
// TForm_string
///////////////////////////////////////////////////////////


TForm_string::TForm_string(TPrint_section* section)
            : TForm_item(section), _memo("",'\n')
{}

TObject* TForm_string::dup() const
{
  TForm_string* fs = new TForm_string(_section);
  copy_to_form_item(fs);
  fs->_str = _str;
  fs->_picture = _picture;
  fs->_field = _field;
  fs->_memo = _memo;
  return fs;
}

bool TForm_string::edit(TMask& m)
{
  const bool godmode = form().edit_level() > 1;
  m.enable(F_PROMPT, godmode ? TRUE : (_field.items()==0));
  return TForm_item::edit(m);
}

bool TForm_string::parse_item(TScanner& scanner)
{
  if (scanner.key() == "FI")  // FIELD reference
  {
    TFieldref* fr = new TFieldref(scanner.line(), 0);
    _field.add(fr);
    return TRUE;
  }

  if (scanner.key() == "PI")  // PICTURE def
  {
    set_picture(scanner.string());
    return TRUE;
  }

  return TForm_item::parse_item(scanner);
}

void TForm_string::print_body(ostream& out) const
{
  TForm_item::print_body(out);
  if (_picture.not_empty())
    out << " PICTURE \"" << _picture << "\"" << endl;
  for (int i = 0; i < _field.items(); i++)
    out << " FIELD " << field(i) << endl;
}

bool TForm_string::read_from(const TRectype& prof)
{
  bool changed = TForm_item::read_from(prof);

  const TString& pict = prof.get("PICT");
  if (_picture != pict)
  {
    _picture = pict;
    changed = TRUE;
  }

  if (has_memo())
  {
    const TString& m = prof.get("TESTO");
    if (_memo != m)
    {
      _memo = m;
      changed = TRUE;
    }
  }

  return changed;
}

void TForm_string::print_on(TToken_string& row)
{
  TForm_item::print_on(row);
  if (_field.items() && form().edit_level() > 1)
    row << '|' << field(0);
}

void TForm_string::print_on(TRectype& prof)
{
  TForm_item::print_on(prof);
  prof.put("PICT", _picture);
  if (has_memo())
  {
    prof.put("TESTO", _memo);
  }
}

void TForm_string::print_on(TMask& m)
{
  TForm_item::print_on(m);
  for (int i = 0; i < _field.items(); i++)
    put_fieldref(field(i), m.field(i == 0 ? F_FIELDREF1 : F_FIELDREF2));

  m.set(F_PICTURE, _picture);
  m.set(F_MEMO, _memo);

  TSheet_field& s = (TSheet_field&)m.field(F_ITEMS);
  s.reset();
  if (_message.items() > 0)
  {
    TToken_string& row = s.row(0);
    row = " | ";
    row.add(message(0));
  }
}

void TForm_string::read_from(const TMask& m)
{
  TForm_item::read_from(m);
  _picture = m.get(F_PICTURE);
  _memo    = m.get(F_MEMO);

  for (int i = 0; i < 2; i++)
  {
    const TString& f = m.get(i == 0 ? F_FIELDREF1 : F_FIELDREF2);
    if (f.not_empty())
    {
      TFieldref* fr = (TFieldref*)_field.objptr(i);
      if (fr == NULL)
      {
        fr = new TFieldref(f, 0);
        _field.add(fr, i);
      }
      *fr = f;
    }
    else
      _field.destroy(i);
  }

  TSheet_field& f = (TSheet_field&)m.field(F_ITEMS);
  TToken_string& msg = f.row(0);
  if (msg.empty_items())
    _message.destroy(0);
  else
    _message.add(msg.get(2), 0);
}

bool TForm_string::set(const char* s)
{
  _str = s;
  return TRUE;
}

const char* TForm_string::get() const
{ return _str; }


// Se un campo e' abilitato ed ha almeno un riferimento su file leggilo
bool TForm_string::read()
{
  const bool ok = enabled();
  if (ok)
  {
    if (_field.items() != 0)
    {
      const char* s = "";
  // !?!?!?!!
      const TRelation* r = (TRelation* )form().relation();
      CHECK(r, "Can't read from null relation");
      for (int i = 0; i < _field.items() && *s == '\0'; i++)
        s = field(i).read(*r);
      set(s);
    }
  }
  return ok;
}

void TForm_string::put_paragraph(const char* s)
{
  if (hidden()) return;          

  const int h = height();

  if (h > 1)
  {
    /*
    const int w = width();
    TParagraph_string p(s, w);
    int i = _prompt.not_empty() ? 1 : 0;
    for (; (s = p.get()) != NULL && i < h; i++)
      string_at(x(), _y+i, s);
    _effective_height = i;
    */
    const int w = width();
    int plen=_prompt.len();
    TString prompt(plen,'-');
    TParagraph_string p(prompt << s, w);

#ifdef DBG
    if (plen>0 && *s)
      yesno_box("Nei form la stampa di un item su pi� righe con prompt ora non pone pi� il prompt isolato sulla prima riga ma allinea a sinistra del prompt ");
#endif
    for (int i=0; (s = p.get()) != NULL && i < h; i++)
    {
      string_at(x()+plen, _y+i, s+plen);
    }
    _effective_height = i;
  }
  else       
  {
    if (_prompt.empty())
      string_at(x(), _y, s);
    else
      string_at(-1 , _y, s);
  }
}


void TForm_string::apply_format(TString& s) const
{
  TString tmp(s);
  if (!picture().blank())
  {
    TToken_string delim(4, ',');         // Stringa con i due delimitatori
    const char * pic = picture();              // Picture senza delimitatori

    if (pic[0] == '(')                   // Se ci sono i delimitatori ...
    {
      const int bra = picture().find(')');
      if (bra > 0)                       // ... cerca la parentesi chiusa
      {
        delim = picture().sub(1, bra);   // memorizza delimitatori
        pic += bra+1;                    // toglili dalla picture
      }
    }

    if (class_name() == "DATA" && s.empty())
      tmp ="";
    else
      tmp.picture(pic, s);               // riempi la stringa col valore pitturato

    if (!delim.empty_items())            // Aggiungi delimitatori
    {
      TString16 d(delim.get(0));
      const int ld = d.len();

      if (ld > 0)                  // Se il primo delimitatore e' valido ...
      {
        for (int spc = 0;s[spc]==' ' ; spc++) ;
        if (spc < ld)
        {
          TString16 spazi;
          spazi.spaces(ld - spc);
          tmp.insert(spazi,0);
          spc = ld;
        }
        tmp.overwrite(d,spc - ld);
      }
      d = delim.get();
      if (d.not_empty())                 // Se il secondo delimitatore e' valido ...
        tmp << d;                          // ... aggiungilo alla fine
    }
    s = tmp;
  }
}

bool TForm_string::update()
{
  if (read()) // valuta il campo
  {
    TForm_item::update();
    // esegue i messaggi
    const int n = (_message.objptr(1) != NULL && *get()=='\0' ? 1 : 0);
    do_message(n);
    // prende il campo e lo stampa
    TString s(get());
    apply_format(s);
    put_paragraph(s);
  }
  return TRUE;
}

const char* TForm_string::example() const
{
  TString prova("XXXXXXXXXXXXXXXXXXXXXXXXXX");
  apply_format(prova);
  const int w = width();
  if (prova.size() > w && w > 0) prova.cut(w);
  return strcpy(__tmp_string, prova);
}



///////////////////////////////////////////////////////////
// TForm_number
///////////////////////////////////////////////////////////

class TForm_number : public TForm_string
{

protected: // TForm_string
  virtual const char* class_name() const { return "NUMERO"; }
  virtual bool parse_head(TScanner& scanner);
  virtual bool update();
  //virtual int height() const { return 0; }

protected:
  int decimals() const { return _height; }

public:

  virtual TObject* dup() const;
  void set_decimals(int d) { _height = d; }
  virtual const char* example() const;
  virtual void set_picture(const char* p);
  virtual void apply_format(TString& s) const;
  virtual void put_paragraph(const char * s);

  TForm_number(TPrint_section* section) : TForm_string(section) {}
  virtual ~TForm_number() {}
};

bool TForm_number::parse_head(TScanner& scanner)
{
  return TForm_item::parse_head(scanner);
}

TObject* TForm_number::dup() const
{
  TForm_number *fn = new TForm_number(_section);
  copy_to_form_item(fn);
  return fn;
}

void TForm_number::put_paragraph(const char* s)
{
  if (hidden()) return;

  int gap = 0;
  if (section().columnwise())
  {
    const int w = width();
    const int l = strlen(s);
    if (w>l) gap = w-l;
  }
  if (_prompt.empty())
    string_at(x()+gap, _y, s);
  else
    string_at(-1, _y, s); // se ha il prompt stampa all'ultima posizione raggiunta
}

bool TForm_number::update()
{
  if (read()) // valuta il campo
  {
    TForm_item::update();
    const int nm = (_message.objptr(1) != NULL ? 1 : 0);
    do_message(nm);
    real n(get());
    n.round(decimals());

    if (!n.is_zero())
    {
      TString s(get());
      apply_format(s);
      put_paragraph(s);
    }
  }

  return TRUE;
}

void TForm_number::apply_format(TString& s) const
{
  if (!picture().blank())
  { 
    real n(s);
    TToken_string delim(4, ',');         // Stringa con i due delimitatori
    TString pic(picture());              // Picture senza delimitatori
    int maxlen = -1;

    if (pic[0] == '(')                   // Se ci sono i delimitatori ...
    {
      const int bra = pic.find(')');
      if (bra > 0)                       // ... cerca la parentesi chiusa
      {
        delim = pic.sub(1, bra);         // memorizza delimitatori
        pic.ltrim(bra + 1);              // toglili dalla picture
      }
    }

    const int at = pic.find('@');
    if (at > 0)
    {
      const int len = atoi(&pic[at+1]);
      if (len > 0)
      {
        maxlen = len;
        pic.cut(at);
      }
    }

    s=n.string(pic);          // riempi la stringa col valore pitturato

    if (maxlen >= 0 && maxlen < s.size())
      s.cut(maxlen);
    if (!delim.empty_items())            // Aggiungi delimitatori
    {
      TString16 d(delim.get(0));
      const int ld = d.len();

      if (ld > 0)                  // Se il primo delimitatore e' valido ...
      {
        for (int spc = 0;s[spc]==' ' ; spc++) ;
        if (spc < ld)
        {
          TString16 spazi;
           spazi.spaces(ld - spc);
           s.insert(spazi,0);
           spc = ld;
         }
         s.overwrite(d,spc - ld);
       }
       d = delim.get();
       if (d.not_empty())                 // Se il secondo delimitatore e' valido ...
         s << d;                          // ... aggiungilo alla fine
    }
  }
//  else s = n.string();
}

void TForm_number::set_picture(const char *p)
{
  TForm_string::set_picture(p);
  const int comma = picture().find(',');
  if (comma > 0) set_decimals(picture().len() - comma -1);
}

const char* TForm_number::example() const
{
  TString s("123456789120.00");
  apply_format(s);
  return strcpy(__tmp_string, s);
}

                        
                        
///////////////////////////////////////////////////////////
// TForm_date
///////////////////////////////////////////////////////////

class TForm_date : public TForm_string
{
  TString16 _format;

protected:
  virtual const char* class_name() const { return "DATA"; }
  virtual bool read();
  virtual bool set(const char*);
  bool set(const TDate& d);
  virtual bool parse_item(TScanner& scanner);
  virtual void print_body(ostream& out) const;

  virtual void print_on(TMask& m);
  virtual void read_from(const TMask& m);

  virtual bool read_from(const TRectype& rform);
  virtual void print_on(TRectype& rform);

public:

  virtual TObject* dup() const;
  void set_format(const char* f) { _format = f; }
  virtual bool edit(TMask& m);
  virtual const char* example() const;
  TForm_date(TPrint_section* section);
  virtual ~TForm_date() {}
};


TForm_date::TForm_date(TPrint_section* section)
: TForm_string(section), _format("1444-")
{}

TObject* TForm_date::dup() const
{
  TForm_date* fd = new TForm_date(_section);
  copy_to_form_item(fd);
  fd->_format = _format;
  return fd;
}

bool TForm_date::read()
{
  bool ok = TForm_string::read();
  if (ok && !get()[0] && automagic())
    set(printer().getdate());
  return ok;
}

void TForm_date::print_body(ostream& out) const
{
  TForm_string::print_body(out);
  out << " FORMAT \"" << _format << "\"\n";
}

bool TForm_date::parse_item(TScanner& scanner)
{
  if (scanner.key() == "FO")
  {
    _format = scanner.string();
    return TRUE;
  }
  return TForm_string::parse_item(scanner);
}

bool TForm_date::read_from(const TRectype& prof)
{
  bool changed = TForm_string::read_from(prof);

  const TString& df = prof.get("DATEFORM");
  if (df.not_empty() && df != _format)
  {
    _format = df;
    changed = TRUE;
  }

  return changed;
}

void TForm_date::print_on(TRectype& prof)
{
  TForm_string::print_on(prof);
  prof.put("DATEFORM", _format);
}


bool TForm_date::set(const char* s)
{
  const TDate da(s);
  return set(da);
}

bool TForm_date::set(const TDate& da)
{
  TFormatted_date d(da); d.set_format(_format);
  TForm_string::set(d.string());
  return TRUE;
}

void TForm_date::print_on(TMask& m)
{
  const TDate dd(TODAY);
  TFormatted_date d(dd); d.set_format(_format);
  m.set(F_DEXAMPLE, d.string());

  m.set(F_DFORMAT, _format.mid(0,1));
  m.set(F_DDAY,    _format.mid(1,1));
  m.set(F_DMONTH,  _format.mid(2,1));
  m.set(F_DYEAR,   _format.mid(3,1));
  m.set(F_DSEP,    _format.mid(4,1));

  TForm_string::print_on(m);
}

void TForm_date::read_from(const TMask& m)
{
  TForm_string::read_from(m);

  // adjust format string
  _format[0] = m.get(F_DFORMAT)[0];
  _format[1] = m.get(F_DDAY   )[0];
  _format[2] = m.get(F_DMONTH )[0];
  _format[3] = m.get(F_DYEAR  )[0];
  _format[4] = m.get(F_DSEP   )[0];
  _format[5] = '\0';
}



bool TForm_date::edit(TMask& m)
{
  return TForm_string::edit(m);
}

const char* TForm_date::example() const
{
  const TDate dd(TODAY);
  TFormatted_date d(dd); d.set_format(_format);
  TString s(d.string());
  return strcpy(__tmp_string, s);
}

///////////////////////////////////////////////////////////
// TForm_list
///////////////////////////////////////////////////////////

class TForm_list : public TForm_string
{
  TToken_string _codes;
  TToken_string _values;

protected:
  virtual const char* class_name() const { return "LISTA"; }
  virtual bool parse_item(TScanner& scanner);
  virtual void print_on(TMask& m);
  virtual void read_from(const TMask& m);
  virtual void print_body(ostream& out) const;
  virtual bool update();

public:
  virtual TObject* dup() const;
  TForm_list(TPrint_section* section);
  virtual ~TForm_list() {}
};

TForm_list::TForm_list(TPrint_section* section)
: TForm_string(section)
{}

TObject* TForm_list::dup() const
{
  TForm_list* fl = new TForm_list(_section);
  copy_to_form_item(fl);
  fl->_codes = _codes;
  fl->_values = _values;
  return fl;
}

bool TForm_list::parse_item(TScanner& scanner)
{
  if (scanner.key() == "IT")
  {
    TToken_string s(scanner.string());
    _codes.add(s.get());
    _values.add(s.get());

    while (scanner.popkey() == "ME")
    {
      TFixed_string m(scanner.line());
      m.strip_spaces();
      message(_values.items()-1).add(m);
    }
    scanner.push();
    return TRUE;
  }

  return TForm_string::parse_item(scanner);
}

void TForm_list::print_on(TMask& m)
{
  TForm_string::print_on(m);
  TSheet_field& s = (TSheet_field&)m.field(F_ITEMS);
  s.reset();
  _codes.restart(); _values.restart();
  for (int i = 0; i < _codes.items(); i++)
  {
    TToken_string& row = s.row(i);
    row = _codes.get();
    row.add(_values.get());
    row.add(message(i));
  }
  //  s.force_update();
}

void TForm_list::read_from(const TMask& m)
{
  TForm_string::read_from(m);

  TSheet_field& s = (TSheet_field&)m.field(F_ITEMS);

  _codes = _values = "";
  for (int i = 0; i < s.items(); i++)
  {
    TToken_string& row = s.row(i);
    _codes.add(row.get(0));
    _values.add(row.get());
    message(i) = row.get();
  }
}

void TForm_list::print_body(ostream& out) const
{
  TForm_string::print_body(out);

  TToken_string& cod = (TToken_string&)_codes; // Trick to skip const
  TToken_string& val = (TToken_string&)_values;

  int i = 0;
  TString c(cod.get(0));
  TString v(val.get(0));

  for (; c[0]; c = cod.get(), v = val.get(), i++)
  {
    out << " ITEM \"" << c;
    if (v.not_empty()) out << '|' << v;
    out << '"';

    const char* m = ((TForm_list*)this)->message(i);
    if (*m) out << " MESSAGE " << m;

    out << endl;
  }
}


bool TForm_list::update()
{
  bool ok = TRUE;

  if (!read()) return ok;

  const TString& val =get();
  int pos = _codes.get_pos(val);
  if (pos < 0)
  {
    TString def= _codes.get(0);
    def.trim();
    if (val == def) pos = 0; // Test default (first & empty) value
    else
    {
      ok = yesno_box("Il campo '%s' non puo' valere '%s': continuare ugualmente",
                     (const char*)key(), (const char*)val);
      set(_codes.get(pos = 0));
    }
  }
  if (ok)
  {
    do_message(pos);

    if (!hidden())
    {
      const char* c = _values.get(pos);
      if (c == NULL) c = val;
      if (c) string_at(x(), _y, c);
    }
  }

  return ok;
}

///////////////////////////////////////////////////////////
// TForm_group
///////////////////////////////////////////////////////////

class TForm_group : public TForm_item
{
protected:
  virtual const char* class_name() const { return "GRUPPO"; }
  virtual bool update() { return TRUE; }

public:
  TForm_group(TPrint_section* section) : TForm_item(section) {};
  virtual ~TForm_group() {}
};

///////////////////////////////////////////////////////////
// TGraphic_section
///////////////////////////////////////////////////////////

class TGraphic_section : public TPrint_section
{
  TString _back;

protected:
  TForm_item* parse_item(const TString& s);
  bool update();

public:
  void append(const char* s) { _back << s; }

  TGraphic_section(TForm* f, pagetype pt) : TPrint_section(f, 'G', pt) {}
  virtual ~TGraphic_section() {}
};

class TForm_picture : public TForm_item
{
protected:
  virtual const char* class_name() const { return "FIGURA"; }
  virtual bool update();

public:
  TForm_picture(TGraphic_section* section) : TForm_item(section) {}
  virtual ~TForm_picture() {}
};

class TForm_line : public TForm_item
{
protected:
  virtual const char* class_name() const { return "LINEA"; }
  virtual bool update();

public:
  TForm_line(TGraphic_section* section) : TForm_item(section) {}
  virtual ~TForm_line() {}
};

class TForm_box : public TForm_item
{
protected:
  virtual const char* class_name() const { return "BOX"; }
  virtual bool update();

public:
  TForm_box(TGraphic_section* section) : TForm_item(section) {}
  virtual ~TForm_box() {}
};

bool TForm_picture::update()
{
  const bool ok = _prompt.not_empty();
  if (ok)
  {
    TString80 i;
    i << "i{" << _prompt << ',' << _x << ',' << _y << ','
      << (_x+width()-1) << ',' << (_y+height()-1) << '}';
    ((TGraphic_section&)section()).append(i);
  }
  return ok;
}

bool TForm_line::update()
{
  int spessore = 1;
  char codice = 'l';
  for (int j = _prompt.len()-1; j >= 0; j--)
  {
    switch (_prompt[j])
    {
    case 'B':
    case 'b':
      spessore = 3; break;
    case 'R':
    case 'r':
      codice = 'r'; break;
    default:
      break;
    }
  }

  TString80 i;
  i << 'W' << spessore << codice
    << '{' << _x << ',' << _y << ','
    << (_x+width()-1) << ',' << (_y+height()-1) << '}';

  ((TGraphic_section&)section()).append(i);
  return TRUE;
}

bool TForm_box::update()
{
  TString80 i;
  const int w = _prompt[0] == '@' ? 3 : 1;
  i << 'W' << w << "b{" << _x << ',' << _y << ','
    << (_x+width()-1) << ',' << (_y+height()-1) << '}';

  ((TGraphic_section&)section()).append(i);
  return TRUE;
}


TForm_item* TGraphic_section::parse_item(const TString& s)
{
  if (s == "FI")
    return new TForm_picture(this);
  else if (s == "LI")
    return new TForm_line(this);
  else if (s == "BO")
    return new TForm_box(this);

  error_box("Campo di stampa non ammesso per lo sfondo: %s", (const char*)s);
  return NULL;
}

bool TGraphic_section::update()
{
  _back.cut(0);
  const bool ok = TPrint_section::update();
  
  int index;
  switch(page_type())
  {        
  case even_page : index = 1; break;
  case first_page: index = 2; break;
  case last_page : index = 3; break;
  default        : index = 0; break;
  }
  printer().setbackground(_back, index);
  return ok;
}

///////////////////////////////////////////////////////////
// TPrint_section
///////////////////////////////////////////////////////////

HIDDEN bool font_handler(TMask_field& f, KEY key)
{
  if (key == K_SPACE)
  {
    main_app().begin_wait();

    const char* family = f.get();
    const int MAXSIZES = 16;
    long sizes[MAXSIZES];
    BOOLEAN scalable;
    const int num_sizes = (int)xvt_fmap_get_family_sizes(printer().get_printrcd(),
                                                         (char*)family, sizes, &scalable, MAXSIZES);

    TToken_string pn1(80), pn2(80);
    if (scalable)
    {
      for (int i = 4; i <= 32; i++)
      {
        pn1.add(i);
        pn2.add(i);
      }
    }
    else
    {
      if (num_sizes > 0)
      {
        for (int i = 0; i < num_sizes; i++)
          pn1.add(sizes[i]);
      }
      else pn1.add(printer().get_char_size());
      pn2 = pn1;
    }
    TList_field& lst = (TList_field&)f.mask().field(F_SIZE);
    lst.replace_items(pn1, pn2);
    lst.set(format("%d",printer().get_char_size()));

    main_app().end_wait();
  }
  return TRUE;
}

TMask* TPrint_section::_msk = NULL;


word TPrint_section::height() const
{ 
  word h = _height;
  if (int(h) < 0)                  // Can't write h < 0!
  {
    if (section_type() == 'F')
    {  
      h += printer().formlen();
    }
    else
    {
      h = items();
      if (h == 0)
        h = 0xFFFF - _height + 1;    // Same as abs(_height)
    }    
  }  
  return h;  
}


TExpression & TPrint_section::eval_expr(TExpression & expr,int defaultfile_id)
{ 
  for (int j = 0; j < expr.numvar(); j++) 
  { // scansione delle variabili dell'espressione di rvalue
    TString var= expr.varname(j);
    if (var[0]=='#') 
    { // riferimento ad un campo del form
      var.ltrim(1);
      TForm_item &fi= find_field(atoi(var));
      expr.setvar(j, fi.get()); // il valore corrente del campo viene settato nell'espressione
    } 
    else 
    { // riferimento ad un campo di file
      TFieldref fr = TFieldref(var, 0);
      int fileno=fr.file();
      // choose logical file number 
      fileno= (fileno==0) ? (defaultfile_id>0 ? defaultfile_id : 0): fileno;
      expr.setvar(j, fr.read( form().relation()->lfile(fileno).curr() ) ); // il valore corrente del campo viene settato nell'espressione
    }
  }
  return expr;
}

TPrint_section::TPrint_section(TForm* f, char st, pagetype pt, TForm_subsection* father)
: _height(0), _form(f), _sec_type(st), _page_type(pt), _dirty(FALSE),
  _upsection(father), _repeat_count(0), _ofspc(0), _ofsvr(0), _nfld(0), _temp(0),
  _columnwise(FALSE)
{
  reset_tabs();
}

TPrint_section::~TPrint_section()
{
  if (_msk)
  {
    delete _msk;
    _msk = NULL;
  }
}

void  TPrint_section::change_field(int n, TForm_item* f)
{
  _item.add(f,n);
}

void  TPrint_section::insert_field(int n, TForm_item* f)
{
  _item.insert(f,n);
}

inline void  TPrint_section::add_field(TForm_item* f)
{
  _item.add(f);
}

inline int TPrint_section::subsections()const 
{
  return _subsections.items();
}

inline TForm_subsection* TPrint_section::subsection(int n) const 
{
  return (TForm_subsection* )_subsections.objptr(n);
}

inline void  TPrint_section::add_subsection(TForm_subsection* s)
{
  _subsections.add(s);
}

const TPrint_section& TPrint_section::copy(const TPrint_section& ps)
{
  _msk = ps._msk;  _height = ps._height;  _ofspc = ps._ofspc;
  _ofsvr = ps._ofsvr;  _nfld  = ps._nfld;  _dirty = ps._dirty;
  _columnwise = ps._columnwise;  _temp = ps._temp; _form = ps._form;
  _sec_type = ps._sec_type; _page_type = ps._page_type; _upsection = ps._upsection;
  _item = ps._item; _repeat_count = ps._repeat_count;
  _subsections=ps._subsections;
  for (int i = 0; i < MAXCOLUMNS; i++) _tab[i] = ps._tab[i];
  return ps;
}

TPrintrow& TPrint_section::row(int num)
{
  TPrintrow* pr = (TPrintrow*)objptr(num);
  if (pr == NULL)
  {
    pr = new TPrintrow;
    add(pr, num);
  }
  return *pr;
}

void TPrint_section::reset_tabs()
{
  for (int i = 0; i < MAXCOLUMNS; i++)
    _tab[i] = -1;
}

int TPrint_section::tab(int col)
{
  int ret = -1;
  if (_columnwise)
  {
    if (_tab[0] == -1 && fields())
    {
      _tab[0] = 2;
      {
        int extraoff=0;
        // compute column offset
        _nfld = 0;
        short maxcolreached = 0,mincolreached = MAXCOLUMNS+1;
        for (word i = 0; i < fields(); i++)
        {
          if (!field(i).is_section() && field(i).shown())
          {
            const int curr_col=field(i)._x;
            CHECKD (curr_col >=0 && curr_col < MAXCOLUMNS, "Colonna ammessa e non concessa: ", field(i)._x);
            _tab[curr_col] = field(i)._width + 1; // one is for separation
            if (curr_col < mincolreached) mincolreached = curr_col;
            if (curr_col > maxcolreached) maxcolreached = curr_col;
            _nfld++;
          }
        }
        // cumulate offsets
        
        if (_upsection)
        {
          if (mincolreached<=MAXCOLUMNS && mincolreached>0)
          {
            // print section of a subsection: extra offset
            extraoff=_upsection->section().tab(mincolreached-1);
            for (i = 1 ; i < (word)mincolreached; i++)
            {
              _tab[i]=_upsection->section()._tab[i];
            }
            if (extraoff== _tab[mincolreached-1])
              extraoff=0;
            else
              extraoff-=_upsection->section().tab(0);
          } 
        } 
          
        int last = 0;
        for (i = mincolreached ; i <= (word)maxcolreached; i++)
        {
          if (_tab[i - 1] != -1)
            last = i - 1;
          if (_tab[i] != -1)
            _tab[i] += _tab[last]+extraoff;
        }
      }
    }
    // se manca la colonna, vai a prendere quella immediatamente prima
    while (_tab[col] == -1 && col > 0)
      col--;
    ret = _tab[col];
  }
  return ret;
}


word TPrint_section::subs_height() const 
{ 
  TForm_subsection *fss;
  word h=0;
  if (subsections())
  {
    fss=subsection(0);
    h=subsection(0)->height();
    h+=subsection(0)->subsection().subs_height(); // ricorsione...
  }
  return h; 
}


void TPrint_section::offset(int& x, int& y)
{
  if (x >= 0)
  {
    if (_columnwise) x = tab(x-1) + _ofspc;
    x += form().offset_x();
  }
  if (y >= 0) 
    y += form().offset_y() + (_height * _repeat_count);
}


TForm_item* TPrint_section::parse_item(const TString& s)
{
  if (s == "ST")
    return new TForm_string(this);
  if (s == "NU")
    return new TForm_number(this);
  if (s == "DA")
    return new TForm_date(this);
  if (s == "LI")
    return new TForm_list(this);
  if (s == "GR")
    return new TForm_group(this);
  if (s == "SE")
    return new TForm_subsection(this);

  yesnofatal_box("Campo di stampa non ammesso per la sezione di stampa: %s", (const char*)s);
  return NULL;
}


TForm_item* TPrint_section::parse_item(TScanner& scanner)
{
  return parse_item(scanner.key());
}

bool TPrint_section::parse_head(TScanner& scanner)
{
  _height = scanner.integer();
  scanner.integer();scanner.integer(); // Eat offset X and Y of Print_section if present

  if (scanner.popkey() == "CO")        // COLUMNWISE attribute
    _columnwise = TRUE;
  else if (scanner.key() == "NO")        // NORMAL (no COLUMNWISE) attribute
    _columnwise = FALSE;
  else 
  {
    if (section_above())
      _columnwise = section_above()->_columnwise;
    else
      _columnwise = FALSE;
    scanner.push();
  }
  return TRUE;
}

bool TPrint_section::parse_body(TScanner& scanner)
{
  while (scanner.popkey() != "EN")
  {
    TForm_item *fi = parse_item(scanner);
    if (fi == NULL) return FALSE;

    if (fi->parse(scanner)) 
    {
      _item.add(fi);
    } else
      return FALSE;
  }
  return TRUE;
}

bool TPrint_section::parse(TScanner& scanner)
{
  if (parse_head(scanner));
    return parse_body(scanner);
  return FALSE;
}


// Azzera tutte le righe della sezione di stampa
void TPrint_section::reset()
{          
  for (int i = last(); i >= 0; i--)
  {
    if (_height > 0xF000)
      destroy(i);
    else
      row(i).reset();
  }  
}

// Aggiorna tutti i campi
bool TPrint_section::update()
{
  bool ok = TRUE;

  reset();
  for (word i = 0; i < fields(); i++)
  {
    if (!field(i).is_section())
    {
      const bool esito = field(i).update();
      if (!esito) ok = FALSE;
    }
  }
  return ok;
}

// @doc EXTERNAL

// @mfunc Legge dal record <p rec> altezza e offset prima colonna della sezione
//
// @rdesc Ritorna se i valori letti hanno modificato quelli attuali
bool TPrint_section::read_from(
     const TRectype& prof)  // @parm Record dal quela leggere i valori

// @comm Nel caso il record non sia LF_RFORM viene dato un messaggio di <f CHECK>.
{
  CHECK(prof.num() == LF_RFORM, "Il record deve essere del file LF_RFORM");

  bool changed = FALSE;
  const word h = (word)prof.get_int("HGT");
  const word l = (word)prof.get_int("LEN");
  const word y = (word)prof.get_int("Y");
  if (_height != h)
  {
    _height = h;
    changed = TRUE;
  }
  if (_ofspc != l)
  {
    _ofspc = l;
    changed = TRUE;
  }
  if (_ofsvr != y)
  {
    _ofsvr = y;
    changed = TRUE;
  }
  return changed;
}

void TPrint_section::print_on(TRectype& prof)
{
  CHECK(prof.num() == LF_RFORM, "Il record deve essere del file LF_RFORM");
  prof.put("ID", 0);
  prof.put("X", 0);
  prof.put("Y", _ofsvr);
  prof.put("LEN", _ofspc);
  prof.put("HGT", _height);
}

typedef struct {
  char name_1[80];  // Fontname old
  char name_2[80];  // Fontname new
  int  size_1;      // size (height) of old font
  int  size_2;      // size (height) of new font
  real ratio;       // ratio (width_old_font/width_new_font)
} s_data;

BOOLEAN XVT_CALLCONV1 wpr (long data)
{
  s_data* st =(s_data*)data;
  WINDOW prwin = xvt_print_create_win(printer().get_printrcd(),"");
  long width_old,width_new;
  TString spc(100);
  spc.fill('m');
  xvt_set_font(prwin,st->name_1, XVT_FS_NONE, st->size_1);
  width_old = xvt_dwin_get_text_width(prwin,(char*)(const char*)spc, 100);
  xvt_set_font(prwin,st->name_2, XVT_FS_NONE, st->size_2);
  width_new = xvt_dwin_get_text_width(prwin,(char*)(const char*)spc, 100);
  st->ratio = (double)width_old / (double)width_new;
  xvt_vobj_destroy(prwin);

  return FALSE;
}

bool TPrint_section::special_field_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE && _special_mask != NULL)
     _special_mask->run();
  return TRUE;
}

bool TPrint_section::repos_fields(const char* name, int size)
{
  TPrint_section& ps = ::section();
  bool rt = FALSE;
  if (ps.form().fontname() != name ||
    ps.form().fontsize() != size)
  {
    if (!ps.form().dirty()) ps.form().set_dirty();
    ps.set_dirty();
    if (yesno_box("E' stato cambiato il font o la dimensione del carattere.\nSi desidera aggiornare le coordinate dei campi?"))
    {
      rt = TRUE;
      s_data  prm;
      prm.size_1=ps.form().fontsize();
      strcpy(prm.name_1,ps.form().fontname());
      prm.size_2=size;
      strcpy(prm.name_2,name);
      prm.ratio = 1.0;
      // Next 3 lines may be changed
      xvt_print_open();
      xvt_print_start_thread (wpr, (long)&prm);
      xvt_print_close();

      if (ps.form().edit_level() > 1)
      {
        TMask rm("Rapporto tra i caratteri", 1, 30, 4);
        rm.add_number(DLG_USER, 0, "Rapporto ", 1, 1, 8, "", 4);
        rm.add_button(DLG_OK, 0, "Conferma", -12, -1, 10, 2);
        rm.add_button(DLG_CANCEL, 0, "Annulla", -22, -1, 10, 2);
        real ratio = prm.ratio;
        rm.set(DLG_USER, ratio);
        if (rm.run() == K_ENTER)
        {
          ratio = rm.get_real(DLG_USER);
          prm.ratio = ratio;
        }
      }

      const char sechar[4] = { 'B', 'F', 'G',  'H' };
      for (int sn = 0; sn < 4 ; sn++)
      {
        const char sc = sechar[sn];
        for (pagetype pt = odd_page; pt <= last_page; pt = pagetype(pt+1))
        {
          TPrint_section* sec = ps.form().exist(sc, pt);
          if (sec != NULL && !sec->columnwise())
          {
            sec->set_dirty();
            for (word i = 0; i < sec->fields() ; i++)
            {
              TForm_item& fi = sec->field(i);
              short value =  fi.x();
              if (value > 0 && (prm.ratio != 1.0))
              {
                real x_pos;
                x_pos = value * prm.ratio;
                x_pos.round();
                fi.set_x((short)x_pos.integer());
                fi.set_dirty();
              }
            }
          }
        }
      }
    }
    ps.form().fontname() = name;
    ps.form().fontsize() = size;
    // Aggiorna lo spreadsheet
    TSheet_field& ms = (TSheet_field&)ps._msk->field(F_FIELDS);
    TToken_string tt(128);
    const word flds = ps.fields();
    for (word i = 0; i < flds; i++)
    {
      TForm_item& f = ps.field(i);
      ps.field(i).print_on_sheet_row(tt);
      ms.row(i) = tt;
    }
  }
  return rt;
}

// handlers for section editing
bool TPrint_section::detail_field_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {

    if (_cur_form && TString(_cur_form->section_mask()).left(2) == "ba")
    {
      TString80 name(_msk->get(F_FONT));
      int       size = _msk->get_int(F_SIZE);
      repos_fields(name, size);
    }

    bool consider_sheet_mask = FALSE;
    if (f.mask().is_running())  // Se la maschera di editing dello sheet e' running
      consider_sheet_mask = TRUE;     // allora deve tener conto anche di essa per l'I/O

    // to avoid kasinations with recursion
    TPrint_section& section = ::section();
    TSheet_field& ms = *f.mask().get_sheet();
    int field = ms.selected();
    TToken_string& tt = ms.row(field);
    TToken_string  tt_mask(tt);
    if (consider_sheet_mask)
    {
      TMask& msheet = f.mask();
      tt_mask.add(msheet.get(idt_id), idt_id - 101);
      tt_mask.add(msheet.get(dsc_id), dsc_id - 101);
      tt_mask.add(msheet.get(len_id), len_id - 101);
      tt_mask.add(msheet.get(prn_id), prn_id - 101);

      if (section.columnwise())
      {
        tt_mask.add(msheet.get(col_id), col_id - 101);
        tt_mask.add(msheet.get(spc_id), spc_id -101);
        tt_mask.add(msheet.get(int_id), int_id - 101);
        tt_mask.add(msheet.get(fnl_id), fnl_id -101);
        tt_mask.add(msheet.get(fnr_id), fnr_id -101);
      }
      else
      {
        tt_mask.add(msheet.get(xps_id), xps_id - 101);
        tt_mask.add(msheet.get(yps_id), yps_id - 101);
      }
    }

    TMask msk("ba2100f");

    msk.set_handler(F_DFORMAT,    dateformat_handler);
    msk.set_handler(F_DYEAR,      dateformat_handler);
    msk.set_handler(F_DMONTH,     dateformat_handler);
    msk.set_handler(F_DDAY,       dateformat_handler);
    msk.set_handler(F_DSEP,       dateformat_handler);

    msk.set_handler(F_FILE1,  but_file_handler);
    msk.set_handler(F_FIELD1, but_field_handler);
    msk.set_handler(F_FROM1,      from_handler);
    msk.set_handler(F_TO1,        to_handler);
    msk.set_handler(F_FILE2,  but_file_handler);
    msk.set_handler(F_FIELD2, but_field_handler);
    msk.set_handler(F_FROM2,      from_handler);
    msk.set_handler(F_TO2,        to_handler);
    msk.set_handler(F_PICTURE,fmt_handler);
    // TBI set_mode etc, vedi sotto

    // gna'
    TForm_item& fi = section.field(field);

    msk.enable(F_OPTIONS, fi.special_items() > 3);
    msk.enable(F_SPACES, section.columnwise());
    msk.enable(F_FINKL, section.columnwise());
    msk.enable(F_FINKR, section.columnwise());

    // build option mask
    if (fi.special_items() > 3)
    {
      msk.set_handler(F_OPTIONS, special_field_handler);

      CHECK(fi.special_items() < 18, "Quanti special! Non ho nessuna voglia di "
            " farti una maschera a piu' pagine. Ripensaci e riprova");

      _special_mask = new TMask("Variabili personalizzate", 1, 78, fi.special_items() + 3);
      _special_mask->add_button(DLG_OK, 0, "", -12, -1, 10, 2);
      _special_mask->add_button(DLG_CANCEL, 0, "", -22, -1, 10, 2);
      fi.specials().restart();

      for (int k = 0; k < fi.special_items(); k++)
      {
         THash_object*  ho = fi.specials().get_hashobj();
         TToken_string& tt = (TToken_string&)(ho->obj());
         TString type = tt.get(0);
         TString val  = tt.get(1);
         TToken_string des(tt.get(2),'/');
         TString prompt(des.get(0));

         if (type == "STRINGA")
         {
           _special_mask->add_string(101+k, 0, prompt, 1, k+1, des.get_int(1),"");
           _special_mask->set(101+k, val);
         }
         else if (type == "NUMERO")
         {
           _special_mask->add_number(101+k, 0, prompt, 1, k+1, des.get_int(1),"",des.get_int(2));
           _special_mask->set(101+k, val);
         }
         else if (type == "LISTA")
         {
           TToken_string codes(128);
           TToken_string value(128);

           for (int jj = 2; jj < des.items(); jj++)
           {
             CHECK (jj < (des.items() - 1), "AAARGH! 'Sta LISTA special e' fatta male");
             TString t1(des.get(jj++));
             TString t2(des.get(jj));
             codes.add(t1);
             value.add(t2);
           }

           _special_mask->add_list(101+k, 0, prompt, 1, k+1, des.get_int(1), "", codes, value);
           _special_mask->set(101+k, val);
         }
         else if (type == "BOOLEAN")
         {
           _special_mask->add_boolean(101+k, 0, prompt, 1, k+1);
           _special_mask->set(101+k, val);
         }
         else if (type == "DATA")
         {
           _special_mask->add_date(101+k, 0, prompt, 1, k+1);
           _special_mask->set(101+k, val);
         }
      }
    }
    if (consider_sheet_mask)
      fi.read_from(tt_mask);
    else
      fi.read_from(tt);
    fi.edit(msk);

    // check specials
    if (_special_mask != NULL)
    {
      if (_special_mask->last_key() == K_ENTER)
      {
        fi.specials().restart();

        for (int k = 0; k < fi.special_items(); k++)
        {
           THash_object*  ho = fi.specials().get_hashobj();
           TToken_string& tt = (TToken_string&)(ho->obj());
           TString val  = tt.get(1);
           TString nvl  = _special_mask->get(k + 101);
           if (nvl != val)
           {
             tt.add(nvl, 1);
             if (tt.items() == 3) tt.add("X");
             fi.set_dirty();
           }
         }
       }

       if (_special_mask != NULL)
       {
         delete _special_mask;
         _special_mask = NULL;
       }
    }

    if (!consider_sheet_mask)
      fi.print_on_sheet_row(tt);
    else
      {
        TMask& msheet = f.mask();
        fi.print_on_sheet_row(tt_mask);
        msheet.set(idt_id, tt_mask.get_int(idt_id - 101));
        msheet.set(dsc_id, tt_mask.get(dsc_id - 101));
        msheet.set(len_id, tt_mask.get_int(len_id - 101));
        msheet.set(prn_id, tt_mask.get(prn_id - 101));
        msheet.set(frm_id, fi.example());

        if (section.columnwise())
        {
          msheet.set(spc_id, tt_mask.get_int(spc_id - 101));
          msheet.set(col_id, tt_mask.get_int(col_id - 101));
          msheet.set(int_id, tt_mask.get(int_id - 101));
          msheet.set(fnl_id, tt_mask.get(fnl_id - 101));
          msheet.set(fnr_id, tt_mask.get(fnr_id - 101));
        }
        else
        {
          msheet.set(xps_id, tt_mask.get_int(xps_id - 101));
          msheet.set(yps_id, tt_mask.get_int(yps_id - 101));
        }
      }

    ms.force_update();

    _cur_sect = &section;
  }
  return TRUE;
}

bool TPrint_section::detail_field_notify (TSheet_field& s, int r, KEY k)
{
  TPrint_section& sec = ::section();
  if (k == K_DEL || k == K_INS)
  {
    if (::form().edit_level() <= 1)
      return FALSE;
    if (k == K_DEL)
    {
      // elimina campo
      sec.destroy_field(r);
      sec.set_dirty();
    }
  }
  else if (k == (K_INS + K_CTRL))
  {
    // new field: set defaults and create field
    TForm_string* f = new TForm_string(&sec);
    sec.insert_field(r, f);
    TToken_string& tt = s.row(r);
    f->print_on_sheet_row(tt);
    tt.add("Nuovo campo", sec.columnwise() ? int_id : dsc_id);
    if (sec.columnwise()) tt.add(r+1, col_id - 101);
    s.force_update();
    sec.set_dirty();
  }
  else if (k == K_ENTER)
  {
    // modify field
    TForm_item*   fld = &sec.field(r);
    TToken_string& tt = s.row(r);

    if (sec.columnwise() && ((word)tt.get_int(col_id - 101) <= 0 ||
                             (word)tt.get_int(col_id - 101) > sec.fields()))
    {
      warning_box("Numero di colonna non accettabile (deve essere da 1 a %u)", sec.fields());
      return FALSE;
    }
    // ??? type changed ???
    if (strcmp(tt.get(typ_id - 101), fld->class_name()) != 0)
    {
      // so'ccazzi: crea nuovo campo del tipo dato e
      // copia gli special (e il resto)
      TString typ(tt.get(typ_id - 101));
      TForm_item* fff = NULL;
      if (typ == "NUMERO")
        fff = new TForm_number(&sec);
      else if (typ == "STRINGA")
        fff = new TForm_string(&sec);
      else if (typ == "DATA")
        fff = new TForm_date(&sec);
      else if (typ == "LISTA")
        fff = new TForm_list(&sec);
      else if (typ == "GRUPPO")
        fff = new TForm_group(&sec);
      else if (typ == "LINEA")
        fff = new TForm_line((TGraphic_section*)&sec);
      else if (typ == "BOX")
        fff = new TForm_box((TGraphic_section*)&sec);
      else if (typ == "FIGURA")
        fff = new TForm_picture((TGraphic_section*)&sec);

      // copia SPECIALS
      TAssoc_array& aa = fld->specials();
      TAssoc_array& bb = fff->specials();
      THash_object* oo = NULL;
      aa.restart();

      for (int i = 0; i < aa.items(); i++)
      {
         oo = aa.get_hashobj();
         bb.add(oo->key(), oo->obj());
      }
      sec.change_field(r, fld = fff);
      fld->set_dirty();
    }
    // modifica valori
    fld->read_from(tt);
  }
  else
  if (k == K_SPACE)
  {
    if (_cur_form && TString(_cur_form->section_mask()).left(2) == "ba")
    {
      TString80 name(s.mask().get(F_FONT));
      int       size = s.mask().get_int(F_SIZE);
      if (repos_fields(name,size))
        s.force_update();
    }
  }

  return TRUE;
}

// @doc EXTERNAL

// @mfunc Esegue l'edit della sezione di stampa
//
// @rdesc Ritorna TRUE se e' stata modificata effettivamente
bool TPrint_section::edit(
     const char* title)  // @parm Titolo della maschera di edit
{
  const bool is_ba_editor = TString(_form->section_mask()).left(2) == "ba";
  bool nstd_dirty = FALSE;
  bool  font_found = FALSE;

  _cur_form = _form;
  _cur_sect = this;

  if (!_upsection)
  {
    TMask m(_form->section_mask());
    _msk = &m;
    m.set_caption(title);
    m.set(F_HEIGHT, _height);
    m.set(F_OFSPC, _ofspc);
    m.set(F_OFSVR, _ofsvr);
    if (is_ba_editor)
    {
      m.set(F_X, form().offset_x());
      m.set(F_Y, form().offset_y());
      m.set(F_CTP, format("%c",form().char_to_pos()));
      m.set(F_IPX, form().ipx());
      m.set(F_IPY, form().ipy());
      m.set(F_FPX, form().fpx());
      m.set(F_FLEN, printer().formlen());
    }
    else
       _form->pre_edit_checks(m,_cur_sect);
    TSheet_field& ms = (TSheet_field&)m.field(F_FIELDS);

    m.hide(F_OFSVR);//Offset verticale, per _columnwise. Non ancora usato.
    if (_columnwise)
    {
      m.disable(F_HEIGHT);
      ms.delete_column(xps_id); ms.sheet_mask().hide(xps_id);
      ms.delete_column(yps_id); ms.sheet_mask().hide(yps_id);
    }
    else
    {
      m.hide(F_OFSPC);
      ms.delete_column(int_id); ms.sheet_mask().hide(int_id);
      ms.delete_column(col_id); ms.sheet_mask().hide(col_id);
      ms.delete_column(spc_id); ms.sheet_mask().hide(spc_id);
      ms.delete_column(fnl_id); ms.sheet_mask().hide(fnl_id);
      ms.delete_column(fnr_id); ms.sheet_mask().hide(fnr_id);
    }

    ms.enable_column(frm_id - 101, FALSE);

    if (form().edit_level() <= 1)
    {
      ms.enable_column(idt_id - 101, FALSE);
      ms.enable_column(typ_id - 101, FALSE);
    }

    // handlers
    ms.set_notify(detail_field_notify);
    ms.sheet_mask().set_handler(100, detail_field_handler);
    if (_form->edit_level()<=1) ms.sheet_mask().disable(DLG_DELREC);

    TToken_string tt(128);
    const word flds = fields();

    // fill sheet
    for (word i = 0; i < flds; i++)
    {
      TForm_item& f = field(i);
      field(i).print_on_sheet_row(tt);
      // TBI colorare se specials (e vedi se colorare solo se non standard)
      ms.row(-1) = tt;
    }

    if (is_ba_editor)
    {
      const int MAX_FAMILIES = 128;
      char* family[MAX_FAMILIES];
      const int num_families = (int)xvt_fmap_get_families(printer().get_printrcd(), family, MAX_FAMILIES);
      TToken_string pn1(256), pn2(256);

      for (int i = 0; i < num_families; i++)
      {
        pn1.add(family[i]);
        pn2.add(family[i]);
        if (!font_found)
          if (form().fontname() == family[i]) font_found = TRUE;
        xvt_mem_free(family[i]);
      }
      TList_field& lst = (TList_field&)m.field(F_FONT);
      if (!font_found)
      {
        warning_box("Il font %s non esiste per la stampante di default.",(const char*) form().fontname());
        pn1.add(form().fontname());
        pn2.add(form().fontname());
      }
      lst.replace_items(pn1, pn2);
      lst.set(form().fontname());
      printer().set_char_size(form().fontsize());
      m.set_handler(F_FONT,font_handler);
    }

    if(_columnwise)
    {
      bool ok = FALSE;
      while (m.run() != K_ESC)
      {
        TSheet_field& ms = (TSheet_field&)m.field(F_FIELDS);
        const int items = ms.items();
        for (int i=0; i<items && !ok; i++)
        {
          TToken_string& tt = ms.row(i);
          ok = tt.get_char(prn_id - 101)==' ';
        }
        if (i==items && !ok) error_box("Selezionare almeno una colonna stampabile.");
        if (ok) break;
      }
      _msk = NULL;
      if (!ok) return FALSE;
    }
    else
      if (m.run() == K_ESC)
      {
        // Se premo Annulla ed il form e' nuovo lo devo cancellare.
        if (form()._isnew)
        {
          TLocalisamfile frm(LF_FORM);
          frm.zero();
          frm.put("TIPOPROF",form().name());
          frm.put("CODPROF",form().code());
          frm.remove();
        }
        _msk = NULL;
        return FALSE;
      }

    bool dirty = m.dirty() != 0;

    if (dirty)
    {
      // Controlli da fare sempre a prescindere dal form editor
      if (_height != (word)m.get_int(F_HEIGHT) )
      {
        _height = m.get_int(F_HEIGHT);
        _dirty=TRUE;
      }
      if (_ofspc != (word)m.get_int(F_OFSPC) )
      {
        _ofspc = m.get_int(F_OFSPC);
        _dirty=TRUE;
      }
      if (_ofsvr != (word)m.get_int(F_OFSVR) )
      {
        _ofsvr = m.get_int(F_OFSVR);
        _dirty=TRUE;
      }
      if (is_ba_editor)
      { // Controlli solo se il form editor e' quello base
        if (m.get_int(F_X) != form().offset_x() || m.get_int(F_Y) != form().offset_y())
        {
          form().offset_x() = m.get_int(F_X);
          form().offset_y() = m.get_int(F_Y);
          form().set_dirty();
          _dirty = TRUE;
        }
        if (m.get(F_CTP)[0] != form().char_to_pos() ||
            m.get_int(F_IPX) != form().ipx() ||
            m.get_int(F_IPY) != form().ipy() ||
            m.get_int(F_IPX) != form().fpx())
        {
          form().char_to_pos() = m.get(F_CTP)[0];
          form().ipx() = m.get_int(F_IPX);
          form().ipy() = m.get_int(F_IPY);
          form().fpx() = m.get_int(F_FPX);
          _dirty = TRUE;
        }

        TString80 name(m.get(F_FONT));
        int       size = m.get_int(F_SIZE);
        repos_fields(name,size);
      } else  // controlli se l'editor non e' quello base
          nstd_dirty = _form->post_edit_checks(m,_cur_sect);
    }
    _msk = NULL;
  }

  if (is_ba_editor)
    if (form()._isnew || (_dirty && yesno_box("Dati generali modificati. Salvare?")))
    {
      TLocalisamfile frm(LF_FORM);
      frm.zero();
      frm.put("TIPOPROF",form().name());
      frm.put("CODPROF",form().code());
      if (frm.read(_isequal,_lock) == NOERR)
      {
          frm.put("OFFY",form().offset_y());
          frm.put("OFFX",form().offset_x());
          frm.put("FONTNAME",form().fontname());
          frm.put("FONTSIZE",form().fontsize());
          frm.put("CTP",form().char_to_pos());
          frm.put("IPX", form().ipx());
          frm.put("IPY", form().ipy());
          frm.put("FPX", form().fpx());
          frm.rewrite();
          _dirty = FALSE;
      }
    }

  if (!_dirty)
    for (word j = 0; j < fields(); j++)
      _dirty |= field(j).dirty();

  set_dirty(_dirty);
  return (_dirty || nstd_dirty);
}

void TPrint_section::print_on(ostream& out) const
{
  out << ' ';
  switch (page_type())
  {
  case even_page:
    out << "EVEN"; break;
  case first_page:
    out << "FIRST"; break;
  case last_page:
    out << "LAST"; break;
  default:
    out << "ODD"; break;
  }
  out << ' ' << _height;
  if (_columnwise) out << " COLUMNWISE";
  out << endl << endl;
  for(word i = 0; i < fields(); i++)
    if (!field(i).temp()) out << field(i);
}

TForm_item& TPrint_section::find_field(short id) const
{
  TForm_item * f = find_field_everywhere(id,this);
  if (f) return *f;
  yesnofatal_box("Can't find item with id %d", id);
  return field(0);
}

// Ricerca i campi verso l'alto:
//   prima la sezione attuale, poi le sezioni sopra
TForm_item *TPrint_section::find_field_everywhere(short id,const TPrint_section *starting_section) const
{
  TForm_item * f ;
  for(word i = 0; i < fields(); i++)
  {
    f = &field(i);
    if (f->id() == id) return f;
  }
  // look into the section above ("father" )
  if (section_above() && section_above()!=starting_section) {
    f=section_above()->find_field_everywhere(id,this);
    if (f)
      return f;
  }   
  // look into sections beside ("brothers" )
  for (int j = 0; j < subsections(); j++) 
  {
    const TPrint_section &bs=subsection(j)->subsection();
    if (&bs!=starting_section) {
      f=subsection(j)->subsection().find_field_everywhere(id,this);
      if (f)
        return f;
      }
  }
  return NULL;
}

TForm_item& TPrint_section::find_field(const char *sec_name) const
{
  TForm_item * f = find_field_everywhere(sec_name,this);
  if (f) return *f;
  yesnofatal_box("Can't find item with id %s", sec_name);
  return field(0);
}

// Ricerca i campi verso il basso:
//   prima la sezione attuale, poi le sottosezioni
TForm_item *TPrint_section::find_field_everywhere(const char *sec_name,const TPrint_section *starting_section) const
{
  // search here
  for (int j = 0; j < subsections(); j++) {
    const char *n=subsection(j)->name();
    if (strcmp(n,sec_name)==0)
      return subsection(j);
  }
  // search down
  for (j = 0; j < subsections(); j++) {
    TForm_item *it=subsection(j)->subsection().find_field_everywhere(sec_name,starting_section);
    if (it)
      return it;
  }
  return NULL;
}

///////////////////////////////////////////////////////////
// TForm
///////////////////////////////////////////////////////////

// classe per le informazioni sugli skip dei file del cursore del form
class TForm_skip_info : public TObject
{
protected:
  virtual TObject * dup() const;  
public :
  bool match_result;
  int match_number;
  ~TForm_skip_info(){};
  TForm_skip_info();
};

TObject * TForm_skip_info ::dup() const
{
  TForm_skip_info *p=new TForm_skip_info;
  p->match_result=match_result;
  p->match_number=match_number;
  return p;
}

TForm_skip_info::TForm_skip_info()
{
  match_result=FALSE;
  match_number=0;
}

TForm_editor& TForm::editor() const
{ return (TForm_editor&)main_app(); }

// 
bool TForm::parse_use(TScanner& scanner)
{
  const int logicnum = scanner.integer();
  const char* tab = NULL;

  if (logicnum > 0)
    _relation = new TRelation(logicnum);
  else
  {
    tab = scanner.pop();
    _relation = new TRelation(tab);
  }

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

  if (scanner.popkey() == "BY" || scanner.key() == "FI")  // file sorted or filtered
  {
    if (scanner.key() == "BY" ) { // "sort BY": user-defined sort
      TToken_string ordexpr(parse_sortexpr(scanner));
      _cursor = new TSorted_cursor(_relation, ordexpr,"", key);
    } else {
      TToken_string filter(parse_filter(scanner));
      _cursor = new TCursor(_relation,filter, key);
    }
  } else {
    scanner.push();
    _cursor = new TCursor(_relation,"", key);
  }

  if (scanner.popkey() == "FI")  // "FIlter" : sorted defined by the user 
  {
    TToken_string filter(parse_filter(scanner));
    _cursor->setfilter(filter);
  } else {
    scanner.push();
  }

  return TRUE;
}


bool TForm::parse_join(TScanner& scanner)
{
  TString16 j(scanner.pop());           // File or table

  int to = 0;
  if (scanner.popkey() == "TO")         // TO keyword
  {
    const char* n = scanner.pop();
    to = name2log(n);
  }
  else scanner.push();
  return parse_join_param(scanner,_relation,j,to);
}

bool TForm::parse_join_param(TScanner& scanner,TRelation * rel, TString16 j, int to )
{
  int key = 1;
  if (scanner.popkey() == "KE")
    key = scanner.integer();
  else scanner.push();

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

  TToken_string exp(80);
  if (scanner.pop() == "INTO")
  {
    exp=parse_filter(scanner);
  }
  if (exp.empty())
    yesnofatal_box("JOIN senza espressioni INTO");

  if (isdigit(j[0]))
    rel->add(atoi(j), exp, key, to, alias);   // join file
  else
    rel->add(j, exp, key, to, alias);         // join table
  return TRUE;
}

bool TForm::parse_sortedjoin(TScanner& scanner)
{
  TToken_string filter,sortexp;
  TString16 j(scanner.pop());           // File or table
  if (scanner.popkey() == "BY" )
  {
    sortexp=parse_sortexpr(scanner);
  } else scanner.push();
  if (scanner.popkey() == "FI" ) {
    filter=parse_filter(scanner);
  } else scanner.push();
  if (filter.empty() && sortexp.empty())
    yesnofatal_box("Sort senza espressioni di ordinamento o filtri");
  TRelation * sortrel=new TRelation(atoi(j)); 
  while (ok && scanner.popkey() != "JO" )
  {
    if (scanner.key() == "US")         // USING keyword
    {
      TString16 subj(scanner.pop());           // File or table
      parse_join_param(scanner,sortrel,subj,atoi(j));
    } else
      yesnofatal_box("Token non riconosciuto in SORT:%s",scanner.key());
  } 

  int to = 0;
  if (scanner.popkey() == "TO")         // TO keyword
  {
    const char* n = scanner.pop();
    to = name2log(n);
  }
  else scanner.push();

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

  int 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();
    }
  }
  if (exp.empty())
    yesnofatal_box("JOIN senza espressioni INTO");
  scanner.push();

  TSortedfile *sf= new TSortedfile(atoi(j),sortrel,sortexp,filter,key);

  _relation->add((TLocalisamfile *)sf, exp, key, to, alias,FALSE);         // join table
  return TRUE;
}

TToken_string TForm::parse_filter(TScanner& scanner)
{
  TToken_string filter;
  const char* r = scanner.pop();
  while (strchr(r, '=') != NULL)
  {
    filter.add(r);
    r = scanner.pop();
  }
  scanner.push();
  return filter;
}

TToken_string TForm::parse_sortexpr(TScanner& scanner)
{
  TToken_string sortexp;
  TToken_string se(scanner.line(),' ');
  const char * c;
  while (c=se.get()) 
    if (*c!='\0')
      sortexp.add(c);
  return sortexp;
}

TRelation_description& TForm::rel_desc() const
{
  CHECK(_rel_desc, "No relation description");
  return *_rel_desc;
}

bool TForm::parse_description(TScanner& scanner)
{
  if (edit_level() > 0)
  {
    CHECK(_rel_desc == NULL, "Can't parse descriptions two times");
    _rel_desc = new TRelation_description(*_relation);
  }

  bool ok = scanner.popkey() == "DE";
  if (ok)
  {
    if (edit_level() > 0)
    {
      scanner.popkey(); // eat BEGIN
      TFieldref fld;
      while (scanner.pop() != "END")
      {
        fld = scanner.token();
        _rel_desc->set_cur_file(fld.file());
        if (fld.name() == "*")
          _rel_desc->file_desc(scanner.string());
        else
          _rel_desc->set_field_description(fld.name(), scanner.string());
      }
    }
    else
    {
      while (scanner.line() != "END");
      ok = FALSE;
    }
  }
  else scanner.push();

  return ok;
}

void TForm::print_description(ostream& out) const
{
  out << "DESCRIPTION\nBEGIN\n";
  out << rel_desc();
  out << "END\n" << endl;
}

bool TForm::parse_general(TScanner &scanner)
{
  bool ok = scanner.popkey() == "GE";
  if (ok)
  {
    while (scanner.pop() != "END")
    {
      if (scanner.key() == "OF")   // Offsets
      {
        _x = scanner.integer();
        _y = scanner.integer();
      }
      if (scanner.key() == "FO")   // Font name
        _fontname = scanner.string();
      if (scanner.key() == "SI")   // Font size
        _fontsize = scanner.integer();
      if (scanner.key() == "CA")   // Carattere di posizionamento
        _char_to_pos = scanner.string()[0];
      if (scanner.key() == "IN")   // Riga e colonna del posizionamento iniziale
      {
        _ipx = scanner.integer();
        _ipy = scanner.integer();
      }
      if (scanner.key() == "FI")   // Riga e colonna del posizionamento finale
        _fpx = scanner.integer();

      if (scanner.key() == "GR")   // Carattere di fincatura
        set_fincatura(scanner.string());

      if (scanner.key() == "PA")   // Numero di pagine fisso
        _npages=scanner.integer();

      extended_parse_general(scanner); // Parse non-standard parameters
    }
  } else scanner.push();

  return (ok);
}

void TForm::print_general(ostream& out) const
{
  out << "GENERAL\nBEGIN\n";
  out << "  OFFSET " << _x << " " << _y << "\n";
  out << "  FONT " << "\"" << _fontname << "\"\n";
  out << "  SIZE " << _fontsize << "\n" ;
  if (_char_to_pos != '\0')
  {
    out << "  CARATTERE \"" << _char_to_pos << "\"\n" ;
    out << "  INIZIO_POS " << _ipx << " " << _ipy << "\n";
    out << "  FINE_POS " << _fpx << "\n";
  }
  out << "  GRID \"" << _fink << "\"\n";
  out << "END\n" << endl;
}

// @doc EXTERNAL

// @mfunc Controlla se esiste una sezione di stampa
//
// @rdesc Ritorna la <c TPrint_section> trovata o creata
TPrint_section* TForm::exist(
      char s,   // @parm Indica in quale parte deve cercare:
        //
        // @flag F | Tra i footers
        // @flag G | Tra gli sfondi
        // @flag H | Tra gli headers
        // @flag B | Tra i bodies (default)
    pagetype t,   // @parm Tipo di pagina (vedi <t pagetype>)
    bool create)  // @parm Indica se creare la sezione nel caso non esista
{
  TArray* a;
  switch (toupper(s))
  {
  case 'F':
    a = &_foot; break;
  case 'G':
    a = &_back; break;
  case 'H':
    a = &_head; break;
  default:
    a = &_body; break;
  }

  TPrint_section* sec = (TPrint_section*)a->objptr(t);
  if (sec == NULL && create)
  {
    sec = (s == 'G') ? new TGraphic_section(this, t) : new TPrint_section(this, s, t);
    a->add(sec, t);
  }

  return sec;
}

TForm_item& TForm::find_field(char s, pagetype t, short id) const
{
  const TPrint_section* ps = ((TForm*)this)->exist(s, t);
  CHECKD(ps, "Can't find section for field ", id);
  return ps->find_field(id);
}

TForm_subsection& TForm::find_field(char s, pagetype t, const char *section) const
{
  const TPrint_section* ps = ((TForm*)this)->exist(s, t);
  CHECKS(ps, "Can't find section for field ", section);
  return (TForm_subsection&)ps->find_field(section);
}

TPrint_section& TForm::section(char s, pagetype pos)
{
  TPrint_section* sec = exist(s, pos, TRUE);
  return *sec;
}

TPrint_section& TForm::section(char s, word pagenum)
{
  pagetype pt = odd_page;
  if (pagenum == 0 && exist(s, last_page))
    pt = last_page;
  if (pagenum == 1 && exist(s, first_page))
    pt = first_page;
  if (pt == odd_page && (pagenum & 0x1) == 0 && exist(s, even_page))
    pt = even_page;

  return section(s, pt);
}


// @doc EXTERNAL

// @mfunc Rilegge la sezione specificata
//
// @rdesc Ritorna se ce l'ha fatta
bool TForm::reread(
     char sec,    // @parm Sezione da rileggere
     pagetype p,  // @parm Posizione della pagina
     bool force)  // @parm Forza rilettura anche se nessun campo e' cambiato
{
  TPrint_section* s = exist(sec,p);
  bool ok = force;
  if (s != NULL)
      for (word j = 0; j < s->fields(); j++)
        ok |= s->field(j).dirty();
  if (s != NULL && ok)
  {
    s->destroy_fields();  // Distrugge tutti gli items...
    // ...si posiziona nel file sorgente alla sezione opportuna...
    TFilename n(_name); n.ext("frm");
    TScanner scanner(n);
    bool ok = FALSE;
    while (!ok)
    {
      while (TRUE)     // ...scans searching for a section...
      {
        const TString& key = scanner.popkey();
        if (key == "SE" || key == "") // ..if section or end of file...
          break;
      }
      const char   secr = scanner.popkey()[0];             // Section name (GRAPH, HEAD, BODY, FOOT)
      if (secr=='\0') break;
      const pagetype pr = char2page(scanner.popkey()[0]);  // Section type (odd, even, first, last)
      if (secr==sec && pr==p) ok = TRUE;  // L'ha trovata...
    }
    // ...riesegue la parse della sezione leggendo dal file sorgente
    if(ok && s->parse(scanner))
    {
     // Legge le modifiche su archivio e poi e' finita.
      s->set_dirty(FALSE);
      TLocalisamfile rprof(LF_RFORM);
      const char sez[3] = {sec,p+'0','\0'};
      rprof.zero();
      rprof.put("TIPOPROF", _name);
      rprof.put("CODPROF", _code);
      rprof.put("SEZ", sez);
      const TRectype filter(rprof.curr());

      for (int err = rprof.read(_isgteq); err == NOERR && rprof.curr() == filter; err = rprof.next())
      {
        const short id = rprof.get_int("ID");

        if (id == 0)
        {
          TPrint_section& se = section(sec, p);
          se.read_from(rprof.curr());
        }
        else
        {
          TForm_item& item = find_field(sec, p, id);
          item.read_from(rprof.curr());
        }
      }
      set_compulsory_specials();
    }
  }
  return ok;
}

///////////////////////////////////////////////////////////
// TForm
///////////////////////////////////////////////////////////

// @doc EXTERNAL

// @mfunc Cambia il formato di tutte le date nel form
//
// @rdesc Ritorna sempre TRUE
bool TForm::ps_change_date_format(
     TPrint_section& s,   // @parm Sezione nella quale modificare le date
     const char* f)   // @parm Nuovo formato delle date

// @comm Ha le stesse funzioni di <mf TForm::change_date_format>, ma per <c TPrint_section>,
//   all'uopo di chiamarla con ricorsiva insistenza

{
  for (word i = 0; i < s.fields(); i++)
  {
    TForm_item& fi = s.field(i);
    if (fi.is_section())
    {
      TPrint_section& ps = ((TForm_subsection&)fi).subsection();
      ps_change_date_format(ps, f);
    }
    else if (strcmp(fi.class_name(), "DATA") == 0)
    {
      ((TForm_date&)fi).set_format(f);
      if (!s.dirty()) s.set_dirty();
      if (!fi.dirty()) fi.set_dirty();
    }
  }
  return TRUE;
}

// @doc EXTERNAL

// @mfunc Cambia il formato di tutti i numeri nel form
//
// @rdesc Ritorna sempre TRUE
bool TForm::ps_change_number_format(
     TPrint_section& s,   // @parm Sezione nella quale modificare i numeri
     int w,       // @parm Dimensione massima del fomato numerico
     int dec,       // @parm Numero di decimali
     const char* p)   // @parm Picture del nuovo formato

// @comm Ha le stesse funzioni di <mf TForm::change_number_format>, ma per <c TPrint_section>,
//   all'uopo di chiamarla con ricorsiva insistenza
{
  for (word i = 0; i < s.fields(); i++)
  {
    TForm_item& fi = s.field(i);
    if (fi.is_section())
    {
      TPrint_section& ps = ((TForm_subsection&)fi).subsection();
      ps_change_number_format(ps, w, dec, p);
    }
    else if (strcmp(fi.class_name(), "NUMERO") == 0)
    {
      TForm_number& fn = (TForm_number&)fi;
      fn.width() = w;
      fn.set_decimals(dec);
      fn.set_picture(p);
      if (!s.dirty()) s.set_dirty();
      if (!fn.dirty()) fn.set_dirty();
    }
  }
  return TRUE;
}


void TForm::change_date_format(const char* f)
{
  char secs[] = { "FHGB" };
  char ptyp[] = { "LOEF" };
  TPrint_section* ps;

  for (int sc = 0; sc < 4; sc++)
    for (int pt = 0; pt < 4; pt++)
       if ((ps = exist(secs[sc], char2page(ptyp[pt]), FALSE)) != NULL)
         ps_change_date_format(*ps, f);
}

void TForm::change_number_format(int w, int dec, const char* p)
{
  char secs[] = { "FHGB" };
  char ptyp[] = { "LOEF" };
  TPrint_section* ps;

  for (int sc = 0; sc < 4; sc++)
    for (int pt = 0; pt < 4; pt++)
       if ((ps = exist(secs[sc], char2page(ptyp[pt]), FALSE)) != NULL)
         ps_change_number_format(*ps, w, dec, p);
}

// @doc EXTERNAL

// @mfunc Effettua l'update della sezione grafica background
//
// @rdesc Ritorna la lunghezza della pagina da stampare
word TForm::set_background(
     word p,  // @parm Numero pagina
     bool u)  // @parm Indica se aggiornare lo sfondo nella stampante corrente

// @xref <mf TForm::set_header> <mf TForm::set_body> <mf TForm::set_footer>
{
  word len = 0;

  if (u && _back.items())
  {
    TPrint_section& graph = section('G', p);
    graph.update();
    len = printer().formlen();
  }

  return len;
}

// @doc EXTERNAL

// @mfunc Effettua l'update della sezione header
//
// @rdesc Ritorna l'altezza dell'header settato
word TForm::set_header(
     word p,  // @parm Numero pagina
     bool u)  // @parm Indica se cambiare l'eventuale header o solo ritornare l'altezza

// @xref <mf TForm::set_background> <mf TForm::set_body> <mf TForm::set_footer>
{
  TPrinter& pr = printer();
  pr.resetheader();

  TPrint_section& head = section('H', p);

  if (u) head.update();
  else
  {
    head.reset();
    pr.headerlen(head.height());
  }

  for (word j = 0; j < head.height(); j++)
    pr.setheaderline(j, head.row(j));

  return head.height();
}

// @doc EXTERNAL

// @mfunc Effettua l'update della sezione body e la sua stampa su Printer
//
// @rdesc Ritorna l'altezza del body settato
word TForm::set_body(
     word p,  // @parm Numero pagina
     bool u)  // @parm Indica se effettuare l'update (TRUE) o fare il reset (FALSE)

// @xref <mf TForm::set_background> <mf TForm::set_header> <mf TForm::set_footer>

{
  TPrint_section& body = section('B', p);

  if (u) body.update_and_print(TRUE);
  else body.reset();

  return body.height();
}

TPrint_section * TPrint_section::section_above() const 
{ 
  if (_upsection)
    return &_upsection->section();
  return NULL;    
}
// @doc EXTERNAL

// @mfunc Compone e stampa tutte le righe della sezione (valida in caso di BODY)
// 
bool TPrint_section::update_and_print(bool show_fields, bool new_page)
{
    bool ok=TRUE;
    TPrinter& pr = printer();
    word last_printed_row=0;

    reset();
    if (show_fields
        && pr.current_row() > pr.headersize()+1
          && new_page)
    {       
        pr.formfeed();
    }
    for (word i = 0; i < fields(); i++)
    {
      if (!field(i).is_section())
      { // compose rows using "simple" fields
        const bool esito = field(i).update();
        if (!esito) ok = FALSE;
      } 
      else 
      { 
        TForm_subsection & ss=(TForm_subsection & )field(i);
        int last_hpos=min(word(field(i).y()-1), height());
        // found a subsection:print preceeding rows
        // preceeding rows are all section rows which have Y coord <= than subsection's Y
        if (show_fields && pr.current_row() > pr.headersize()+1)
        {
          if (word(last_hpos+ss.height()) > pr.rows_left())
            pr.formfeed();
        }
        for (word j = last_printed_row; show_fields && j < (word)last_hpos ;j++)
        {
          pr.print(row(j));
        }
        last_printed_row=j;
        // process subsection
        if (ss.enabled())
          ss.set_body(show_fields) ;
      }
    }

    const word hgt = height();

    if (show_fields && 
      last_printed_row < hgt && 
      pr.current_row() > pr.headersize()+1)
    {
      if (hgt-last_printed_row > pr.rows_left())
        pr.formfeed();
    }
    
    if (show_fields)
    {  
      for (word j = last_printed_row; j < hgt; j++)
        pr.print(row(j));
    }  

    return ok;
}


// @doc EXTERNAL

// @mfunc Effettua l'update della sezione footer
//
// @rdesc Ritorna l'altezza del footer settato
word TForm::set_footer(
     word p,  // @parm Numero pagina
     bool u)  // @parm Indica se cambiare l'eventuale footer corrente!!!

// @xref <mf TForm::set_background> <mf TForm::set_header> <mf TForm::set_body>

{
  TPrinter& pr = printer();
  pr.resetfooter();

  TPrint_section& foot = section('F', p);

  if (u) foot.update();
  else
  {
    foot.reset();
    pr.footerlen(foot.height());
  }

  for (word j = 0; j < foot.height(); j++)
    pr.setfooterline(j, foot.row(j));

  return foot.height();
}

void TForm::header_handler(TPrinter& p)
{
  if (form().firstpage_is_lastpage()) 
  {
    form().set_background(1, TRUE);
    form().set_header(1, TRUE);
    form().set_footer(0, FALSE);
  } 
  else 
  {
    const word page = form().page(p);
    form().set_background(page, TRUE);
    form().set_header(page, TRUE);
    form().set_footer(page, FALSE);
  }
}

void TForm::footer_handler(TPrinter& p)
{
  if (form().firstpage_is_lastpage()) {
    form().set_footer(0, TRUE);
  } else {
    const word currp = form().page(p);
    form().set_footer(currp, TRUE);
    if (currp)
      form().set_header(form().next_page(p), FALSE);
  }
}

// @doc EXTERNAL

// @mfunc Ritorna se la prima pagina coincide conl'ultima
bool TForm::firstpage_is_lastpage() const
{
  return _first_eq_last;
}

// @doc EXTERNAL

// @mfunc Ritorna il numero di pagina correntemente in stampa
//
// @rdesc Se <md _TForm::lastpage> e' TRUE (sta stampando l'ultima pagina)
//    ritorna 0, altrimenti ritorna il numero della pagina corrente da stampare
//    (chiam <mf TPrinter::getcurrentepage>).
word TForm::page(
     const TPrinter& p) const // @parm Operazione corrente di stampa
{
  return _lastpage || fixed_pages() && p.getcurrentpage()==_npages ? 0 : p.getcurrentpage();
}

word TForm::next_page(
     const TPrinter& p) const // @parm Operazione corrente di stampa
{
  return _lastpage || fixed_pages() && p.getcurrentpage()+1==_npages ? 0 : p.getcurrentpage()+1;
}

// @doc EXTERNAL

// @mfunc Effettua il posizionamento manuale del modulo
void TForm::arrange_form()

// @comm Nota: siccome si scrive direttamente sulla porta, sarebbe necessario
//   mandare una stringa di reset alla stampante, o per lo meno far si' che
//   ogni volta che si fa il posizionamento il font col quale scrive sia sempre
//   lo stesso. Tutto cio' non e' possibile con la generica solo testo, o meglio
//   ad ogni stampa col posizionamento e' necessario che la stampante sia resettata.
//   <nl>Riassumendo, come regola generale, i posizionamenti devono essere fatti con il
//   font di default della stampante (tipicamente 10 cpi). Accade pero' (con la generica
//   solo testo) che rimanga settato l'ultimo font, di conseguenza quando si effettua una
//   seconda stampa con posizionamento, stampera' tali caratteri in 17"! Per questo
//   motivo e' necessario settare a 17 cpi, almeno la prima volta, la stampante!.
//   <nl>Quindi, per ovviare a tutto cio, visto che <mf TForm::arange_form> ha senso solo su
//   stampanti ad aghi, che le stampanti ad aghi possono andare in emulazione EPSON o IBM,
//   che il codice per settare il font draft 17cpi e' lo stesso sia per EPSON che IBM
//   CHR(15), allora prima del posizionamento scrivo il chr(15) sulla stampante!

{
  int i, x;
  TString str_pos;
  TMask m("ba2100c");

#if XVT_OS == XVT_OS_WIN || XVT_OS == XVT_OS_NT
/*
  char defPrinter[80];
  char szDevice[50];

  // get default printer driver
  GetProfileString ("windows", "device", ",,,", defPrinter, sizeof(defPrinter));
  TToken_string pdev (defPrinter, ',');
  GetProfileString ("devices", pdev, "", szDevice, sizeof(szDevice));
  pdev.add(szDevice);
  device = pdev.get(2);
  device = device.left(4); //Legge solo LPTx...
  */
#else
  TString device(printer.printername());
  FILE* lpt = fopen(device,"w");

  if (lpt == NULL)
    fatal_box("Impossibile aprire il device %s.",device);
#endif

  // _ipy viene assunto uguale per entrambi i posizionamneti
  str_pos = "\017";  // Questo e' 15 in ottale...
  for (i=1; i < _ipy; i++) str_pos << "\n";
#if XVT_OS == XVT_OS_WIN ||  XVT_OS == XVT_OS_NT
  SpoolRow((char *) (const char *) str_pos, str_pos.len());
#else

  fprintf(lpt,"%s",(const char*) str_pos);
  fflush(lpt);              // Salta le righe...
  fclose(lpt);
#endif
  str_pos = "";  //Azzera la stringa di posizionamento
  for (i=1; i < _ipx; i++) str_pos << " "; //Aggiunge gli spazi necessari...
  if (_ipx > 0)
    str_pos << _char_to_pos;   // aggiunge il primo carattere di posizionamento...
  x = _fpx - _ipx ;                 // calcola quanti spazi aggiungere...
  for (i=1; i < x; i++) str_pos << " ";
  if (_fpx > 0)
    str_pos << _char_to_pos;  // aggiunge il secondo carattere di posizionamento
  str_pos << '\r';
//  TString bspc; bspc.fill('\b',str_pos.len()); // Questi servono per tornare indietro...
  do
  {
#if XVT_OS == XVT_OS_WIN ||  XVT_OS == XVT_OS_NT
    SpoolRow((char *)(const char *) str_pos, str_pos.len());
#else
    lpt = fopen(device,"w");
    if (lpt == NULL) fatal_box("Impossibile aprire il device %s.",device);
//    fprintf(lpt,"%s",(const char*) bspc);
    fprintf(lpt,"%s\r",(const char*) str_pos);
    fflush(lpt);
    fclose(lpt);
#endif
  } while (m.run() == K_ESC);     // cicla sulla stampa posizionamento...
/*  const int h = height(odd_page);
  str_pos.cut(0);
  for (i=0; i < h; i++) str_pos << "\n";
  lpt = fopen(device,"w");
  if (lpt == NULL) fatal_box("Non rieso ad aprire il device %s.",device);
  fprintf(lpt, "%s", (const char*) str_pos); // Salta tante righe quanto e' lungo il form standard
  fclose (lpt);       */
  printer().set_offset(-(_ipy-1), printer().get_column_offset());
}

long TForm::records() const
{
  const long r = cursor() ? cursor()->items() : 0;
  return r;
}

// @doc EXTERNAL

// @mfunc Genera automaticamente la sezione grafica con colonne fincate
//
// @rdesc Ritorna FALSE se non c'e' il body per quella pagina
bool TForm::genera_fincatura(
     pagetype p,  // @parm Posizione della pagina (vedi <t pagetype>)
     int y1,    // @parm Prima y per le righe verticali
     int y2,    // @parm Ultima y per le righe verticali
     const int* rows) // @parm Array di posizioni riga con 0 per finire


{
  TPrint_section* body = exist('B', p);
  if (body == NULL) return FALSE;

  body->reset_tabs();

  bool istemp = exist('G', p) == NULL;

  TGraphic_section* grs = (TGraphic_section*)exist('G', p, TRUE);
  grs->temp() = istemp;

  int j = 0, start = 999, end = 0, wlast = 0;
  int cols[MAXCOLUMNS];

  for (word k = grs->fields(); k > 0; k--)
    if (grs->field(k-1).temp())
      grs->destroy_field(k-1);

  for (int i = 0; i < (int)body->fields(); i++)
  {
    TForm_item& f = body->field(i);
    if (!f.shown() || f.is_section()) 
      continue;

    int x = f.x();

    if (x < start) start = x;
    if (x > end)   { end = x; wlast = f.width(); }
    cols[j++] = f.finkl() ? -x : x;
  }

  // inner lines
  for (i = 0; i < j; i++)
  {
    if (cols[i] != start && cols[i] > 0)
    {
      TForm_line* l = new TForm_line(grs);
      l->set_x(cols[i]-1);
      l->y()      = (word)y1;
      l->id()     = -1;
      l->width()  = 1;
      l->height() = (int)(y2 - y1 + 1);
      l->set("@R");
      l->temp() = TRUE;
      grs->add_field(l);
    }
  }

  // box around
  if (start != 999 && end != 0)
  {
    TForm_box* l = new TForm_box(grs);
    l->set_x(start-1);
    l->y()      = (word)y1;
    l->id()     = -1;
    l->width()  = (int)(end + wlast - start + 2);
    l->height() = (int)(y2 - y1 + 1);
    l->set("@B@R");
    l->temp() = TRUE;
    grs->add_field(l);
  }

  // horizontal lines
  if (start != 999 && end != 0)
    for (i = 0; rows[i]; i++)
    {
      TForm_line* l = new TForm_line(grs);
      l->set_x(start-1);
      l->y()      = (word)rows[i];
      l->id()     = -1;
      l->width()  = (int)(end + wlast - start + 2);
      l->height() = 1;
      l->set("@R");
      l->temp() = TRUE;
      grs->add_field(l);
    }

  return TRUE;
}

// @doc EXTERNAL

// @mfunc Genera le righe di intestazione colonna alla riga indicata, vale per COLUMNWISE
//
// @rdesc Ritorna FALSE se non esiste body (o non e' COLUMNWISE) o se non esiste l'header
bool TForm::genera_intestazioni(
     pagetype p,  // @parm Posizione della pgaina (vedi <t pagetype>)
     short y)     // @parm Riga dell'header in cui vanno inserite

// @comm I form_items di intestazione vengono aggiunti alla sezione header di tipo <p p> con ID -1

{
  TPrint_section* body = exist('B', p);
  if (body == NULL || !body->columnwise()) 
    return FALSE;

  TPrint_section* header = exist('H', p);
  if (header == NULL) 
    return FALSE;
  body->reset_tabs();
  
  // Elimina eventuali campi temporanei precedenti
  for (int k = header->fields()-1; k >= 0; k--)
    if (header->field(k).temp())
      header->destroy_field(k);

  const word items = body->fields();
  TForm_string* s;
  for (word j=0;j<items;j++)
  {// Scans all body items to print, and append header items...
    TForm_item& fi = body->field(j);
    if (!fi.shown() || fi.is_section())
      continue;
    char align='\0';
    if (fi.col_head()[0]=='@')
    {
      switch (fi.col_head()[1])
      {
        case 'C': // centered
        case 'c':
          align='c';
          break;
        case 'R': // right just
        case 'r':
          align='r';
          break;
        default: // left just
          align='l';
          break;
      }
    }
    TParagraph_string prompts(fi.col_head().mid(align ? 2:0),fi.width());
    // Paragraph_string has as many items as lines needed...
    for (int c=0;c < prompts.items(); c++)
    {
      TString p(prompts.get(c));
      switch (align)
      {
        case 'c':
          p.center_just(fi.width());
          break;
        case 'r':
          p.right_just(fi.width());
          break;
      }
      s = new TForm_string(header);
      s->id() = -1;
      s->set_x(fi.x());
      s->y() = y+c;
      s->set_prompt(p);
      s->temp() = TRUE;
      header->add_field(s);
    }
  }
  return TRUE;
}

/*
void TForm::remove_temp_items(char sec, pagetype p)
{
  TPrint_section* s = exist(sec,p);
  if (s!=NULL)
  {
    const word items = s->fields();
    for (word j=0;j<items; j++)
    {
      if  (s->field(j).temp())
        s->destroy_field(j,FALSE);
    }
    s->field_array().pack();
  }
}


void TForm::put_examples(char sez, pagetype p)
{
  TPrint_section* s = exist(sez,p);
  if (s!=NULL)
  {
    const word items = s->fields();
    for (word i=0;i<items;i++)
    {
      TForm_item& fi = s->field(i);
      if (fi.fields()!=0) continue;
      if (fi.memo())
        fi.set(fi.memo_info());
      else
      if (fi.prompt().empty())
      {
        if (fi.class_name() == "DATA")
        {
          const TDate d(TODAY);
          fi.set(d);
        }
        else
          if (fi.class_name() == "NUMERO")
          {
            fi.set_prompt(fi.example());
            fi.temp() = TRUE;
          }
          else
            fi.set(fi.key());
      }
    }
  }
}

void TForm::remove_examples(char sez, pagetype p)
{
  TPrint_section* s = exist(sez,p);
  if (s!=NULL)
  {
    const word items = s->fields();
    for (word i=0;i<items;i++)
    {
      TForm_item& fi = s->field(i);
      if (fi.fields()!=0) continue;
      if (fi.memo())
        fi.set("");
      else
        if (fi.class_name() == "NUMERO" && fi.temp())
        {
          fi.set_prompt("");
          fi.temp() = FALSE;
        }
    }
  }
}
*/

// @doc EXTERNAL

// @mfunc Stampa gli items da <p form> a <p to>
//
// @rdesc Ritorna se ha effettuato correttamente la stampa
bool TForm::print(
     long from,   // @parm Primo item da stampare (default 0l)
     long to)   // @parm Ultimo da stampare (se <lt>0 stampa fino alla fine del file, default -1l)

// @comm Se i parametri di posizionamento sono settati e cosi' pure gli offset genera un <f error_box>.
{
  _cur_form = this;

  if ((_char_to_pos != '\0' || ((_ipx +_ipy+_fpx) != 0)) &&  // Se i parametri di posizionamento sono settati e
      (_x != 0 || _y != 0))                                      // cosi' pure gli offset genera un errore.
  {
    error_box("Non e' possibile settare contemporaneamente gli offset"
              " e i parametri di posizionamento del modulo.");
    return FALSE;
  }
  TPrinter& pr = printer();
  if (_frompage) pr.set_from_page(_frompage);
  if (_topage) pr.set_to_page(_topage);

  if (_char_to_pos != '\0' || (_ipx +_ipy+_fpx) != 0)   // Effettua il posizionamento del form...
  {
    if (_arrange && pr.printtype() == winprinter)
      arrange_form();
  }
  else
    pr.set_offset(_y,_x);

  pr.setheaderhandler(header_handler); // Setta handlers
  pr.setfooterhandler(footer_handler);
  if (!pr.is_generic())
  {
    for (pagetype t = odd_page; t <= last_page; t = pagetype(t+1))
    {
      if (height(t)> (word)pr.formlen())
      {
        TString s("La lunghezza totale della sezione ");
        switch ( t )
        {
        case odd_page:
          s << "standard"; break;
        case even_page:
          s << "pagine pari"; break;
        case first_page:
          s << "prima pagina"; break;
        case last_page:
          s << "ultima pagina"; break;
        default:
          break;
        }
        s << " eccede la lunghezza reale del foglio.";
        message_box(s);
      }
    }
  }
  else
  {
    pr.formlen(height());
  }
  pr.set_char_size(_fontsize);                     // Set font name and size
  pr.set_fontname(_fontname);                      // according to current form
  const bool was_open = pr.isopen();

  set_last_page(FALSE);                            // non e' l'ultima pagina

  set_background(1, TRUE);

  if (!was_open && !pr.open())
    return FALSE;
  do_events();

  long lastrec= records()-1;

  if (to < 0) to = lastrec;

  // controlla i casi di stampa
  _first_eq_last=FALSE;
  if (!fixed_pages() && set_body(1,FALSE) * word(to-from+1) <= pr.formlen()- set_header(1,FALSE) - set_footer(0,FALSE))
    // tutta la stampa sta in una pagina
    _first_eq_last=TRUE;
//  if (to == lastrec) to--; // l'ultima pagina � gestita come caso particolare

  bool ok = TRUE;

  for (long i = from; i <= to && ok;)
  {
    if (from < 0) to = from;
    else if (cursor()) 
        *cursor()=i;
    match_result();
          
    if (pr.current_row() > pr.headersize()+1)
    {
      const word h = set_body(page(pr), FALSE);
      if (h > pr.rows_left()) {
        pr.formfeed();
        // quanto resta da stampare sta nell'ultima  pagina
        if (!fixed_pages() && set_body(0,FALSE) * word(to-i+1) <= pr.formlen()- set_header(1,FALSE) - set_footer(0,FALSE))
        {
          while (i < to && from >= 0) // stampa l'ultima pagina
          {
            i++;
            set_last_page(TRUE);
            set_background(0, TRUE);
            set_header(0, TRUE);
            if (cursor()) {
              *cursor() = i;
              set_body(0, TRUE);
            }     
          }
        }
        break;
      }
    }
  
    set_body(page(pr), TRUE);
    if (cursor()) {
      if (next_match_done()) 
        i=cursor()->pos();
      else  
        i++;
    }
  } // fine ciclo di stampa

  while (fixed_pages() && page(pr) % _npages !=0)
  {
    static TPrintrow empty_line;
    pr.formfeed();
    pr.print(empty_line);
  }
  pr.formfeed();
  if (!was_open)
    pr.close();

  pr.setheaderhandler(NULL);
  pr.setfooterhandler(NULL);

  return ok;
}

/*bool TForm::print(
     long from,   // @parm Primo item da stampare (default 0l)
     long to)   // @parm Ultimo da stampare (se <lt>0 stampa fino alla fine del file, default -1l)

// @comm Se i parametri di posizionamento sono settati e cosi' pure gli offset genera un <f error_box>.
{
  _cur_form = this;

  if ((_char_to_pos != '\0' || ((_ipx +_ipy+_fpx) != 0)) &&  // Se i parametri di posizionamento sono settati e
      (_x != 0 || _y != 0))                                      // cosi' pure gli offset genera un errore.
  {
    error_box("Non e' possibile settare contemporaneamente gli offset"
              " e i parametri di posizionamento del modulo.");
    return FALSE;
  }
  TPrinter& pr = printer();
  if (_frompage) pr.set_from_page(_frompage);
  if (_topage) pr.set_to_page(_topage);

  if (_char_to_pos != '\0' || (_ipx +_ipy+_fpx) != 0)   // Effettua il posizionamento del form...
  {
    if (_arrange && pr.printtype() == winprinter)
      arrange_form();
  }
  else
    pr.set_offset(_y,_x);

  pr.setheaderhandler(header_handler); // Setta handlers
  pr.setfooterhandler(footer_handler);
  if (!pr.is_generic())
  {
    for (pagetype t = odd_page; t <= last_page; t = pagetype(t+1))
    {
      if (height(t)> (word)pr.formlen())
      {
        TString s("La lunghezza totale della sezione ");
        switch ( t )
        {
        case odd_page:
          s << "standard"; break;
        case even_page:
          s << "pagine pari"; break;
        case first_page:
          s << "prima pagina"; break;
        case last_page:
          s << "ultima pagina"; break;
        default:
          break;
        }
        s << " eccede la lunghezza reale del foglio.";
        message_box(s);
      }
    }
  }
  else
  {
    pr.formlen(height());
  }
  pr.set_char_size(_fontsize);                     // Set font name and size
  pr.set_fontname(_fontname);                      // according to current form
  const bool was_open = pr.isopen();

  set_last_page(FALSE);                            // non e' l'ultima pagina

  set_background(1, TRUE);

  if (!was_open && !pr.open())
    return FALSE;
  do_events();

  long lastrec= records()-1;

  if (to < 0) to = lastrec;
  if (to == lastrec) to--; // l'ultima pagina � gestita come caso particolare

  bool ok = TRUE;

  for (long i = from; i <= to && ok;)
  {
    if (from < 0) to = from;
    else if (cursor()) 
        *cursor()=i;
    match_result();
        
    if (pr.current_row() > pr.headersize()+1)
    {
      const word h = set_body(page(pr), FALSE);
      if (h > pr.rows_left()) {
        pr.formfeed();
      }
    }

    set_body(page(pr), TRUE);
    if (cursor()) {
      if (next_match_done()) 
        i=cursor()->pos();
      else  
        i++;
    }
  }

  if (i >= lastrec && from >= 0) // stampa l'ultima pagina (garantisce il footer diverso)
  {
    set_last_page(TRUE);
    set_background(0, TRUE);
    set_header(0, TRUE);
    if (cursor() && i == lastrec) {
        *cursor() = i;
        set_body(0, TRUE);
    }     
    pr.formfeed();
  }

  if (!was_open)
    pr.close();

  pr.setheaderhandler(NULL);
  pr.setfooterhandler(NULL);

  return ok;
} */


void TForm::print_section(ostream& out, char s) const
{
  for (pagetype t = odd_page; t <= last_page; t = pagetype(t+1))
  {
    const TPrint_section* sec = ((TForm*)this)->exist(s, t);
    if (sec && sec->ok() && !sec->temp())
    {
      const char* name;
      switch (s)
      {
      case 'F':
        name = "FOOTER"; break;
      case 'G':
        name = "GRAPHICS"; break;
      case 'H':
        name = "HEADER"; break;
        default :
        name = "BODY"; break;
      }
      out << "SECTION " << name;
      out << *sec;
      out << "END\n" << endl;
    }
  }
}

// funzione di validazione delle macro
bool TForm::validate(TForm_item &cf, TToken_string &s)
{ 
  const TString code(s.get(0)); // prende il primo parametro, il codice del messaggio
  TString valore;

  if (code=="_ISAMREAD")
  {
    // lettura generica di un file del database
    // sintassi: _ISAMREAD,<file>,<espressione input>[!<espressione input>!...],{<campo file>|<espressione output>[!<espressione output>!...]}
    // dove: <file> � il numero logico del file o il nome della tabella
    //       <espressione input> � un'espressione del tipo <campo file>=<espressione campi form>
    //       <espressione campi form> � un'espressione di costanti numeriche, stringhe e valori di campi della form (indicati con il loro numero preceduto da #)
    //       <espressione output> � un'espressione del tipo <campo form o gruppo>=<campo file> (se � un gruppo deve essere seguito da @) oppure solo <campo file> (il campo della form � quello corrente)
    int i, j, poseq, posrv, itms;
    pagetype pt;
    char sec;
    TLocalisamfile *file;
    TString f_code(s.get()); // prende il codice del file da leggere
    if (atoi(f_code) != 0) file= new TLocalisamfile(atoi(f_code)); // se il codice � numerico allora � un file
    else file= new TTable(f_code); // altrimenti � una tabella
    file->zero(); // vuota il record corrente del file
    TToken_string in(s.get(), '!');
    for (i=0; i<in.items(); i++)
    { // scansione sugli elementi dell'input
      TString curr(in.get(i));
      poseq= curr.find("=="); // divide la stringa corrente in lvalue e rvalue
      if (poseq== -1)
      {
        poseq= curr.find('=');
        if (poseq != -1) posrv= poseq+1;
      }
      else
        posrv= poseq+2;
      TString fld(curr.left(poseq)); // preleva il nome del campo del file alla sinistra dell'uguale
      TString expr(curr.mid(posrv)); // preleva l'espressione di assegnamento alla destra dell'uguale
      TExpression rval(expr, _strexpr);
      for (j=0; j<rval.numvar(); j++)
      { // scansione delle variabili dell'espressione di rvalue
        TString var= rval.varname(j);
        if (var[0]=='#') var.ltrim(1); // rimuove dalla stringa il primo carattere
        TForm_item &fi= cf.find_field(var);
        rval.setvar(j, fi.get()); // il valore corrente del campo viene settato nell'espressione
      }
      file->put(fld, (const char *)rval); // scrive il risultato dell'espressione nel campo del file
    }
    if (file->read()== NOERR)
    { // tenta una lettura del file
      TToken_string out(s.get(), '!');
      for (i=0; i<out.items(); i++)
      { // scansione sugli elementi dell'output
        TString curr(out.get(i));
        poseq= curr.find("=="); // divide la stringa corrente in lvalue e rvalue
        if (poseq== -1)
        {
          poseq= curr.find('=');
          if (poseq != -1) posrv= poseq+1;
        }
        else
          posrv= poseq+2;
        if (poseq== -1)
        {
          const TString &dat= file->get(curr); // preleva il nome del campo del file e lo legge dal record
          cf.set(dat);
        }
        else
        {
          TString fld(curr.left(poseq)); // preleva il nome del campo del form alla sinistra dell'uguale
          const TString &dat= file->get(curr.mid(posrv)); // preleva il nome del campo del file alla destra dell'uguale e lo legge dal record
          if (fld[0]=='#') fld.ltrim(1);
          if (fld.right(1)== "@")
          { // se c'� la a-commerciale � un gruppo
            if (fld.find("->") != -1)
            { // se nel gruppo c'� la freccia si riferisce ad un'altra sezione
              sec= fld[0];
              if (fld[1] != '-') pt= char2page(fld[1]);
              else pt= even_page;
              itms= section(sec, pt).fields();
            }
            else
            { // altrimenti si riferisce alla sezione corrente
              sec= cf.section().section_type();
              pt= cf.section().page_type();
              itms= cf.section().fields();
            }
            for (j=0; j<itms; j++)
            { // per ogni campo della sezione specificata (o sottointesa)...
              TForm_item &fi= section(sec, pt).field(j);
              fi.set(dat);
            }
          }
          else
          {
            TForm_item &fi= cf.find_field(fld);
            fi.set(dat);
          }
        }
      }
    }
    delete file;
    return TRUE;
  // fine _ISAMREAD
  }

  if (code== "_TABLEREAD")
  {
  // lettura generica di un campo di una tabella
  // sintassi: _TABLEREAD,<tabella>,<chiave>,<campo file>
  // dove: <tabella> nome tabella da leggere
  //       <chiave> costante stringa o riferimento a campo della form (preceduto da '#') da usare come chiave di ricerca
  //       <campo file> identificativo del campo da leggere dalla tabella
    TTable tab(s.get()); // prende il nome della tabella
    tab.zero(); // vuota il record corrente della tabella
    TString in(s.get()); // prende il valore o il campo da usare come codice di ricerca
    if (in[0]== '#')
    {
     in.ltrim(1);
     TForm_item &fi= cf.find_field(in);
     in= fi.get();
    }
    tab.put("CODTAB", in); // setta la chiave nella tabella
    if (tab.read()== NOERR)
    {
      const TString &fld= s.get(); // prende il nome del campo da leggere...
      valore = tab.get(fld);
      cf.set(valore);
    }
    return TRUE;
  } // fine _TABLEREAD

  if (code== "_ALIGN")
  {
    // allineamento della posizione di un campo rispetto ad un altro
    // sintassi: _ALIGN,<campo form>[,<allineamento>][,<allineamento>...]
    // dove: <campo form> � il campo della form (preceduto da '#') da cui prendere l'allineamento
    //       <allineamento> � uno dei seguenti valori:
    //        TOP    allinea sulla riga d'inizio
    //        MIDDLE allinea al centro (effettivo)
    //        BOTTOM allinea sulla riga di fine (effettiva, non preimpostata)
    //        LEFT   allinea sulla colonna d'inizio
    //        RIGHT  allinea sulla colonna di fine
    TString in(s.get());
    if (in[0]== '#') in.ltrim(1);
    TForm_item &fi= cf.find_field(in);

    int i= 2;
    for (const char * cmd = s.get(); cmd != NULL; cmd = s.get())
    { // Calcola la nuova posizione
      TString align(cmd);
      if (align[0]=='!')
        align.ltrim(1);
        
      if (align== "TOP") cf.y()= fi.y();
      else
        if (align== "MIDDLE") cf.y()= fi.y()+ fi.effective_height()/2;
        else
          if (align== "BOTTOM") cf.y()= fi.y()+ fi.effective_height()-1;
          else
            if (align== "LEFT") cf.set_x(fi.x());
            else
              if (align== "RIGHT") cf.set_x(fi.x()+ fi.width());
    }
    // altrimenti stampa la stringa cosi' com'e'
    return TRUE;
  } // fine _ALIGN

  if (code== "_PAGENO") // Messaggio per stampare il numero di pagina corrente
  {
    TString16 pg; pg << int(printer().getcurrentpage() );
    cf.set(pg);
    return TRUE;
  } 

  if (code== "_TODAY")// Messaggio per stampare la data di oggi
  {
    TString16 dt(TDate(TODAY).string());
    cf.set(dt);
    return TRUE;
  } 

  if (code== "_NUMEXPR" || code== "_STREXPR")
  {
  // Messaggio per la valutazione di espressioni numeriche e stringhe
    TExpression rval(s.get(),code[1]=='N' ? _numexpr:_strexpr,TRUE);
    cf.section().eval_expr(rval);
    const char *res = (const char *)rval;
    if (!rval.error())
      cf.set(res);
    return TRUE;
  } 

  if (code== "_DITTA")
  {
    // lettura dei dati della ditta
    // sintassi: _DITTA,{<campo relazione>|<macro>}
    // dove: <campo relazione> � un riferimento alla relazione di gestione dei dati della ditta (es. 113@->DENCOM � la denominazione del comune di residenza della ditta)
    //       <macro> � uno delle macro seguenti:
    //         !RAGSOC  ragione sociale
    //         !IND     indirizzo (fiscale se c'�, oppure di residenza)
    //         !NUM     numero civico (fiscale se c'�, oppure di residenza)
    //         !CAP     CAP (fiscale se c'�, oppure di residenza)
    //         !COM     comune (fiscale se c'�, oppure di residenza)
    //         !PROV    provincia (fiscale se c'�, oppure di residenza)
    //         !IVA     partita iva
    //         !CF      codice fiscale
    //         !TEL     numero di telefono (con prefisso)
    //         !FAX     numero di fax (con prefisso)
    //         !REGSOC  numero di registrazione presso il Tribunale
    //         !CCIAA   numero di registrazione presso la camera di commercio
    // nota: la relazione della ditta � cos� strutturata:
    //       %NDITTE (9) Dati ditte
    //       + %ANAGR (6) Anagrafica generale (indirizzo, ecc.)
    //          + %COMUNI (113@) Comune di residenza
    //          + %COMUNI (213@) Comune di residenza fiscale
    TLocalisamfile firm(LF_NDITTE);
    TLocalisamfile anag(LF_ANAG);
    firm.put("CODDITTA",main_app().get_firm());
    firm.read();
    anag.put("TIPOA",firm.get("TIPOA"));
    anag.put("CODANAGR",firm.get("CODANAGR"));
    anag.read();
    TString in(s.get());
    if (in[0]=='!') {
      in.ltrim(1);
      bool _fisc= anag.get("INDRF").not_empty();
      if (in=="RAGSOC") 
        valore = anag.get("RAGSOC");
      if (in=="IND") {
        if (_fisc) 
          valore = anag.get("INDRF");
        else 
          valore = anag.get("INDRES");
      }
      if (in=="NUM") {
        if (_fisc)
          valore = anag.get("CIVRF");
        else 
          valore = anag.get("CIVRES");
      }
      if (in=="CAP") {
        if (_fisc) 
          valore = anag.get("CAPRF");
        else 
          valore  = anag.get("CAPRES");
      }
      /*if (in=="COM") {
        if (_fisc)
          valore = _firmrel.lfile(-213).get("DENCOM");
        else 
          valore = _firmrel.lfile(-113).get("DENCOM");
      }
      if (in=="PROV") {
        if (_fisc)
          valore = _firmrel.lfile(-213).get("PROVCOM");
        else 
          valore = _firmrel.lfile(-113).get("PROVCOM");
      }*/
      if (in=="IVA")
        valore = anag.get("PAIV");
      if (in=="CF") 
        valore = anag.get("COFI");
      if (in=="TEL") {
        valore = anag.get("PTEL");
        valore << "/" << anag.get("TEL");
      }
      if (in=="FAX") {
        valore = anag.get("PFAX");
        valore << "/" << anag.get("FAX");
      }
      /*if (in=="REGSOC") {
        valore = _firmrel[LF_UNLOC].get("REGTRIB");
        valore << " Vol. " << _firmrel[LF_UNLOC].get("VOLTRIB");
        valore << " Fasc. " << _firmrel[LF_UNLOC].get("FASCTRIB");
      }
      if (in=="CCIAA") {
        valore = _firmrel[LF_UNLOC].get("NUMCCIAA");
        valore << " del " << _firmrel[LF_UNLOC].get("DATAICCIAA");
      } */
    } else {
      TFieldref fref(s.get(), 0);
      valore = fref.read(anag.curr());
    }
    cf.set(valore);
    return TRUE; 
  }
  
  return TRUE; 
}

void TForm::print_on(ostream& out) const
{
  main_app().begin_wait();

  if (relation())
  {
    out << *relation() << "\nEND" << endl;
    print_description(out);
  }

  print_general(out);

  print_section(out, 'G');
  print_section(out, 'H');
  print_section(out, 'B');
  print_section(out, 'F');

  out << "END" << endl;

  main_app().end_wait();
}


word TForm::height(word page)
{
  const pagetype t = (page == 1) ? first_page : ((page & 0x1) ? odd_page : even_page);

  word h = 0;
  if (_back.items() == 0)
  {
    if (_head.items() && exist('H', t) != NULL)
      h += section('H', page).height();
    if (_body.items() && exist('B', t) != NULL)
      h += section('B', page).height();
    if (_foot.items() && exist('F', t) != NULL)
      h += section('F', page).height();
  }
  else
    h = printer().formlen();

  return h;
}

// @doc EXTERNAL

// @mfunc Legge un profilo
//
// @rdesc Ritorna se e' riuscito nell'operazione:
// @flag TRUE | E' riuscito a leggere il prfilo
// @flag FALSE | Non ha letto il profilo
bool TForm::read_profile()
// @comm Per la lettura del profilo procede leggendo prima la definizione del
//   profilo base e successivamente le modifiche relative.
{
  TLocalisamfile prof(LF_FORM);
  TLocalisamfile rprof(LF_RFORM);

  prof.zero();
  prof.put("TIPOPROF",_name);
  prof.put("CODPROF",_code);
  if (prof.read(_isequal) == NOERR)
  {
    rprof.zero();
    rprof.put("TIPOPROF", _name);
    rprof.put("CODPROF", _code);
    const TRectype filter(rprof.curr());
    TString set("HGBF");

    for (int err = rprof.read(_isgteq); err == NOERR && rprof.curr() == filter; err = rprof.next())
    {
      // controllo lingua per codici profilo
      char cl = rprof.curr().get("CODPROF").right(1)[0];
      char cp = _code.right(1)[0];

      // stop se il codice letto e' di un profilo EC in lingua e
      // il corrente e' ec non in lingua
      if (!isdigit(cl) && cl != cp) break;

      const TString& s = rprof.get("SEZ");
      const char sec = s[0];
      if (set.find(sec)<0) continue; // Se non fa parte di una sezione standard la salta
      const pagetype pt = char2page(s[1]);
      const short id = rprof.get_int("ID");

      if (id == 0)
      {
        TPrint_section& se = section(sec, pt);
        se.read_from(rprof.curr());
      }
      else
      {
        TForm_item& item = find_field(sec, pt, id);
        item.read_from(rprof.curr());
      }
    }
    if (_code.not_empty()&& !_isnew)
    {
      _x = prof.get_int("OFFX");
      _y = prof.get_int("OFFY");
      _fontname = prof.get("FONTNAME");
      _fontsize = prof.get_int("FONTSIZE");
      _char_to_pos = prof.get("CTP")[0];
      _ipx = prof.get_int("IPX");
      _ipy = prof.get_int("IPY");
      _fpx = prof.get_int("FPX");
      set_fincatura(prof.get("GRID"));
    }
  }
  return TRUE;
}

bool TForm::write_profile()
{
  const char sechar[4] = { 'B', 'F', 'G',  'H' };

  get_compulsory_specials();
  TLocalisamfile form(LF_FORM);
  form.zero();
  form.put("TIPOPROF",_name);
  form.put("CODPROF",_code);
  if (form.read(_isequal,_lock) == NOERR)
  {
    if (_dirty)
    {
      form.put("OFFY",_y);
      form.put("OFFX",_x);
      form.put("FONTNAME",_fontname);
      form.put("FONTSIZE",_fontsize);
      form.put("CTP",_char_to_pos);
      form.put("IPX", _ipx);
      form.put("IPY", _ipy);
      form.put("FPX", _fpx);
      form.put("GRID", _fink);
      form.rewrite();
      _dirty=FALSE;
    }
  }

  TLocalisamfile rform(LF_RFORM);
  TRectype& cur = rform.curr();

  int err = NOERR;
  for (int sn = 0; sn < 4 && err == NOERR; sn++)                        // For each section
  {
    const char sc = sechar[sn];
    for (pagetype pt = odd_page; pt <= last_page; pt = pagetype(pt+1))  // For each section type
    {
      TPrint_section* sec = exist(sc, pt);
      if (sec != NULL && sec->dirty())
      {
        const char codsez[3] = { sc, pt+'0', '\0' };
        cur.zero();
        cur.put("TIPOPROF", name());
        cur.put("CODPROF", code());
        cur.put("SEZ", codsez);
        sec->print_on(cur);
        sec->set_dirty(FALSE);

        err = _isnew ? rform.write() : rform.rewrite();
        if (err != NOERR)
          err = _isnew ? rform.rewrite() : rform.write();

        for (word i = 0; i < sec->fields() && err == NOERR; i++)
        {
          TForm_item& fi = sec->field(i);
          if (fi.dirty())
          {
            fi.print_on(cur);
            err = _isnew ? rform.write() : rform.rewrite();
            if (err != NOERR)
              err = _isnew ? rform.rewrite() : rform.write();
            fi.set_dirty(FALSE);
          }
        }
      }
    }
  }
  form.reread(_unlock);
  if (err != NOERR)
    return error_box("Errore di salvataggio profilo: %d", err);

  return TRUE;
}

void TForm::init()
{
  _npages=0;
  _relation= NULL;
  _cursor= NULL;
  _rel_desc= NULL;
  _isnew= FALSE;
  _fontname= "Roman 17cpi";
  _fontsize= 12;
  _x= 0;
  _y= 0;
  _char_to_pos= '\0';
  _ipx= 0;
  _ipy= 0;
  _fpx= 0;
  _arrange= TRUE;
  set_fincatura("+++++++++-|");
  set_fink_mode(TRUE);
  _dirty= FALSE;
  _msg_add_enabled = TRUE;
}

// @doc EXTERNAL

// @mfunc Carica il form dal file specificato
void TForm::read(
     const char* name,  // @parm Nome del profilo di stampa da leggere
     const char* code,  // @parm Codice del profilo di stampa da leggere
     int lev,     // @parm Permessi di stampa
     const char* desc)  // @parm Descrizione del formato da leggere
{
  _name= name;
  _code= code;
  _editlevel= lev;
  _desc= desc;

  main_app().begin_wait();

  if (_code.not_empty())
  {
    // extract base form name
    TLocalisamfile forms(LF_FORM);
    forms.zero();

    forms.put("TIPOPROF", _name);
    forms.put("CODPROF", _code);
    _isnew = forms.read() != NOERR;
    if (_isnew)
    {
      // create new form
      forms.put("TIPOPROF", _name);
      forms.put("CODPROF", _code);
      forms.put("DESC",    _desc);
      forms.put("OFFY",_y);
      forms.put("OFFX",_x);
      forms.put("FONTNAME",_fontname);
      forms.put("FONTSIZE",_fontsize);
      forms.put("CTP", _char_to_pos);
      forms.put("IPX", _ipx);
      forms.put("IPY", _ipy);
      forms.put("FPX", _fpx);
      forms.put("GRID", _fink);
      forms.write();
    }
    else
      if (forms.status() == NOERR)
      {
        _desc = forms.get("DESC");
        if (_desc != desc && desc != "") // Controlla se la descrizione e' diversa, in questo caso l'aggiorna
        {
          forms.reread(_lock);
          forms.put("DESC",desc);
          forms.rewrite();
        }
      }
  }

  // read base form
  TFilename n(_name); n.ext("frm");
  if (!fexist(n)) fatal_box("Il file %s non esiste.",(const char *) n);
  TScanner scanner(n);

  bool ok = TRUE;
  if (scanner.popkey() == "US")                         // Parse relation
  {
    ok = parse_use(scanner);
    while (ok && scanner.popkey() == "JO" || scanner.key() == "SO")
    {
      if (scanner.key() == "JO")
        ok = parse_join(scanner);
      else // join a sorted file
      {
        ok= parse_sortedjoin(scanner);
      } 
    }

    parse_description(scanner);                         // Parse description
  }
  else scanner.push();

  if (ok) parse_general(scanner);                       // Parse general

  while (ok)
  {
    if (scanner.popkey() != "SE")                       // SECTION or END
      break;
    const char sec = scanner.popkey()[0];               // Section name (GRAPH, HEAD, BODY, FOOT)
    const pagetype p = char2page(scanner.popkey()[0]);  // Section type (odd, even, first, last)
    TPrint_section* ps = exist(sec, p, TRUE);           // Create section
    ok = ps->parse(scanner);                            // Parse section
  }

  if (_code.not_empty())
    read_profile();    // read from LF_RFORM file

  set_compulsory_specials();
  TPrinter& pr = printer();
  pr.set_offset(_y,_x);
  if (_fontname.not_empty())
  {
    pr.set_char_size(_fontsize);
    pr.set_fontname(_fontname);
  } 
  else
  {
    _fontname = pr.fontname();
    _fontsize = pr.get_char_size();
  }
  set_fink_mode(TRUE);

  main_app().end_wait();
}

void TForm::set_compulsory_specials()
{
  const char sechar[3] = { 'B', 'F', 'H' };

  for (int sn = 0; sn < 3; sn++)
  {
    const char sc = sechar[sn];
    for (pagetype pt = odd_page; pt <= last_page; pt = pagetype(pt+1))
    {
      TPrint_section* sec = exist(sc, pt);
      if (sec != NULL)
      {
        const word fields = sec->fields();
        const bool col_wise = sec->columnwise();
        for (word i = 0; col_wise && i < fields; i++)
        {
          TForm_item& fi = sec->field(i);
          if (fi.special_items()>0)
          {
            fi.set_col_head(fi.get_special_value("INTESTAZIONE"));
            fi.ofs() = (short)atoi(fi.get_special_value("OFFSET"));
            TString fnk(fi.get_special_value("FINCATURA"));
            fi.set_finkl(fnk[0]!='X'); // !finkl significa fincatura abilitata
            fi.set_finkr(fnk[1]!='X'); // !finkr significa fincatura abilitata
          }
        }
      }
    }
  }
}

void TForm::get_compulsory_specials()
{
  const char sechar[3] = { 'B', 'F', 'H' };

  for (int sn = 0; sn < 3; sn++)
  {
    const char sc = sechar[sn];
    for (pagetype pt = odd_page; pt <= last_page; pt = pagetype(pt+1))
    {
      TPrint_section* sec = exist(sc, pt);
      if (sec != NULL)
      {
        const word fields = sec->fields();
        const bool col_wise = sec->columnwise();
        for (word i = 0; col_wise && i < fields; i++)
        {
          TForm_item& fi = sec->field(i);
          if (fi.special_items()>0)
          {
            fi.set_special_value("INTESTAZIONE", fi.col_head());
            fi.set_special_value("OFFSET", format("%d",fi.ofs()));
            TString fnk(fi.finkl()? " " : "X");
            fnk << (fi.finkr() ? " " : "X");
            fi.set_special_value("FINCATURA",fnk);
          }
        }
      }
    }
  }
}

void TForm::set_fincatura(const char* s)
{
  _fink = s; _fink.cut(11);
  printer().set_fincatura(_fink);
}

void TForm::set_fink_mode(bool f)
{   
  printer().set_fink_mode(f);
}

bool TForm::get_fink_mode()
{
  return printer().get_fink_mode();
}

// @cmember Indica se ignorare il prossimo skip del ciclo sul file 
void TForm::match_result(int file,int val)
{
  if (file==0)
    file=cursor()->file().num();
  if (val <0)
    _skip_info.destroy(file);
  else 
  {
    TForm_skip_info info;
    if (next_match_done(file))
      info.match_number=((TForm_skip_info &)_skip_info[file]).match_number;
    if (val) 
      info.match_number++;
    info.match_result=val;
    _skip_info.add(info,file);
  }
}
// @cmember Indica se ignorare (default) o non ignorare il prossimo skip del ciclo sul file
bool TForm::last_match_result(int file)
{
  if (file==0)
    file=cursor()->file().num();
  if (next_match_done(file))
    return ((TForm_skip_info &)_skip_info[file]).match_result;
  return fatal_box("Tryed to get result of a never executed skip in file %d",file); 
}
// @mfunc Memorizza l'avvenuto next_match  sul file passato 
bool TForm::next_match_done(int file)
{
  if (file==0)
    file=cursor()->file().num();
  return _skip_info.objptr(file)!=NULL;
}

// @mfunc Indica l'avvenuto next_match  sul file passato
int TForm::matches_done(int file)
{
  if (file==0)
    file=cursor()->file().num();
  if (next_match_done(file))
    return ((TForm_skip_info &)_skip_info[file]).match_number;
  return 0;
}

TForm::TForm()
{
  init();
  _frompage=_topage=0;
}

TForm::TForm(const char* name, const char* code, int lev, const char* desc)
{
  init();
  read(name, code, lev, desc);
  _frompage=_topage=0;
}

TForm::~TForm()
{
  if (_cursor)
  {
    delete _cursor;
    delete _relation;
    if (_rel_desc)
      delete _rel_desc;
  }
}