#include <defmask.h>
#include <mailbox.h>
#include <msksheet.h>
#include <postman.h>
#include <progind.h>
#include <sheet.h>
#include <recarray.h>
#include <relapp.h>
#include <urldefid.h>
#include <utility.h>

///////////////////////////////////////////////////////////
// TRelation_application
///////////////////////////////////////////////////////////

TRelation_application::TRelation_application()
                     : _mask(NULL), _search_id(-1), _lnflag(0),
                       _autodelete(0), _navigating(false)
{ }

TRelation_application::~TRelation_application()
{ }

TCursor& TRelation_application::get_filtered_cursor() const
{
  TEdit_field& f = get_search_field();
  TCursor* cur = f.browse()->cursor();
  return *cur;
}

void TRelation_application::setkey()
{
  if (has_filtered_cursor())
  {
    get_filtered_cursor().setkey();
    return;  // ?????
  }
  file().setkey(1);
}

void TRelation_application::set_key_filter()
{
  TString rf = get_user_read_filter(); rf.trim();
  if (rf.not_empty())
  {
    TString expr;
    for (int f = _mask->fields()-1; f >= 0; f--)
    {
      TMask_field& fld= _mask->fld(f);
      if (fld.is_edit() && fld.in_key(0))
      {
        TBrowse* b = ((TEdit_field&)fld).browse();
        if (b && b->cursor()->relation()->lfile().num() == get_relation()->lfile().num())
        {
          expr = b->get_filter();
          if (expr.find(rf) < 0)
          {
            if (expr.not_empty())
            {
              expr.insert("(", 0);
              expr << ")&&(" << rf << ')';
            }
            else
              expr = rf;
            b->set_filter(expr);
          }
        }
      }
    }
  }
}


// @doc INTERNAL

// @mfunc Setta i limiti
void TRelation_application::set_limits(
     byte what) // @parm tipo di limite da assegnare al record

// @comm I limiti possibili sono:
// @flag 0 | Nessuna operazione
// @flag 1 | Primo record
// @flag 2 | Ultimo record
// @flag 3 | Entrambi
{
  if (has_filtered_cursor())
  {
    TEdit_field& f = get_search_field();
    TBrowse* b = f.browse();
    TCursor* cur = b != NULL ? b->cursor() : NULL;
    if (cur)
    {
      cur->setkey();
      b->do_input(TRUE);
      if (cur->items() == 0) 
        _first = _last = -1;
      else
      {
        if (what & 0x1)
        {
          *cur = 0;
          _first = cur->file().recno();
        }
        if (what & 0x2)
        {
          *cur = cur->items() - 1;
          _last = cur->file().recno();
        }
      }
      return;
    }
  }

  file().setkey(1);
  if (what & 0x1)
  {
    if (!file().empty() && file().first() == NOERR)
			_first = file().recno();
		else
			_first = -1;
  }
  if (what & 0x2)
  {
    if (!file().empty() && file().last() == NOERR) 
	    _last = file().recno();
		else
			_last = -1;
  }
}

void TRelation_application::set_find_button()
{
  int pos = _mask->id2pos(DLG_FINDREC);
  if (pos >= 0 && _mask->id2pos(DLG_FIRSTREC) >= 0) //se e' un bottone pentapartito...
	{
		TButton_field& f_find = (TButton_field &)_mask->fld(pos);
		RCT rct_base;	f_find.get_rect(rct_base);
    const int bwidth = (rct_base.right - rct_base.left);
    const int bheight = (rct_base.bottom - rct_base.top);
    if (bwidth > 3*bheight/2) // Controllo se ho gia' ridimensionato i bottoni in precedenza
    {
		  int bx = bwidth / 3;
		  int by = bheight / 2;

      RCT r = rct_base; r.left += bx-2;  r.right -= bx-2;
			f_find.set_rect(r); // Ridimensiona il bottone centrale di ricerca
			
      bx += 5; by += 3;   // Aggiusta dimensioni bottoni sussidiari

		  pos = _mask->id2pos(DLG_FIRSTREC);
		  if (pos >= 0)
		  {	
			  r = rct_base; r.top = r.bottom - by;  r.right = r.left + bx;
			  _mask->fld(pos).set_rect(r);
		  }
		  pos = _mask->id2pos(DLG_PREVREC);
		  if (pos >= 0) 
		  {
			  r = rct_base; r.bottom = r.top + by;  r.right = r.left + bx;
			  _mask->fld(pos).set_rect(r);
		  }
		  pos = _mask->id2pos(DLG_NEXTREC);
		  if (pos >= 0) 
		  {
			  r = rct_base; r.bottom = r.top + by;  r.left = r.right - bx;
			  _mask->fld(pos).set_rect(r);
		  }
		  pos = _mask->id2pos(DLG_LASTREC);
		  if (pos >= 0) 
		  {
			  r = rct_base; r.top = r.bottom - by;  r.left = r.right - bx;
			  _mask->fld(pos).set_rect(r);
		  }
    }
	}
}

bool TRelation_application::create()
{
  bool ok = user_create();
  if (ok)
  {
    write_enable();
    _mask = get_mask(MODE_QUERY);

    filter();
    set_key_filter();
    set_limits();
  }
  return ok ? TSkeleton_application::create() : ok;
}


bool TRelation_application::destroy()
{   
  user_destroy();
  return TSkeleton_application::destroy();
}


void TRelation_application::set_fixed()
{
  TToken_string s(256, '=');
  for (const char* f = _fixed.get(0); f && *f; f = _fixed.get())
  {
    s = f;
    const int id = s.get_int(0);
    s = s.get();
    if (s.not_empty())
      _mask->set(id, s);
    
    if (_lnflag < 2)  
      _mask->disable(id);
  }
}


void TRelation_application::enable_query()
{
  const bool query = _mask->query_mode(); 
  const bool keyon = query || get_relation()->status() == _isreinsert;

  for (int i = _mask->fields() - 1; i >= 0; i--)
  {
    TMask_field& c = _mask->fld(i);
    if (c.in_key(0) && c.enabled_default())
    {
      if (c.in_key(1))
        c.enable(keyon);
      if (c.is_edit())
      {
        TEdit_field& e = (TEdit_field&)c;
        if (e.browse() != NULL)
          e.enable_check(query);
      }  
    }
  }

  set_fixed();
}

void TRelation_application::set_toolbar()
{
  const int mode = _mask->mode();
  const bool can_edit_some = user_can_write(NULL);
  
  int pos = _mask->id2pos(DLG_SAVEREC);
  if (pos >= 0)
  {
    bool enabsave=mode != MODE_QUERY;
    if (enabsave)
      enabsave = user_can_write(get_relation());
    _mask->fld(pos).enable(enabsave);
  }
  pos = _mask->id2pos(DLG_DELREC);
  if (pos >= 0) 
  {               
    bool enabdel = (mode == MODE_QUERY && can_edit_some) || (mode == MODE_MOD);
    if (enabdel && mode == MODE_MOD)
    {
      TRelation& r = *get_relation();
      const TRecnotype oldpos = r.lfile().recno();
      enabdel = !protected_record(r);
      if (r.lfile().recno() != oldpos) 
        r.lfile().readat(oldpos);
    }  
    _mask->fld(pos).enable(enabdel);
  }

  pos = _mask->id2pos(DLG_FINDREC);
  if (pos >= 0) 
	{
    _mask->fld(pos).enable(_lnflag == 0);
		
		const long recno = get_relation()->lfile().recno();
		const bool enable_next_prev = _mask->edit_mode();

    pos = _mask->id2pos(DLG_FIRSTREC);
		if (pos >= 0)
			_mask->fld(pos).enable(_lnflag == 0 && (enable_next_prev ? _first != recno : _first > 0));
		pos = _mask->id2pos(DLG_PREVREC);
		if (pos >= 0) 
			_mask->fld(pos).enable(_lnflag == 0 && enable_next_prev && _first > 0 && _first != recno);
		pos = _mask->id2pos(DLG_NEXTREC);
		if (pos >= 0) 
			_mask->fld(pos).enable(_lnflag == 0 && enable_next_prev && _last > 0 && _last != recno);
		pos = _mask->id2pos(DLG_LASTREC);
		if (pos >= 0) 
      _mask->fld(pos).enable(_lnflag == 0 && (enable_next_prev ? _last != recno : _last > 0));
	}

  pos = _mask->id2pos(DLG_NEWREC);
  if (pos >= 0) 
  {
    bool enabins = (mode == MODE_QUERY || _lnflag == 0) && can_edit_some;
    _mask->fld(pos).enable(enabins);
  }

  set_find_button();
  enable_query();
}

void TRelation_application::update_navigation_bar()
{
  if (_mask->query_mode())
  {
    set_limits();
    set_toolbar();
  }
}

bool TRelation_application::save_and_new() const
{ return FALSE; }

int TRelation_application::set_mode(int mode)
{
  static int _mode = NO_MODE;
  if (mode < NO_MODE) mode = _mode;
  
  const int m = ((TMaskmode)mode == NO_MODE) ? (int) MODE_QUERY : mode;
  _mask->set_mode(m);

  set_toolbar();
  _mode = mode;

  const char* t = "";
  switch(mode)
  {
  case MODE_QUERY: 
    t = TR("Ricerca"); break;
  case MODE_MOD: 
    t = TR("Modifica"); break;
  case NO_MODE: 
    t = TR("Ricerca/Inserimento"); break;
  case MODE_INS: 
    t = TR("Inserimento"); break;
  default: 
    break;
  }

  xvtil_statbar_set(t, TRUE);

  return _mode;
}

// @doc INTERNAL

// @mfunc Permette di autonumerare un record
//
// @rdesc Ritorna se e' riuscito a creare una nuova autonumerazione:
//
// @flag TRUE | La chiave non e' vuota
// @flag FALSE | Non e' riuscito ad autonumerare il documento
bool TRelation_application::autonum(
     TMask* m,  // @parm Maschera a cui applicare l'autonumerazione 
     bool rec)  // @parm Indica se registrare la chiave anche sul record corrente
{
  TToken_string k;
  if (!get_next_key(k))
  {
    k = get_next_key();
/*
    if (k.not_empty())
      NFCHECK("La 'const char* get_next_key()' verra' sostituita dalla 'bool get_next_key(TToken_string&)'");
*/
  }
  
  if (!rec && !m->query_mode()) 
    m->reset();
  _renum_message = "";
  
  for (const char* n = k.get(0); n && *n; n = k.get())
  {
    const short id = atoi(n);                                                       
    CHECKD (id > 0 && m->id2pos(id) >= 0, "Identificatore di autonumerazione errato: ", id);
    const char* val = k.get();
    TMask_field& f = m->field(id);
    if (rec || f.empty()) 
      f.set(val);
    if (rec) 
      ((TEditable_field&)f).autosave(*get_relation());
    if (_renum_message.empty() || f.in_key(1))
      _renum_message.format("L'elemento e' stato registrato con :\n  %s = %s", (const char *) f.prompt(), (const char *) f.get());
  }
  return k.not_empty();
}

// @doc EXTERNAL

// @mfunc Entra in modo di ricerca
void TRelation_application::query_mode(
     bool pre_ins)  // @parm Indica in quale modo andare:
      //
      // @flag TRUE | Entra in modo MODE_QUERY_INSERT
      // @flag FALSE | Entra in modo MODE_QUERY (default)
{
  TMask* old = _mask;            
  const bool was_open = old != NULL && old->is_open();
  const bool changing = changing_mask(MODE_QUERY);

  if (changing && was_open)
    old->close_modal();

  _mask = get_mask(MODE_QUERY);
  if (changing)
  {
    if (was_open) 
      _mask->open_modal();
    set_limits();
  }
  
  _mask->set_mode(pre_ins ? MODE_QUERYINS : MODE_QUERY);
  _mask->reset();
  _mask->disable_page(1); // Nasconde pagine inutili

  if (pre_ins)
  {
    set_mode(NO_MODE);
    init_query_insert_mode(*_mask);
		enable_query();
  }
  else
  {
    set_mode(MODE_QUERY);
    init_query_mode(*_mask);
    
    // Aggiorna bottoni di ricerca: utile soprattutto per ve0 che imposta CODNUM
    if (has_filtered_cursor()) 
    {
      set_limits();
      set_toolbar(); 
    }
  }
}


void TRelation_application::insert_mode()
{ 
  bool try_auto = TRUE;

  if (_mask->query_mode())
    try_auto = test_key(1, FALSE) == FALSE;

  if (try_auto && !autonum(_mask, FALSE))
  {
    query_insert_mode();
    return;
  }

  TRelation* r = get_relation();
  for (int f = _mask->fields()-1; f >= 0; f--)
  {
    TMask_field& fld = _mask->fld(f);
    if (fld.is_loadable() && fld.shown() && fld.in_key(0))
      ((TLoadable_field&)fld).autosave(*r);
  }
  if (!user_can_write(r))
  {
    warning_box(FR("L'utente %s non puo' inserire in questo archivio"), 
                        (const char*)user());
    return;
  }

  const bool changing = changing_mask(MODE_INS);
  TFilename workname; workname.temp("msk");
  if (changing)
  {
    _mask->set_workfile(workname);
    _mask->save();
    _mask->close_modal();
  }
  _mask = get_mask(MODE_INS);
  if (changing)
  {
    _mask->reset();
    _mask->set_workfile(workname);
    _mask->load();
    ::remove(workname);
    _mask->open_modal();
  }
  else
    _mask->enable_page(1);

  set_mode(MODE_INS);
  r->zero();          // Azzera tutta la relazione!
  
  init_insert_mode(*_mask);
  
  // ....possibilmente spostare questa chiamata .....
  if (_curr_transaction == TRANSACTION_INSERT)
    ini2insert_mask(); 
}

bool TRelation_application::modify_mode()
{
  TRelation* rel = get_relation();
  if (!user_can_read(rel))
  {
    warning_box(TR("I dati non sono accessibili per l'utente %s"), (const char*)user());
    query_mode();
    return FALSE;
  }

  const TReclock block = user_can_write(rel) ? _testandlock : _nolock;
  int err = rel->read(_isequal, block);
  if (err != NOERR)
  {
    if (err == _islocked)
      message_box(TR("I dati sono gia' usati da un altro programma"));
    else
      error_box(FR("Impossibile leggere i dati: errore %d"), err);
    query_mode();
    return FALSE;
  }

  const bool changing = changing_mask(MODE_MOD);
  if (changing)
    _mask->close_modal();

  _mask = get_mask(MODE_MOD);
  
  if (changing)
    _mask->open_modal();                
  else
    _mask->enable_page(1);
  
  set_mode(MODE_MOD);
                             
  err = read(*_mask);
  if (err != NOERR)
  {
    query_mode();
    return FALSE;
  }

  rel->save_status();
  init_modify_mode(*_mask);

  // ....possibilmente spostare questa chiamata .....
  // Forse deve essere fatta prima della init_modify_mode()!
  if (_curr_transaction == TRANSACTION_MODIFY)
    ini2insert_mask(); 
  return TRUE;
}


TEdit_field& TRelation_application::get_search_field() const
{                      
  short id = _search_id;
                        
  if (id <= 0 || !_mask->field(id).shown())
  {                      
    for (int i = _mask->fields()-1; i >= 0; i--)
    {
      const TMask_field& f = _mask->fld(i);
      if (f.is_edit() && f.in_key(1) && f.shown()) 
      {
        id = f.dlg();
        break;
      }  
    }
  }
  
  return _mask->efield(id);
}

bool TRelation_application::search_mode()
{
  if (_mask->mode() != MODE_QUERY)
    query_mode();

  TEdit_field* prima = &get_search_field();
  while (prima)
  {
    if (prima->on_key(K_F9))
    {
      if (find(1))
        return modify_mode();
      else
        break;  
    }        
    
    TMask_field* dopo = &_mask->focus_field();
    if (dopo != prima && dopo->is_edit() && dopo->in_key(0))
      prima = (TEdit_field*)dopo; 
    else
      break;
  }
  return FALSE;
}

HIDDEN bool delete_handler(TMask_field& f, KEY k)
{
  if (k == K_TAB && f.focusdirty() && !f.empty())
  {
    TMask& m = f.mask();
    const bool finale = *f.prompt() == 'A';
    const int pos = m.id2pos(f.dlg());
    if (pos >= 3)
    {           
      const TMask_field& e = m.fld(pos - 3);
      if (e.is_edit() && e.get().blank())
      {
        const short id = e.dlg() - (finale ? 200 : 100);
        const TRelation_application& app = (TRelation_application&)main_app();
        const TMask_field& orig = app.curr_mask().field(id);
        bool req = orig.required();
        if (!req && orig.is_edit())
        {
          const TEdit_field& e = (TEdit_field&)orig;
          req = e.validate_func() == 12;
        }
        if (req)
        {
          TString str; str << (finale ? TR("A ") : TR("Da ")) << orig.prompt();
          return f.error_box(FR("Specificare anche il valore %s"), (const char*)str);
        }
      }
    }
    if (finale && !f.get().blank())
    {                                       
      const TMask_field& p = m.fld(pos - 1);
      TString80 prec = p.get();
      TString80 curr = f.get();
      bool ok;
      switch (p.class_id())
      {
      case CLASS_REAL_FIELD: ok = real(prec) <= real(curr); break;
      case CLASS_DATE_FIELD: ok = TDate(prec) <= TDate(curr); break;
      default              : ok = prec <= curr; break;
      }
      if (!ok)
        return f.error_box(FR("Inserire un valore non inferiore a '%s'"), (const char*)prec);
    }
  }
  return TRUE;
}

int TRelation_application::delete_mode()
{
  TEdit_field& fld = get_search_field();
  TBrowse* brw = fld.browse(); 
  if (brw)
  {                
    brw->do_input(TRUE);
    TCursor& cur = *brw->cursor();
  
    TToken_string head(brw->head());
    head.insert("@1|", 0); 
    TToken_string items(brw->items());
    items.insert(" |", 0); 
    
    int tab1 = 0, tab2 = 0, y = 0;
    // Nuovo modo basato sugli input
    TToken_string inplist = brw->get_input_fields();
    if (inplist.not_empty())
    {
      FOR_EACH_TOKEN(inplist, tok)
      {
        if (*tok != '"' && strchr(tok, '@') == NULL)
        {
          TMask_field& e = _mask->field(short(atoi(tok)));
          if (e.active())
          {
            const int len = strlen(e.prompt());
            if (len > tab1) tab1 = len;
            const int size = e.size();
            if (size > tab2) tab2 = size;
            y++;
          }
        }
      }
    }
    tab1 += 5;
    tab2 += tab1+2;
    
    cur = 0L;
    TCursor_sheet sht(&cur, items, "Eliminazione", head, 0x4, y);
    
    y = -1;         // Posizione del campo precedente
    TString prompt; // Prompt del campo corrente
    const short delta = 100;

    FOR_EACH_TOKEN(inplist, tok)
    {
      if (*tok != '"' && strchr(tok, '@') == NULL)
      {
        TMask_field& e = _mask->field(short(atoi(tok)));
        if (!e.active())
          continue;
        
        const short id = e.dlg()+delta; 
        prompt = "Da "; prompt << e.prompt();
        sht.add_static(DLG_NULL, 0, prompt, 1, ++y);
        TString16 flags;            
        if (e.automagic())       flags << 'A';
        if (e.roman())           flags << 'M';
        if (e.right_justified()) flags << 'R';
        if (e.uppercase())       flags << 'U';
        if (e.zerofilled())      flags << 'Z';
        switch (e.class_id())
        {
        case CLASS_DATE_FIELD:
          {
            TDate_field& d1 = sht.add_date(id, 0, "", tab1, y, flags);
            TDate_field& d2 = sht.add_date(id+delta, 0, "A ", tab2, y, flags);
            d1.set_handler(delete_handler);
            d2.set_handler(delete_handler);
          }
          break;
        case CLASS_REAL_FIELD:
          {
            TReal_field& r1 = sht.add_number(id, 0, "", tab1, y, e.size(), flags);
            TReal_field& r2 = sht.add_number(id+delta, 0, "A ", tab2, y, e.size(), flags);
            r1.set_handler(delete_handler);
            r2.set_handler(delete_handler);
          }
          break;
        default:
          {
            TEdit_field& e1 = sht.add_string(id, 0, "", tab1, y, e.size(), flags);
            TEdit_field& e2 = sht.add_string(id+delta, 0, "A ", tab2, y, e.size(), flags);
            e1.set_handler(delete_handler);
            e2.set_handler(delete_handler);
          }
          break;
        } 
        if (y == 0)
          sht.first_focus(id);
      }
    }    
    
    sht.open_modal();

    KEY tasto;
    bool keep_running = TRUE;
    while (keep_running)
    {
      tasto = sht.run();
      if (tasto == K_ENTER)
      {
        TRectype rec_from(cur.curr()), rec_to(cur.curr());
        rec_from.zero(); rec_to.zero();
        TToken_string fldlist = brw->get_input_field_names();
        TString80 str;
        int fi = 0;        
        for (TString80 tok = inplist.get(fi); tok.not_empty(); tok = inplist.get(), fi++)
        { 
          const TString16 fn = fldlist.get(fi);
          const TFieldref fr(fn, 0);
          if (*tok == '"')
          {   
            str = tok;
            str.ltrim(1); str.rtrim(1);
            fr.write(str, rec_from);
            fr.write(str, rec_to);
          }
          else
          {
            const short id = short(atoi(tok));
            if (sht.id2pos(id+delta) >= 0)
            {       
              str = sht.get(id+delta);                
              if (str.not_empty())
                fr.write(str, rec_from);
              str = sht.get(id+2*delta);                
              if (str.not_empty())
                fr.write(str, rec_to);
            }
            else
            {
              str = _mask->get(id);
              fr.write(str, rec_from);
              fr.write(str, rec_to);
            }
          }                   
        } 
        
        if (rec_from.empty() && rec_to.empty())
        {   
          sht.check(-1);
        }
        else
        {
          const long totit = cur.items();
          cur.freeze(TRUE);
          cur.curr() = rec_from;
          cur.read();
          while (cur.pos() < totit && cur.curr() <= rec_to)
          {
            sht.check(cur.pos());
            ++cur;
          }
          cur.freeze(FALSE);
        }  
      } 
      else
      {
        keep_running = FALSE;
        if (tasto == K_DEL && sht.checked() == 0)
        {
          error_box(TR("Non e' stato selezionato nessun elemento"));
          sht.select(0);
          keep_running = TRUE;
        }
      }
    }  
    sht.close_modal();
 
    if (tasto == K_DEL)
    { 
      long deleting = sht.checked();
      TString msg;
      msg.format(FR("Confermare l'eliminazione di %d elementi"), deleting);
      bool can_delete = yesno_box(msg);
      if (can_delete && deleting > 100)
      {
        msg.insert(TR("ATTENZIONE: "), 0);
        can_delete = yesno_box(msg);
      }
      if (can_delete)
      {                
        TWait_cursor hourglass;
        long skipped = 0; // Record non cancellati perche' protetti
        cur.freeze(TRUE); // Congelo il cursore altrimenti si riaggiorna troppo       
        for (long pos = sht.items()-1; deleting > 0; pos--)  
        {       
          if (sht.checked(pos))
          {
            cur = pos;
            brw->do_output();
            bool can_delete = FALSE;
            if (find(1))
            {
              TRelation& r = *get_relation();
              _autodelete = 0x3;
              if (!protected_record(r))
              {
                if (modify_mode())
                {
                  r.restore_status();
                  can_delete = remove();  
                }
                query_mode();  
              }                  
              _autodelete = FALSE;
            }
            if (!can_delete)
              skipped++;
            deleting--;
          }
        }  
        cur.freeze(FALSE);
        set_limits(); // Riaggiorno il numero del primo/ultimo record

        if (skipped > 0)
        {
          warning_box(FR("%ld elementi non sono stati cancellati in quanto protetti."), skipped);
          query_mode();  
        }  
      }
    }
  }        
  else
  {
    if (search_mode())
      _autodelete = TRUE;
  }
  return TRUE;
}

// @doc INTERNAL

// @mfunc Controlla se una chiave e' completa ed esiste su file
//
// @rdesc Ritorna se la chave esiste sul file
bool TRelation_application::test_key(
     word k,  // @parm Chiave da ricercare
     bool err)  // @parm Indica se visualizzare eventuali errori occorsi
{
  bool onereq = FALSE, onefill = FALSE;

  for (TEditable_field* e = _mask->get_key_field(k, TRUE);
       e != NULL; 
       e = _mask->get_key_field(k, FALSE))
  {
    if (e->required() && e->shown())
    {
      onereq = TRUE;
      if (e->empty())         
      {
        if (err)
        {                                               
          TString msg(80);
          msg = TR("Manca un valore indispensabile per la ricerca.");
#ifdef DBG
          msg << "\nChiave " << int(k) << " - Campo " << e->dlg(); 
          const TFieldref* fr = e->field();
          if (fr != NULL)
          {
            msg << " - " << fr->name(); 
            if (fr->to() > 0)
              msg << '[' << fr->from() << ',' << fr->to() << ']';
          }    
#endif          
          error_box(msg);
          _mask->first_focus(-e->dlg());
        }
        return FALSE;
      }
    }
    else
      /*      if (k == 1 && !onereq && !onefill && c.get().not_empty()) */
      if (!onereq && !onefill && e->is_edit() && !e->empty()) 
        onefill = TRUE;
  }
  if (k == 1 && !onereq && !onefill)
  {
    if (err)
      error_box(TR("Manca un valore indispensabile per la ricerca"));
    return FALSE;
  }
  return onefill || onereq;
}

bool TRelation_application::find(word k)
{                         
  if (k == 0)
  {
    for (k = 1; k <= MAX_KEYS && !test_key(k, FALSE); k++);
    if (k > MAX_KEYS)
      return test_key(1, TRUE);
  }

  file().setkey(k);
  file().zero();
  for (TEditable_field* e = _mask->get_key_field(k, TRUE); e; e = _mask->get_key_field(k, FALSE))
  {
    if (e->shown())                        // Ignora campi invisibili
      e->autosave(*get_relation());
  }

  const int err = file().read(_isequal);
  return err == NOERR;
}


bool TRelation_application::save(bool check_dirty)
{
  static bool was_dirty = FALSE;
  
  int pos = _mask->id2pos(DLG_SAVEREC);
  if (pos < 0 || !_mask->fld(pos).active()) 
    return TRUE;

  int err = NOERR;
  const int mode = _mask->mode();
  if (check_dirty)
  {
    const short dirty = _mask->dirty();

    if (mode == MODE_QUERY)
    {
      const bool cont = !dirty || yesno_box(TR("Annullare i dati inseriti?"));
      return cont;
    }

    if (!dirty && !was_dirty) 
    {  
      if (mode == MODE_MOD)
      {
        get_relation()->restore_status();
        get_relation()->lfile().reread(_unlock);           // Unlock main file
      }
      return TRUE;
    }
    
    const KEY last = _mask->last_key();
    const bool annulla = last == K_ESC || last == K_QUIT || last == K_F9;
    const bool errore = dirty && _mask->field(dirty).dirty() > 1;
    
    KEY k; 
    if (errore)
    {
      if (annulla) 
      { 
        TString w;
        TMask_field& df = _mask->field(dirty);
        if (df.is_edit())
          w = ((TEdit_field&)df).get_warning();
        if (w.blank()) 
        {
          w = df.prompt();
          if (!w.blank()) 
          {
            w.trim();
            w << ' ' << TR("inconsistente.");
          }
        }
        if (w.blank()) 
          w = TR("Campo inconsistente.");
        w << '\n';
        switch (last)
        {
        case K_ESC:                   
          w << TR("Si desidera annullare?"); break;
        case K_QUIT:
          w << TR("Si desidera uscire?"); break;  
        default:
          w << TR("Si desidera continuare?"); break;
        } 
        k = yesno_box(w) ? K_NO : K_ESC;
        if (k == K_ESC) 
          _mask->first_focus(-dirty);
      }  
      else k = K_ESC;
    }
    else
      k = yesnocancel_box(TR("Si desidera registrare?")); 
    
    if (k == K_ESC || k == K_NO)
    {                   
      if (mode == MODE_MOD)
      {
        get_relation()->restore_status();
        get_relation()->lfile().reread(_unlock);     // Unlock main file
      }  
      was_dirty = FALSE;
      return k == K_NO;
    }        

    if (annulla)
    {
      TMask_field& ff = _mask->focus_field();
      if (ff.focusdirty() && ff.is_edit()) // I need simulate tab on the current field!
      {
        if (!ff.on_key(K_TAB))
        {
          _mask->first_focus(-ff.dlg());
          was_dirty = TRUE; 
          return FALSE;
        }
      }
      if (!_mask->check_fields())                   // Exit with ESC didn't check values
      {                       
        // check_fields sets the focus on the blocking field
        _mask->first_focus(-_mask->focus_field().dlg());
        was_dirty = TRUE; 
        return FALSE;
      } 
    }   
  }
  was_dirty = FALSE;
  
  TWait_cursor hourglass;
  if (mode == MODE_INS)
  {
    bool changed = TRUE;        
    bool changed_key = FALSE;

    while (changed)
    {
      err = write(*_mask);
      if (err == _isreinsert)
      {
        changed = autonum(_mask, TRUE);
        if (!changed) 
        {
          _mask->disable_starting_check();
          enable_query();             // Abilita chiave 1 per rinumerazione manuale
        }
        else
          changed_key = TRUE;
      }  
      else
        changed = FALSE;
    }
    if (err == NOERR)
    {
      if (changed_key)
        message_box(_renum_message);
      get_relation()->save_status();
      set_limits();
      get_relation()->restore_status();
    }
  }
  else 
  {  
    get_relation()->restore_status();
    err = rewrite(*_mask);
  }
  
  switch(err)
  {
  case NOERR:
    _recins = get_relation()->lfile().recno();
    mask2mail(*_mask);
    break;
  case _isreinsert:
    warning_box(TR("Esiste gia' un elemento con la stessa chiave"));
    break;
  default:
    error_box(FR("Impossibile registrare i dati: errore %d"), err);
    break;
  }
  return err == NOERR;
}


int TRelation_application::read(TMask& m)
{
  const TRelation &r = *get_relation();
  const int max = m.fields();
  for (int i = 0; i < max; i++)
  {
    if (m.fld(i).is_sheet()) 
    {
      TSheet_field& f = (TSheet_field&)m.fld(i);
      if (f.record() && !f.external_record()) 
        f.record()->read(*f.putkey(r));
    }
  }
  m.autoload(r);
  return NOERR;
}


int TRelation_application::write(const TMask& m)
{
  TRelation &r = *get_relation();
  m.autosave(r);

  // write relation and all independent sheets 
  int err=r.write();
  const int max = m.fields();
  for (int i = 0; i < max; i++)
  {
    if (m.fld(i).is_sheet()) 
    {
      TSheet_field& f = (TSheet_field& )m.fld(i);
      if (f.record() && !f.external_record())
        err|=f.record()->write(FALSE);
    }
  }
  return err;
}


int TRelation_application::rewrite(const TMask& m)
{
  TRelation& r = *get_relation();
  m.autosave(r);
  // rewrite relation and all independent sheets 
  r.rewrite();
  int err=r.status();
  const int max = m.fields();
  for (int i = 0; i < max; i++)
  {
    if (m.fld(i).is_sheet()) 
    {
      TSheet_field& f = (TSheet_field&)m.fld(i);
      if (f.record()&& !f.external_record())
        err|=f.record()->write(TRUE);
    }
  }
  return err;
}

// @doc INTERNAL

// @mfunc Cancella il record corrente
//
// @rdesc Ritorna se il record e' stato eliminato
bool TRelation_application::relation_remove()
// @comm Se la maschera e' in MODE_MOD non e' possibile cancellare il record e viene
//   emesso un <f CHECK> di errore.
{
  CHECK(_mask->edit_mode(), "You can call remove in edit mode only");

  TRelation& r = *get_relation();
  r.restore_status();
  if (protected_record(r))
    return message_box(TR("Elemento non eliminabile"));

  if (_curr_transaction == TRANSACTION_DELETE || yesno_box(TR("Confermare l'eliminazione")))
  {
    r.restore_status();
    const bool ok = remove();
    if (ok || is_transaction())
      set_limits();
    else
    {
      const int err = r.status();
      if (err != NOERR) // Succede nei remove con richiesta di conferma all'utente
        return error_box(FR("Errore di cancellazione %d"), err);
    }
  }
  return TRUE;
}


bool TRelation_application::protected_record(TRelation &r) 
{
  if (user_can_delete(&r))
    return protected_record(r.curr());
  return true;
}

bool TRelation_application::remove()
{
  int err = get_relation()->remove();
  if (err == NOERR)
  {
    const int maxf = _mask->fields();
    for (int i = 0; i < maxf; i++)
    {
      const TMask_field& mf = _mask->fld(i);
      if (mf.is_sheet()) 
      {
        TSheet_field& f = (TSheet_field&)mf;
        if (f.record()&& !f.external_record())
          err |= f.record()->remove();
      }
    }
  }
  if (err == NOERR)
  {
    _mask->set_mode(NO_MODE);
    mask2mail(*_mask);
  }
  return err == NOERR;
}

bool TRelation_application::firm_change_enabled() const
{
  bool ok = TApplication::firm_change_enabled();
  ok &= (_mask == NULL || _mask->query_mode()) && _lnflag == 0;
  return ok;
}

void TRelation_application::main_loop()
{ 
  KEY k;
  do { 
    // ciclo delle transazioni
    _recins = -1;
    
    // imposta la maschera in query mode
    query_mode();
    _mask->open_modal();
    
    // Provoca l'autopremimento per il messaggio di LINK
    if (_lnflag)
    {
      if (_trans_counter < _ntransactions)
      {
        // la transazione � sul .ini : la imposta nelle variabili _curr_transaction...
        if (load_transaction())
        {
          // la transazione necessita di autopremimento
          _autodelete = _curr_transaction == TRANSACTION_DELETE;
          if (_curr_transaction == TRANSACTION_INSERT )
            _mask->send_key(K_CTRL+'N', 0);
          else 
            _mask->send_key(K_AUTO_ENTER, 0);
        } 
      } 
      else // la transazione non � sul .ini 
      {
        _mask->send_key(K_AUTO_ENTER, 0);
      }
    }

    if (is_transaction())  
      ini2query_mask();
    
    do
    {
      const bool change = firm_change_enabled();
      // Dis/abilita cambio ditta
      enable_menu_item(M_FILE_NEW, change);
      
      if (_mask->edit_mode())
      {
        if (_autodelete)
        {                                
          const int pos = _mask->id2pos(DLG_DELREC);
          if (pos >= 0 && _mask->fld(pos).active())
            _mask->send_key(K_CTRL+'E', 0);    
          else
            error_box(TR("Elemento non eliminabile."));
          _autodelete = FALSE;
        } 
      }
  
      k = _mask->run();
  
      switch (k)
      {
      case K_ESC:
        if (save(TRUE))
          query_mode();
        if (_lnflag) 
          k = K_QUIT;
        break;                             
      case K_QUIT:      
        if (!save(TRUE))
          k = K_ENTER;
        break;
      case K_ENTER:
        if (_lnflag && _curr_transaction != TRANSACTION_RUN)
        {     
          if (find(1))
          {
            if (_curr_transaction == TRANSACTION_INSERT)
              _curr_transaction = TRANSACTION_MODIFY;
            modify_mode();
          }
          else       
          { 
            if (_curr_transaction == TRANSACTION_MODIFY)
              _curr_transaction = TRANSACTION_INSERT;
            insert_mode();
          }
        }
        else
        {
          if (find(0)) 
            modify_mode();
          else 
            insert_mode();
        }
        if (_curr_trans_mode == TM_AUTOMATIC)
          _mask->send_key(K_CTRL+'R', 0);    
        break;
      case K_SAVE:
        if (save(FALSE))
        {
          if ((_autoins_caller.not_empty() || is_transaction()) && _curr_trans_mode != TM_REMAIN)
          {
            k = K_QUIT;
          }
          else
          {
            if (save_and_new())
            {
              if (_mask->insert_mode())
                insert_mode();
              else
                query_mode();
            }  
            else  
              modify_mode();
          }  
        }
        break;
      case K_INS:
        if (_mask->query_mode() || save(TRUE))
        {
          const bool trovato = _mask->query_mode() && test_key(1, FALSE) && find(1);
          if (trovato)
          { 

            if (is_transaction())
              _curr_transaction=TRANSACTION_MODIFY;
            else
              warning_box(TR("Elemento gia' presente"));
            modify_mode();

          }
          else
            insert_mode();
        }  
        if (_curr_trans_mode == TM_AUTOMATIC)
          _mask->send_key(K_CTRL+'R', 0);    
        break;
      case K_DEL:
        if (_mask->query_mode())
        {  
          delete_mode();           
        }
        else
        {
          if (relation_remove())
          {
            query_mode();
            if (_autoins_caller.not_empty() || is_transaction())
            {    
              if (_lnflag) _recins = 0;
              k = K_QUIT;
            }  
          }
        }
        break;
      case K_F9:   
        if (_mask->query_mode() || save(TRUE))
          search_mode();
        break;
      default:
        if (save(TRUE))
        {
          setkey();
          int err = ~NOERR;
          switch (k)
          {
          case K_HOME:
            err = file().readat(_first, _testandlock);
            break;
          case K_NEXT:
            err = file().reread();
            if (has_filtered_cursor())
            {
              TCursor& cur = get_filtered_cursor();
              cur.curr() = file().curr();
              cur.read();
              ++cur;
              file().curr() = cur.curr();
              err = get_relation()->read(_isequal, _testandlock);
            }
            else
              err = file().next(_testandlock);
            break;
          case K_PREV:
            file().reread();
            if (has_filtered_cursor())
            {
              TCursor& cur = get_filtered_cursor();
              cur.curr() = file().curr();
              cur.read();
              --cur;
              file().curr() = cur.curr();
              err = get_relation()->read(_isequal, _testandlock);
            }
            else
              err = file().prev(_testandlock);
            break;
          case K_END:
            err = file().readat(_last, _testandlock);
            break;
          default:
            break;
          }
          if (err == NOERR || err == _islocked) 
          {
            _navigating = true;
            modify_mode();
            _navigating = false;
          }
          else 
            query_mode();
        }
        break;
      }
    } while (k != K_QUIT);

    if (_mask->is_open())
      _mask->close_modal();

    _mask->set_mode(NO_MODE);

    if (autoins_caller().not_empty() && _recins >= 0)
    { 
      NFCHECK("Obsolete LINK message calling convention");
      TString16 num; 
      num.format("%ld", _recins);
      TMessage msg(autoins_caller(), _lnflag ? MSG_LN : MSG_AI, num);
      msg.send();
    }
    
    if (is_transaction())
    { 
      TConfig ini(_trans_ini.row(_trans_counter), "Transaction");
      ini.set("Record", _recins);
      if (_recins >= 0)
      {
        ini.set("Result", "OK");
        ini.set("Error", "0");
        edit_mask2ini();
      }
      else
      {                    
        const int err = get_relation()->status();
        ini.set("Result", err == NOERR ? "CANCEL" : "ERROR");
        ini.set("Error", err);
      }   
    
    }
    _trans_counter++;
  } while ( _trans_counter < _ntransactions);
}

bool TRelation_application::filter()
{                          
  if (parse_command_line())
    return TRUE;

  TMailbox mail;
  TMessage* msg = mail.next_s(MSG_FS);

  if (msg)
  {
    _mask = get_mask(MODE_MOD);
    TToken_string body(msg->body());

    short id = body.get_int();
    while (id > 0)
    {
      _search_id = id;
      TEdit_field& f = (TEdit_field&)_mask->field(id);
      TCursor* cur = f.browse()->cursor();
      TRectype& rec = cur->curr();
      rec.zero();
      
      TString80 t;
      const char* s;
      while((s = body.get()) != NULL)
      {
        t = s;
        const int u = t.find('=');
        if (u < 0)
        {
          id = atoi(t);
          break;
        }
        _fixed.add(t);
        const short fid = atoi(t.left(u));
        const TFieldref* campo = _mask->field(fid).field();
        if (campo != NULL)
          campo->write(t.mid(u+1), rec);
      }
      cur->setfilter("");
      cur->setregion(rec, rec, 0x2);
      if (s == NULL) id = 0;
    }
  }

  mail.restart();
  msg = mail.next_s(MSG_AI);
  if (msg) _autoins_caller = msg->from();

  mail.restart();
  msg = mail.next_s(MSG_LN);
  if (msg) 
  {
    TToken_string body(msg->body());
    const int key = body.get_int();
    
    _autoins_caller = msg->from();
    _lnflag = TRUE;
    
    TString str, tmp;
    const char* v = body.get();
    for (int i = 0; v != NULL && i < _mask->fields(); i++)
    {
      TMask_field& f = _mask->fld(i);
      if (f.active() && f.dlg() > 0 && f.in_key(key))
      {        
        str = v;
        tmp.format("%d=", f.dlg());
        str.insert(tmp, 0);
        _fixed.add(str);
        v = body.get();
      }
    }
  }

  mail.restart();
  msg = mail.next_s(MSG_ED);
  if (msg) 
  {
    TToken_string body(msg->body());
    const int key = body.get_int();
    
    _autoins_caller = msg->from();
    _lnflag = 2;       

    TAssoc_array field_values;
    const char * s;
    TString t, v;
    
    while((s = body.get()) != NULL)
    {  
      t = s;
      const int u = t.find('=');
      
      CHECKS(u > 0, "Invalid edit message ", (const char *) body);
      if (u > 0)
      {                       
        v = t.mid(u + 1);
        
        t.cut(u);
        field_values.add(t, v);
      } 
    }
      
    for (int i = 0; i < _mask->fields(); i++)
    {
      TMask_field& f = _mask->fld(i);
      const TFieldref * field = f.field();
      
      if (field && f.in_key(key))
      {                                       
        TString16 field_name(field->name()); 
        const int from = field->from();
        const int to = field->to();
        
        if (to >= 0)
          field_name << "[" << (from + 1);
        const TString * v = (const TString *) field_values.objptr(field_name);
        
        TString val;
        if (v == NULL && to >= 0)
        {
          v = (const TString *)field_values.objptr(field->name());
          if (v) 
            val = v->sub(from, to);
        }
        else
          if (v) val = *v;   
        
        if (v)
        {
          t.format("%d=", f.dlg());
          val.insert(t, 0);
          _fixed.add(val);
        }  
      }
    }
  }

  return TRUE;
}

void TRelation_application::set_link(TMask & m, const char * keyexpr)
{
  CHECK(keyexpr != NULL, "Invalid expression");
  TToken_string body(keyexpr);
  const int key = body.get_int();
  
  _lnflag = TRUE;
  
  const char* v = body.get();
  
  TString16 tmp;                      
  const int max = m.fields();
  for (int i = 0; i < max && v != NULL; i++)
  {
    TMask_field& f = m.fld(i);
    
    if (f.active() && f.dlg() > 0 && f.in_key(key))
    {                          
      TString s(v);  
      tmp.format("%d=", f.dlg());
      s.insert(tmp, 0);
      _fixed.add(s);
      v = body.get();
    }
  }
}


bool TRelation_application::parse_command_line() 
{         
  _trans_ini.destroy(); 
  _trans_counter=0;
  _curr_transaction = "";
  _curr_trans_mode = 'I';
  
  TFilename ini;
  for (int i = argc()-1; i > 0; i--)
  {
    ini = argv(i);
    if ((ini[0] == '-' || ini[0] == '/') && (ini[1] == 'I' || ini[1] == 'i'))
    {
      ini.ltrim(2);
      CHECK(!ini.blank(),"Manca l'indicazione della transazione. Il nome va indicato di seguito al -i, senza interporre spaziatura.");
      if (ini.find('*')>=0)
      {
        // metachars:
        list_files(ini, _trans_ini);
      }
      else
        _trans_ini.add(ini);
      break;
    }
  } 
  
  _ntransactions= _trans_ini.items();
  
  _lnflag = _ntransactions>0;
  return _lnflag != 0;
}                         

// il valore di ritorno  indica se attivare l'"automagia" (spedizione dei tasti e 
// precaricamento della maschera)
bool TRelation_application::load_transaction() 
{
  bool retv = FALSE;
  if (_trans_counter < _ntransactions)
  {
    TConfig cnf(_trans_ini.row(_trans_counter), "Transaction");
    _curr_transaction = cnf.get("Action");
    _curr_transaction.upper();
    _curr_trans_mode = toupper(cnf.get("Mode")[0]);
    _curr_trans_from = cnf.get("From");  
    const long firm = cnf.get_long("Firm");
    if (firm > 0)    
    {
      bool ok = set_firm(firm);
      if (ok) 
        _mask->on_firm_change();
      else
        error_box(FR("La ditta %ld non esiste"), firm);
    }
    if (_curr_transaction == TRANSACTION_RUN)
      retv= FALSE;    // Ho gia' finito qui: basta il cambio ditta
    else
      retv = TRUE;    // Attiva automagia
  }
  return retv;
}

void TRelation_application::on_firm_change()
{
  TApplication::on_firm_change();
  if (_mask != NULL)
  {
    set_limits(0x3);
  }
}

void TRelation_application::ini2query_mask() 
{            
  if (is_transaction())
  {         
    TString8 n; n.format("%d", get_relation()->lfile().num());
    TConfig ini(_trans_ini.row(_trans_counter), n);
    ini2mask(ini, *_mask, TRUE);
  }     
}      

void TRelation_application::ini2insert_mask() 
{
  if (is_transaction())
  {        
    TString8 n; n.format("%d", get_relation()->lfile().num());
    TConfig ini(_trans_ini.row(_trans_counter), n);
    ini2mask(ini, *_mask, FALSE);
  }     
}
 
void TRelation_application::ini2mask(TConfig& ini, TMask& m, bool query)
{
  const TString16 defpar = ini.get_paragraph();
  for (int f = m.fields()-1; f >= 0; f--)
  {
    TMask_field& campo = m.fld(f);
    const TFieldref* fref = campo.field();
    if (fref)
    {
      if (!query || campo.in_key(0))
      {
        const TString& str = fref->read(ini, defpar);
        if (str.not_empty())
          campo.set(str);
      }
    }
    else 
    {
      if (!query && campo.is_sheet())
      {
        TSheet_field &sheet=(TSheet_field &)campo;
        ini2sheet(ini, sheet);
      }
    }
  }
  ini.set_paragraph(defpar);
}

void TRelation_application::edit_mask2ini() 
{            
  if (_trans_ini.row(_trans_counter).not_empty())
  {         
    TString8 head; head.format("%d", get_relation()->lfile().num());
    TConfig ini(_trans_ini.row(_trans_counter), head);
    mask2ini(*_mask, ini);
  }     
}      


void TRelation_application::ini2sheet(TConfig& ini,TSheet_field &sheet)
{
  if (sheet.record() != NULL)
  {
    const int lognum = sheet.record()->logic_num();
    const TMask& sm = sheet.sheet_mask();

    // scrive le righe nello sheet associato
    TString16 defpar;
    
    for (int r = 1; ;r++)
    {
      defpar.format("%d,%d", lognum, r);
      if (ini.set_paragraph(defpar))
      {
        TToken_string& row = sheet.row(r-1);
        for (int sf = sm.fields()-1; sf >= 0; sf--)
        {
          TMask_field& campo = sm.fld(sf);
          const TFieldref* fref = campo.field();
          if (fref)
          {     
            const TString& str = fref->read(ini, defpar);
            row.add(str, sheet.cid2index(campo.dlg()));
          } 
        }
        sheet.check_row(r-1);
      }
      else
        break;
    }
  }
}

void TRelation_application::sheet2ini(TSheet_field &sheet,TConfig& ini)
{
  if (sheet.record() != NULL)
  {
    const int lognum = sheet.record()->logic_num();
    const TMask& sm = sheet.sheet_mask();

    // scrive le righe degli sheet associati
    TString16 defpar;
    TString str;
    int r;
    
    for (r = 1 ; r <= sheet.items(); r++)
    {
      defpar.format("%d,%d", lognum, r);
  
      TMask_field* fkey;
      sheet.restart_key();
      while ((fkey = sheet.get_key(str)))
      {
        ini.set(str, fkey->get(), defpar);
      }        
      
      TToken_string& row = sheet.row(r-1);
      const char* value;
      int i;
      for (i = 0, value = row.get(0); value; i++, value = row.get())
      {
        const TMask_field& campo = sm.field(FIRST_FIELD+i);
        const TFieldref* fr = campo.field();
        if (fr)
        {
          if (value == NULL || *value == '\0') 
            value = " ";
          // ini.set(fr->name(), value, defpar);
          fr->write(ini, defpar, value);
        } 
      } 
    } 
    for (r = sheet.items()+1; ; r++)
    {
      defpar.format("%d,%d", lognum, r);
      if (ini.set_paragraph(defpar))
        ini.remove_all();
      else
        break;  
    }
  }
} 

void TRelation_application::mask2ini(const TMask& m, TConfig& ini)
{
  ini.set("Firm", get_firm(), "Transaction");

  int year, release, tag, patch;
  if (get_version_info(year, release, tag, patch))
  {
    TString80 ver;
    ver.format("%d %d.%d-%d", year, release, tag, patch);
    ini.set("Version", ver);
  }

  const TLocalisamfile& lfile = get_relation()->lfile();
  
  TString16 defpar; 
  defpar.format("%d", lfile.num());
  ini.set_paragraph(defpar);
  switch (lfile.num())
  {
    case LF_TAB:
    case LF_TABCOM:
    case LF_TABGEN:
    {          
      const TString& tabname = lfile.curr().get("COD");
      ini.set("COD", tabname, defpar);
    }
    break;
  }
  for (int f = 0; f < m.fields(); f++)
  {
    TMask_field& campo = m.fld(f);
    if (campo.shown())
    { 
      const TFieldref* fr = campo.field();
      if (fr)
      {                
        if (campo.empty())
          fr->write(ini, defpar, " ");
        else
        {
          if (campo.class_id() == CLASS_DATE_FIELD && campo.right_justified())
          {
            const TDate d(campo.get());
            fr->write(ini, defpar, d.string(ANSI));
          }
          else
            fr->write(ini, defpar, campo.get());
        }
      } 
      else 
        if (campo.is_sheet())
        {
          TSheet_field &sheet=(TSheet_field &)campo;
          sheet2ini(sheet,ini);  // It's virtual
        }
    }
  } 
  ini.set_paragraph(defpar); // Reimposta paragrafo standard
}

bool TRelation_application::mask2mail(const TMask& m)
{
  TWait_cursor hourglass;
  bool ok = _curr_trans_from.empty() && ::can_dispatch_transaction(get_relation()->curr());   
  if (ok)
  {
    TFilename ininame; ininame.temp();
    if (ok) // Test qualunque per usare {}
    {
      TConfig ini(ininame, "Transaction");
      const char* action = "";
      char mode[2] = { TM_AUTOMATIC, '\0' };
      switch (m.mode())
      {
      case NO_MODE:
        action = TRANSACTION_DELETE;
        break;
      case MODE_MOD:
        action = TRANSACTION_MODIFY;
        break;
      default:
        action = TRANSACTION_INSERT;
        break;
      }
      ini.set("Action", action);
      ini.set("Mode", mode);
      mask2ini(m, ini);
    }
    ok = ::dispatch_transaction(get_relation()->curr(), ininame);
    ::remove(ininame);
  }
  return ok;
}