#include <stdlib.h>

#include <applicat.h>
#include <colors.h>
#include <controls.h>
#include <execp.h>
#include <mailbox.h>
#include <progind.h>
#include <relation.h>
#include <urldefid.h>
#include <utility.h>
#include <varmask.h>

#include "cg3.h"
#include "cglib.h"

#include "cg3600.h"

#include "causali.h"
#include "mov.h"
#include "pconti.h"
#include "rmov.h"
#include "saldi.h"

///////////////////////////////////////////////////////////
// TList
///////////////////////////////////////////////////////////

class TList : public TContainer
{           
  enum { MAX_SKIP = 8192 };

  struct TList_object : public TObject
  { 
    TObject* _obj;
    TList_object* _next;
    TList_object* _prev;
  };

  TList_object** _skip;
  long _items, _current;
  long _step, _last_skip;

protected:
  // @cmember Ritorna un puntatore al primo oggetto del contenitore
  virtual TObject* first_item( );
  // @cmember Ritorna un puntatore all'ultimo oggetto del contenitore
  virtual TObject* last_item( );
  // @cmember Ritorna un puntatore all'oggetto successivo all'oggetto corrente
  virtual TObject* succ_item( );
  // @cmember Ritorna un puntatore all'oggetto che precede l'oggetto corrente
  virtual TObject* pred_item( );
  // @cmember Ritorna il numero di oggetti nel contenitore
  virtual long objects( );

protected:
  TList_object* detach_lstobj(long index);
  TList_object* lstobjptr(long index) const;
  
public:
  long items() const { return _items; }
  
  TObject* objptr(long index) const;
  
  TObject& obj(long index)
  { TObject* o = objptr(index); CHECK(o, "Null list item"); return *o; }

  const TObject& obj(long index) const
  { const TObject* o = objptr(index); CHECK(o, "Null list item"); return *o; }
  
  void destroy();
  long insert(TObject* obj, long pos);
  long append(TObject* obj, long pos = -1);
  
  TObject* detach(long pos);
  bool remove(long pos);
  
  void change_step(long step);          // Forza il passo per gli elementi
  long choose_step(long expected_size); // Sceglie il passo per gli elemnti

  TList(long expected_size = 128L);
  virtual ~TList();
};

TList::TList(long expected_size)
          : _items(0), _current(0)
{
  _skip = new TList_object*[MAX_SKIP+1];
  _skip[0] = NULL;
  choose_step(expected_size);
}

TList::~TList()
{
  destroy();
  delete [] _skip;
} 

long TList::objects( )
{
  return _items;
}

TObject* TList::first_item()
{
  return objptr(_current = 0);
}

TObject* TList::succ_item()
{
  return objptr(++_current);
}

TObject* TList::pred_item()
{
  return objptr(_current++);
}

TObject* TList::last_item()
{
  return objptr(_current = _items-1);
}

void TList::destroy() 
{
  TList_object* head = _skip[0];
  while (head)
  {
    TList_object* next = head->_next;
    if (head->_obj)
      delete head->_obj;
    delete head;
    head = next;
  }
  _items = _current = _last_skip = 0;
  _skip[0] = NULL;
}

TList::TList_object* TList::lstobjptr(long index) const
{
  TList_object* lstobj = NULL;
  const ldiv_t p = ldiv(index, _step);
  if (p.quot >= 0 && p.quot < _last_skip)
  {
    TList_object* lo = _skip[p.quot];
    for (long s = p.rem; lo && s > 0; s--)
      lo = lo->_next;

    lstobj = lo;
  }
  return lstobj;
}

TObject* TList::objptr(long index) const
{
  const TList_object* lo = lstobjptr(index);
  return lo ? lo->_obj : NULL;
}

void TList::change_step(long step)
{
  CHECKD(step > 0 && step <= 16384, "Bad list step ", step);
  _step = step;
  _last_skip = 0;
  
  step = 0;       
  for (TList_object* lo = _skip[0]; lo; lo = lo->_next, step--)
  {
    if (step == 0)
    {
      CHECK(_last_skip < MAX_SKIP, "Too many items");
      _skip[_last_skip++] = lo;
      step = _step; 
   }
  }
}

long TList::choose_step(long expected_size)
{   
  long step = expected_size / MAX_SKIP + 1;
  if (step < 8)
    step = 8;
  change_step(step);
  return step;
}

long TList::insert(TObject* obj, long index)
{
  if (index < 0)
    index = 0;
  else  
  {
    if (index > _items) 
      index = _items;
  }    
  const long pred = index-1;
  
  TList_object* newobj = new TList_object;
  newobj->_obj = obj;  
  
  if (pred < 0)
  {               
    newobj->_prev = NULL;
    newobj->_next = _skip[0];
    if (_skip[0])
      _skip[0]->_prev = newobj;
  }  
  else
  {
    TList_object* lo = lstobjptr(pred);
    CHECK(lo, "NULL insertion point");
    newobj->_next = lo->_next;
    newobj->_prev = lo;
    if (lo->_next)
      lo->_next->_prev = newobj;
    lo->_next = newobj;
  }
  _items++;
  
  ldiv_t p = ldiv(index, _step);
  if (p.rem == 0 && p.quot < MAX_SKIP)    // Cambia il capolista
    _skip[p.quot] = newobj;

  for (long i = p.quot + 1; i < _last_skip; i++)
    _skip[i] = _skip[i]->_prev;           // Aggiorna capilista successivi
  
  // Siamo andati oltre l'ultimo skip
  const long s = (_items - 1) / _step + 1; 
  if (s > _last_skip)
  {
    if (s > MAX_SKIP)
      change_step(_step+1);
    else
    {
      if (_last_skip > 0)
      {  
        TList_object* lo = _skip[_last_skip-1];
        while (lo->_next != NULL)
          lo = lo->_next;
        _skip[_last_skip++] = lo;
        _skip[_last_skip] = NULL;
      }
      else
        _last_skip = 1;
    }  
  }   
  
  return index;
}

long TList::append(TObject* obj, long index)
{
  if (index < 0 || index >= _items) 
    index = _items;
  else
    index++;
  return insert(obj, index);  
}

TList::TList_object* TList::detach_lstobj(long index)
{
  TList_object* lo = lstobjptr(index);
  if (lo)
  {
    if (lo->_prev)
      lo->_prev->_next = lo->_next;
    if (lo->_next)
      lo->_next->_prev = lo->_prev;
    ldiv_t res = ldiv(index, _step);
    if (res.rem == 0)
      _skip[res.quot] = lo->_next;
    long p;
    for (p = res.quot + 1; p < _last_skip; p++)
      _skip[p] = _skip[p]->_next;
      
    _items--;
  
    p = (_items - 1) / _step + 1; 
    if (p < _last_skip)
    {                 
      _last_skip--;
      _skip[_last_skip] = NULL;
    }  
  } 
  
  return lo;
}

TObject* TList::detach(long index)
{
  TList_object* lo = detach_lstobj(index);
  TObject* obj;
  if (lo)
  {
    obj = lo->_obj;
    delete lo;
  }  
  else
    obj = NULL;
  return obj;
}

bool TList::remove(long index)
{
  TObject* o = detach(index);
  if (o)
    delete o;
  return o != NULL;
}   

///////////////////////////////////////////////////////////
// TBalance
///////////////////////////////////////////////////////////

class TBalance : public TObject
{           
  TImporto _saldo_ini, _progr_dare, _progr_avere, _saldo_fin;

protected:                
  bool find(const TBill& b, int esercizio, 
            TImporto& si, TImporto& da, TImporto& av, TImporto& sf) const;

public:
  void read(int g, int c, long s, int esercizio, bool ignora_movap = FALSE);
  void read(const TBill& b, int esercizio, bool ignora_movap = FALSE);
  void reread();
  
  const TImporto& saldo_iniziale() const;
  const real& progressivo_dare_iniziale() const;
  const real& progressivo_avere_iniziale() const;
  real progressivo_dare_finale() const;
  real progressivo_avere_finale() const;
  TImporto saldo_finale(bool chiusura = FALSE) const;
  TImporto saldo_finale_chiusura() const;

  TBalance();
  TBalance(int g, int c, long s, int esercizio, bool ignora_movap = FALSE);
  TBalance(const TBill& b, int esercizio, bool ignora_movap = FALSE);
  virtual ~TBalance() { }
};

TBalance::TBalance()
{
}

TBalance::TBalance(int g, int c, long s, int esercizio, bool ignora_movap)
{
  read(g, c, s, esercizio, ignora_movap);
}

TBalance::TBalance(const TBill& b, int esercizio, bool ignora_movap)
{
  read(b, esercizio, ignora_movap);
}

bool TBalance::find(const TBill& b, int esercizio, 
                    TImporto& si, TImporto& da, TImporto& av, TImporto& sf) const
{   
  CHECK(b.sottoconto() > 0L, "Sottoconto mancante");
  
  TLocalisamfile saldi(LF_SALDI);
  TRectype& curr = saldi.curr();
  curr.put(SLD_GRUPPO, b.gruppo());
  curr.put(SLD_CONTO, b.conto());
  curr.put(SLD_SOTTOCONTO, b.sottoconto());
  curr.put(SLD_ANNOES, esercizio);
  curr.put(SLD_FLSCA, FALSE);
  const bool ok = saldi.read() == NOERR;
  if (ok)
  {
    si.set(curr.get_char(SLD_FLAGSALINI), curr.get_real(SLD_SALDO));
    da.set('D', curr.get_real(SLD_PDARE));
    av.set('A', curr.get_real(SLD_PAVERE));
    sf.set(curr.get_char(SLD_FLAGSALFIN), curr.get_real(SLD_SALDOFIN));
  } 
  else
  {
    si.set('D', ZERO);
    da = av = sf = si;
  }
  return ok;
}                    


void TBalance::read(int gruppo, int conto, long sottoconto, int esercizio, bool ignora_movap)
{
  const TBill zio(gruppo, conto, sottoconto);
  read(zio, esercizio, ignora_movap);
}

void TBalance::read(const TBill& b, int esercizio, bool ignora_movap)
{
  TImporto si, sf, pd, pa;
  
  find(b, esercizio, si, pd, pa, sf);
  _saldo_ini   = si;
  _progr_dare  = pd;
  _progr_avere = pa;
  _saldo_fin   = sf;

  if (_saldo_ini.is_zero())
  {
    TLocalisamfile pcon(LF_PCON);
    TRectype& curr = pcon.curr();
    curr.put(PCN_GRUPPO, b.gruppo());
    curr.put(PCN_CONTO, b.conto());
    const int err = pcon.read();
    CHECK(err == NOERR, "Impossibile stabilire indicatore di bilancio");
    const int indbil = curr.get_int(PCN_INDBIL);
    if (indbil == 1 || indbil == 2 || indbil == 5)
    {                                                 
      TEsercizi_contabili esercizi;
      const int precedente = esercizi.pred(esercizio);
      if (find(b, precedente, si, pd, pa, sf))
      {
        _saldo_ini = si;
        _saldo_ini += pd;
        _saldo_ini += pa;
      }  
    }  
  }
  else
  {
    if (ignora_movap)
      _saldo_ini.set('D', ZERO);
  }
}

const TImporto& TBalance::saldo_iniziale() const
{ return _saldo_ini; }

const real& TBalance::progressivo_dare_iniziale() const
{
  return _saldo_ini.sezione() == 'D' ? _saldo_ini.valore() : ZERO;
}

const real& TBalance::progressivo_avere_iniziale() const
{
  return _saldo_ini.sezione() == 'A' ? _saldo_ini.valore() : ZERO;
}

real TBalance::progressivo_dare_finale() const
{
  real pd = progressivo_dare_iniziale();
  pd += _progr_dare.valore();
  if (_saldo_fin.sezione() == 'D')
    pd += _saldo_fin.valore();
  return pd;  
}

real TBalance::progressivo_avere_finale() const
{
  real pa = progressivo_avere_iniziale();
  pa += _progr_avere.valore();
  if (_saldo_fin.sezione() == 'A')
    pa += _saldo_fin.valore();
  return pa;  
}

TImporto TBalance::saldo_finale(bool chiusura) const
{
  TImporto sf(_saldo_ini);
  sf += _progr_dare;
  sf += _progr_avere;
  if (chiusura) 
    sf += _saldo_fin;
  return sf;  
}

TImporto TBalance::saldo_finale_chiusura() const
{ 
  return saldo_finale(TRUE); 
}

///////////////////////////////////////////////////////////
// TMastrino
///////////////////////////////////////////////////////////

enum tipo_riga_mastrino { riga_mastrino, riga_contropartita };

class TRiga_mastrino : public TObject
{            
  tipo_riga_mastrino _type; // Tipo della riga
  TRecnotype _mov, _rmov;   // Numero fisico di record movivento e riga movimento
  real _dare, _avere;       // Progressivi dare ed avere
  TDate _data;              // Data di registrazione (Ottimizzazione)
  
public:  
  tipo_riga_mastrino tipo() const { return _type; }
  TRecnotype rmov() const { return _rmov; }
  TRecnotype mov() const { return _mov; }
  
  const TDate& data() const { return _data; }  
  const real& dare() const { return _dare; }
  const real& avere() const { return _avere; }
  TImporto saldo() const;           // Dare-Avere normalizzato
  
  TRiga_mastrino(tipo_riga_mastrino trig, 
                 TRecnotype rmov, TRecnotype mov,
                 const real& d, const real& a,
                 const TDate& datareg);
  virtual ~TRiga_mastrino() { }
};

TRiga_mastrino::TRiga_mastrino(tipo_riga_mastrino trig, 
                               TRecnotype rmov, TRecnotype mov,
                               const real& d, const real& a,
                               const TDate& datareg)
              : _type(trig), _rmov(rmov), _mov(mov), 
                _dare(d), _avere(a), _data(datareg) 
{ }

TImporto TRiga_mastrino::saldo() const
{
  TImporto imp('D', _dare - _avere);
  imp.normalize();            
  return imp;
}  

class TMastrino : public TObject
{  
  static long _instances;
  static TRelation* _rel;
  static TLocalisamfile *_rmov;   // File principale della relazione
  static TLocalisamfile *_mov;    // File secondario della relazione
  
  TBill _conto;                   // Conto del mastrino
  int _esercizio;                 // Esercizio di riferimento (eventualmente 0)
  TDate _da_data, _a_data;
  TString _da_caus, _a_caus;
  
  real _pdare_ini, _pavere_ini;
  real _pdare_per, _pavere_per;
  real _pdare_fin, _pavere_fin;

  TList _riga;                    // Righe del mastrino

protected:
  TRelation& rel() { return *_rel; }
  TLocalisamfile& rmov() { return *_rmov; }
  TLocalisamfile& mov() { return *_mov; }
  
  void position_rel(long n);
  TRiga_mastrino& row(long n) const { return (TRiga_mastrino&)_riga.obj(n); }
 
public:
  long items() const { return _riga.items(); }

  void read(const TBill& conto, 
            int annoes, const TDate& dd, const TDate& ad,
            const TString& dc, const TString& ac);
  void reread();
  
  TRiga_mastrino& operator[](long n) const { return row(n); }
  const TRectype& riga(long n);
  const TRectype& testata(long n);
  
  long first(tipo_riga_mastrino tipo = riga_mastrino) const; 
  long pred(long rec, tipo_riga_mastrino tipo = riga_mastrino) const;
  long succ(long rec, tipo_riga_mastrino tipo = riga_mastrino) const;
  long last(tipo_riga_mastrino tipo = riga_mastrino) const;
  
  void destroy() { _riga.destroy(); }
  
  const real& progressivo_dare_iniziale() const { return _pdare_ini; }
  const real& progressivo_avere_iniziale() const { return _pavere_ini; }
  TImporto saldo_iniziale() const; 

  const real& progressivo_dare_finale() const { return _pdare_fin; }
  const real& progressivo_avere_finale() const { return _pavere_fin; }
  TImporto saldo_finale() const;
  
  real progressivo_dare_periodo() const { return _pdare_ini + _pdare_per; }
  real progressivo_avere_periodo() const { return _pavere_ini + _pavere_per; }
  TImporto saldo_periodo() const;
  
  int esercizio() const { return _esercizio; }
  const TDate& inizio_periodo() const { return _da_data; }
  const TDate& fine_periodo() const { return _a_data; }
  void periodo(TDate& dd, TDate& ad) const { dd = _da_data; ad = _a_data; }
  
  bool expandable(long rec) const;
  bool expand(long rec);
  bool collapse(long rec);

  TMastrino();
  virtual ~TMastrino();
};

long TMastrino::_instances = 0L;
TRelation* TMastrino::_rel = NULL;
TLocalisamfile* TMastrino::_rmov = NULL;   // File principale della relazione
TLocalisamfile* TMastrino::_mov = NULL;    // File secondario della relazione


TMastrino::TMastrino()
         : _esercizio(0)
{          
  if (_instances == 0L)
  {            
    _rel = new TRelation(LF_RMOV);
    _rel->add(LF_MOV, "NUMREG=NUMREG");
    _rmov = &_rel->lfile();
    _mov = &_rel->lfile(LF_MOV);
  }
  _instances++;  
}

TMastrino::~TMastrino()
{
  _instances--;  
  if (_instances == 0L)
  { 
    delete _rel;
    _rel = NULL;
    _rmov = _mov = NULL;
  }
}

long TMastrino::succ(long rec, tipo_riga_mastrino tipo) const
{ 
  if (rec < 0) rec = -1;
  
  const long ul = items();
  for (long i = rec+1; i < ul; i++)
  {
    if (row(i).tipo() == tipo)
      break;
  }    
  return i;
}

long TMastrino::pred(long rec, tipo_riga_mastrino tipo) const
{
  if (rec > items()) rec = items();
  for (long i = rec-1; i >= 0; i--)
  {
    if (row(i).tipo() == tipo)
      break;
  }    
  return i;
}

long TMastrino::first(tipo_riga_mastrino tipo) const
{ 
  return succ(-1, tipo);
}  

long TMastrino::last(tipo_riga_mastrino tipo) const
{ 
  return pred(items(), tipo);
}  

void TMastrino::read(const TBill& conto, 
                     int ae, const TDate& dd, const TDate& ad,
                     const TString& dc, const TString& ac)
{
  TEsercizi_contabili esercizi;

  _conto = conto; 
  _esercizio = ae;
  
  _riga.destroy();
  
  rmov().setkey(2);
  TRectype& rmov_rec = rmov().curr(); 
  TRectype& mov_rec  = mov().curr(); 

  if (ae <= 0)
  {
    if (dd.ok()) 
      ae = esercizi.date2esc(dd);
    else
      ae = esercizi.date2esc(ad);
  }
  CHECKD(esercizi.exist(ae), "Anno di esercizio fantasioso: ", ae);
  
  const TDate& inizio_esercizio = esercizi[ae].inizio();
  _da_data = dd.ok() ? dd : inizio_esercizio;
  _a_data  = ad.ok() ? ad : esercizi[ae].fine();

  const bool test_caus = !(dc.blank() && ac.blank());
  _da_caus = dc;
  _a_caus = ac.blank() ? "zzz" : ac;       // Se vuota sceglie la massima causale
  
  TDate max_data_reg = _a_data;
  long num_giorni = _a_data - inizio_esercizio + 1;
  if (_esercizio > 0) 
  {
    const int succ = esercizi.next(ae);   
    if (succ > 0)
    {
      max_data_reg = esercizi[succ].fine();
      num_giorni += 30;
    }
    else
      max_data_reg = esercizi[ae].fine();
  }
  
  TString caption(80);
  caption.format("Caricamento mastrino %03d.%03d.%06ld", 
                 _conto.gruppo(), _conto.conto(), _conto.sottoconto());
  TProgind pi(num_giorni, caption, FALSE, TRUE, 48);
  
  // Stima dimensione mastrino
  _riga.choose_step(num_giorni);
  
  // Valori dei saldi fino alla data di inizio stampa:
  // Vengono inizializzati con i saldi iniziali dell'esercizio,
  // poi verranno sommati gli importi dei movimenti che
  // vanno dall'inizio dell'esercizio al giorno precedente
  // la data di inizio stampa

  const TBalance saldo(_conto, ae, TRUE);
  _pdare_ini  = saldo.progressivo_dare_iniziale();
  _pavere_ini = saldo.progressivo_avere_iniziale();
  _pdare_fin  = saldo.progressivo_dare_finale();
  _pavere_fin = saldo.progressivo_avere_finale();
  
  // Valori dei saldi del perido in esame:
  // Vengono inizializzati a zero e poi si incrementa man mano
  // coi valori degli importi dei movimenti compresi nei
  // limiti della stampa
  _pdare_per = _pavere_per = ZERO;
  
  rmov_rec.zero();
  conto.put(rmov_rec);
  rmov_rec.put(RMV_DATAREG, inizio_esercizio);

  const TRecfield rmov_datareg   (rmov_rec, RMV_DATAREG);
  const TRecfield rmov_numreg    (rmov_rec, RMV_NUMREG);
  const TRecfield rmov_gruppo    (rmov_rec, RMV_GRUPPO);  
  const TRecfield rmov_conto     (rmov_rec, RMV_CONTO);
  const TRecfield rmov_sottoconto(rmov_rec, RMV_SOTTOCONTO);
  const TRecfield rmov_sezione   (rmov_rec, RMV_SEZIONE);  
  const TRecfield rmov_importo   (rmov_rec, RMV_IMPORTO);  
  
  const TRecfield mov_datacomp   (mov_rec, MOV_DATACOMP);  
  const TRecfield mov_provvis    (mov_rec, MOV_PROVVIS);  
  const TRecfield mov_codcaus    (mov_rec, MOV_CODCAUS);  
  
#ifdef DBG
  long num_rec = 0;                                    
  const clock_t clock_start = clock();
#endif  
  
  TDate ultimo_giorno;  // memorizza ultima data registrazione utilizzata
  
  for (int err = rel().read(_isgteq); err == NOERR; err = rel().next())
  {
    // Controlla di non aver superato la data limite  
    const TDate data_reg((TDate)rmov_datareg);
    if (data_reg > max_data_reg)
      break;

    // Controlla che il conto sia ancora quello selezionato
    if (_conto.sottoconto() != long(rmov_sottoconto) ||
        _conto.conto() != int(rmov_conto) || 
        _conto.gruppo() != int(rmov_gruppo))
      break;
    
#ifdef DBG
    num_rec++;
    if ((num_rec & 0x7F) == 0)
    {             
      const double sec = (clock() - clock_start) / CLOCKS_PER_SEC;
      if (sec > 0.0)
      {
        TString80 msg;
        msg.format("%ld records at %ld rec/sec", num_rec, long(num_rec/sec));
        pi.set_text(msg);
      }  
    }
#endif    
    
    // Ignora i movimenti provvisori
    const char provvis = *(const char*)mov_provvis;
    if (provvis > ' ')  
      continue;
    
    const TDate data_corrente = _esercizio <= 0 ? data_reg : (TDate)mov_datacomp;
    if (data_corrente > _a_data)
      continue;
      
    // Aggiorna la barra solo una volta al giorno
    if (data_reg > ultimo_giorno)
    {
      const long giorno = data_reg - inizio_esercizio + 1;
      pi.setstatus(giorno);
      ultimo_giorno = data_reg;
    }  
                     
    const char sezione = *((const char*)rmov_sezione);                 
    const real importo((const char*)rmov_importo);
                           
    if (data_corrente < _da_data)                       
    {            
      if (sezione == 'D')                       
        _pdare_ini += importo;
      else  
        _pavere_ini += importo;
    }
    else
    { 
      if (sezione == 'D')                       
        _pdare_per += importo;
      else  
        _pavere_per += importo;

      // Controlla che la causale sia nei limiti  
      if (test_caus)
      {
        const bool ok = _da_caus <= mov_codcaus && _a_caus >= mov_codcaus;
        if (!ok)
          continue;
      }  
      
      TRiga_mastrino* r = new TRiga_mastrino(riga_mastrino, 
                                             rmov().recno(), mov().recno(),
                                             _pdare_per, _pavere_per, data_reg);  
      _riga.append(r);
    }
  }
  
  pi.setstatus(num_giorni);    // Raramente arriva in fondo!
}

void TMastrino::reread()
{
  read(_conto, _esercizio, _da_data, _a_data, _da_caus, _a_caus);
} 


void TMastrino::position_rel(long n)
{
  const TRiga_mastrino& r = row(n);
  if (rmov().recno() != r.rmov())
    rmov().readat(r.rmov());
  if (mov().recno() != r.mov())
    mov().readat(r.mov());
}

const TRectype& TMastrino::riga(long n)
{
  position_rel(n);
  return rmov().curr();  
}

const TRectype& TMastrino::testata(long n)
{
  position_rel(n);
  return mov().curr();  
}

TImporto TMastrino::saldo_iniziale() const
{
  TImporto s('D', _pdare_ini - _pavere_ini);
  return s.normalize();
}

TImporto TMastrino::saldo_finale() const
{
  TImporto s('D', _pdare_fin - _pavere_fin);
  return s.normalize();
}

TImporto TMastrino::saldo_periodo() const
{
  TImporto s('D', progressivo_dare_periodo() - progressivo_avere_periodo());
  return s.normalize();
}

bool TMastrino::expandable(long rec) const
{   
  bool e = FALSE;
  if (row(rec).tipo() == riga_mastrino)
  {
    if (rec < items()-1)
      e = row(rec+1).tipo() != riga_contropartita;
    else
      e = TRUE;  
  }  
  return e;
}

// Genera le righe di contropartita di una riga del mastrino
bool TMastrino::expand(long rec)
{
  bool ok = expandable(rec);
  if (ok)
  {   
    const TRectype& head = testata(rec);               // Testata movimento
    const long numreg = head.get_long(RMV_NUMREG);     // Numero di registrazione
    const int numrig = riga(rec).get_int(RMV_NUMRIG);  // Numero riga contabile
    const TDate datareg = head.get(MOV_DATAREG);       // Data di registrazione
    
    rmov().setkey(1);                                  // Usa chiave NUMREG+NUMRIG
    TRectype& curr = rmov().curr();                    // Record corrente
    
    const TRecfield rnumreg (curr, RMV_NUMREG);        // Numero di registrazione corrente
    const TRecfield rnumrig (curr, RMV_NUMRIG);        // Numero di riga corrente
    const TRecfield rsezione(curr, RMV_SEZIONE);       // Sezione Dare/Avere
    const TRecfield rimporto(curr, RMV_IMPORTO);       // Importo della riga
    
    int err = NOERR;
    if (numrig != 1)    // Se non e' gia' posizionato grazie a riga(rec)
    {
      curr.zero();                                     // Azzera record corrente
      curr.put(RMV_NUMREG, numreg);                    // Inizializza la chiave parziale
      err = rmov().read(_isgteq);                      // Cerca la prima riga del movimento
    }  
    for (; err == NOERR; err = rmov().next())          // Scandisce righe movimento
    {
      if (numreg != (long)rnumreg)                     // Controlla validita' numero 
        break;                               

      if (numrig != (int)rnumrig)                      // Ignora la riga gia' presente
      {
        real dare, avere;                              // Costruisce importo della riga
        if (*(const char*)rsezione == 'D')  
          dare = rimporto;                     
        else
          avere = rimporto;
        // Aggiunge una riga di contropartita al mastrino
        TRiga_mastrino* r = new TRiga_mastrino(riga_contropartita, 
                                               rmov().recno(), mov().recno(),
                                               dare, avere, datareg);  
        _riga.append(r, rec++);
      }  
    }
  }
  return ok;
}

// Elimina le righe di contropartita di una riga del mastrino
bool TMastrino::collapse(long rec)
{       
  bool ok = TRUE;                        // Posso eliminare?
  if (row(rec).tipo() != riga_mastrino)  // Se non sono su una riga mastrino ...
    rec = pred(rec, riga_mastrino);      // ... mi sposto sulla precedente
  else
    ok = !expandable(rec);               // Controlla che sia possibile
  
  if (ok)                                // Posso effetivamente procedere
  {        
    rec++;                               // Elimino ogni riga contropartita successiva
    while (rec < items() && row(rec).tipo() != riga_mastrino)
      _riga.remove(rec);
  }
  return ok;
}

///////////////////////////////////////////////////////////
// TGrid_control
///////////////////////////////////////////////////////////

class TGrid_control;


class TGrid_cell : public TFixed_string
{
  XI_EVENT* _xiev;

public:                    
  TString& set(const char* txt);
  TString& set(long num);
  void set_icon(int id);
  void show_button(bool on = TRUE);
  void hide_button() { show_button(FALSE); }
  void set_back_color(COLOR col);
  void set_fore_color(COLOR col);
  void set_colors(COLOR back, COLOR fore);
  
  short get_column() const { return _xiev->v.cell_request.col_nbr; }
  
  TString& operator = (const char* str) { return set(str); }
  TString& operator = (const TString& str) { return set(str); }

  TGrid_cell(XI_EVENT* xiev);
  virtual ~TGrid_cell() { }
};

class TGrid_field : public TOperable_field
{     
protected: // TMask_field
  virtual void create(WINDOW parent);
  virtual void parse_head(TScanner& scanner);
  virtual bool parse_item(TScanner& scanner);
  virtual word class_id() const;

  TGrid_control& grid() const { return (TGrid_control&)*_ctl; }

public:
  virtual bool handler(XI_EVENT* xiev);
  virtual long items() const;
  virtual void cell_request(long rec, short id, TGrid_cell& cell);

  virtual bool on_record(long rec)  { return TRUE; }
  virtual bool off_record(long rec) { return TRUE; }
  virtual bool on_resize_column(short cid, int new_size) { return TRUE; }
  virtual void on_dbl_cell(long rec, short id) { }
  virtual void on_grid_button() { } 
  virtual void on_record_button(long rec) { }
  virtual void on_cell_button(long rec, short cid) { }

  long selected() const;
  void update(long n = -1);
  int visible_rows() const;
  bool select(long rec);
  
  void reset_columns_order();
  void save_columns_order() const; 
  
  TGrid_field(TMask* m);
  virtual ~TGrid_field() { }
};

class TGrid_control : public TControl
{
  enum grid_control_constants { MAX_COL = 128 };

  long _cur_rec;
  bool _read_only;
  
  // @cmember:(INTERNAL) Tipo di ogni colonna
  byte _type[MAX_COL];
  
  TGrid_field* _grid;
  
  int _default_width[MAX_COL];
  int _columns_order;

protected:  // TControl
  //@cmember Gestisce gli eventi delle celle
  virtual bool event_handler(XI_OBJ* itf, XI_EVENT* xiev);
  
  //@cmember Chiama gli handlers opportuni per verificare il cambio record
  bool try_to_select(long rec) const;  

protected:  
  //@cmember Ritorna il numero totale di righe
  long items() const { return _grid->items(); }
  
  //@cmember Converte un record nella eventuale riga corrispondente a video
  int rec2row(long rec) const;
  
  //@cmember Converte una riga a video nell'eventuale record corrispondente
  long row2rec(int row) const;
  
  //@cmember Converte un indice di colonna nel corrispondente id
  short int col2cid(int pos) const;
  
  void update_selection(XI_EVENT* xiev);

  void set_columns_order(TToken_string* order);
  
  XI_OBJ* find_column(short cid) const;
  XI_OBJ* find_column(const char* head) const;

public:                   
  long selected() const { return _cur_rec; }
  bool select(long n);
  
  int visible_rows() const;
  
  byte& column_type(int c) { CHECKD(c >= 0 && c < MAX_COL, "Bad column ", c); return _type[c]; }

  void update(long n = -1);
  bool is_visible(long rec) const;
  
  void load_columns_order();
  void save_columns_order() const; 
  void reset_columns_order() { set_columns_order(NULL); }
  
  TGrid_control(WINDOW parent, short cid, 
                short x, short y, short dx, short dy,              
                const char* flags, const char* head,
                TGrid_field* owner);
  virtual ~TGrid_control() {}
};


TGrid_control::TGrid_control(
  WINDOW parent,         // @parm Finestra alla quale appartiene lo spreadsheet
  short cid,             // @parm Identificatore
  short x,               // @parm Coordinata x (in caratteri) nel quale posizionare lo spreadsheet
  short y,               // @parm Coordinata y (in caratteri) nel quale posizionare lo spreadsheet
  short dx,              // @parm Larghezza (in caratteri) dello spreasheet
  short dy,              // @parm Lunghezza (in caratteri) dello spreasheet
  const char* flags,     // @parm Flags di abilitazione
  const char* head,      // @parm Titolo delle colonne
  TGrid_field* owner)
              : _grid(owner), _cur_rec(-1), _columns_order(0)
{ 
  _read_only        = FALSE;
  bool auto_num     = FALSE;
  bool multi_line   = FALSE;
  int lines_in_cell = 1;
  
  for (const char* f = flags; *f; f++)
  {
    switch(*f)
    {       
    case 'A':
      auto_num = TRUE;
      break;
    case 'D': 
      _read_only = TRUE;
      break;
    case 'M': 
      multi_line = TRUE; 
      lines_in_cell = (int)xi_get_pref(XI_PREF_DEFAULT_MAX_LINES_IN_CELL);
      break;
    case '2':
    case '3':
    case '4':
    case '5':               
      if (multi_line)
        lines_in_cell = *f - '0'; 
      break;
    default:  
      break;
    }
  }

  const int NUMBER_WIDTH = auto_num ? 7 : 1;
  short v_width[MAX_COL];
  short m_width[MAX_COL];
  int fixed_columns = 1;                               // Number of fixed columns
  int lines_in_header = 1;                             // Number of header lines
  
  // Calcolo larghezza massima tabella
  TToken_string header(head);
  TToken_string new_header(256);
  int i = 0;
  int f_width = NUMBER_WIDTH;                          // Stima larghezza colonne fisse
  int max_width = f_width;                             // Stima larghezza della colonna piu' grande
  
  for (const char* h = header.get(); h; h = header.get(), i++)
  {
    CHECKD(i < MAX_COL, "Tu meni calumns in scit: ", i);
    _type[i] = ' ';
    
    TFixed_string testa(esc(h));
    const bool multiple = testa.find('\n') > 0;
    if (multiple)
      lines_in_header = 2;

    const int at = testa.find('@');
    int v = testa.len();                             // Video width
    if (at >= 0)
    {
      const TString& wi = testa.mid(at+1);
      const int video = atoi(wi);
      if (video > 0) v = video;
      if (wi.find('F') >= 0) 
      {
        fixed_columns = i+2;
        f_width += v+1;
      }    
      if (wi.find('R') >= 0)
        _type[i] = 'R';

      testa.cut(at);
    } 
    
    v++;                                                 
    // memory width of column
    m_width[i] = v * lines_in_cell;         
    if (v > 64) v = 64;
    v_width[i] = v;
    if (v_width[i] > max_width) 
      max_width = v_width[i];
    new_header.add(testa);
  }
  
  // Calcola rettangolo massimo per lo sheet
  XI_RCT rct; coord2rct(parent, x, y, dx, dy, rct);
  rct.right -= 2*XI_FU_MULTIPLE;           // toglie scroll-bar
 
  // Controlla se ci sono troppe colonne fisse
  if ((f_width+max_width)*XI_FU_MULTIPLE > rct.right)
    fixed_columns = 1;
    
  XI_OBJ* itf = get_interface(parent);     

  XI_OBJ_DEF* listdef = xi_add_list_def(NULL, cid,
                                        rct.top, rct.left, rct.bottom-rct.top,
                                        XI_ATR_ENABLED | XI_ATR_VISIBLE,
                                        NORMAL_COLOR, NORMAL_BACK_COLOR,     // normal
                                        DISABLED_COLOR, DISABLED_BACK_COLOR, // disabled
                                        FOCUS_COLOR,                         // active
                                        0);
                                        
  listdef->app_data = (long)this;
  
  XI_LIST_DEF* l = listdef->v.list;
  l->min_heading_height = xi_button_calc_height_font_id(xvt_default_font()) * lines_in_header;
  l->sizable_columns    = TRUE;
  l->movable_columns    = TRUE;
  l->fixed_columns      = fixed_columns;
  l->max_lines_in_cell  = lines_in_cell;
  l->scroll_bar         = TRUE;
  l->scroll_bar_button  = TRUE;
  l->white_space_color  = MASK_DARK_COLOR;
  l->rule_color         = MASK_DARK_COLOR;
  
  if (_read_only)
    l->single_select = TRUE;
  else
    l->active_back_color = FOCUS_BACK_COLOR;

  // Definizione della prima colonna (numero di riga)
  const long attr = XI_ATR_VISIBLE | XI_ATR_RJUST | XI_ATR_SELECTABLE;                                            
  XI_OBJ_DEF* coldef = xi_add_column_def(listdef, FIRST_FIELD+1000-1, attr, 0, 
                                         NUMBER_WIDTH * XI_FU_MULTIPLE, NUMBER_WIDTH , "");
  
  coldef->app_data = (long)this;            
  XI_COLUMN_DEF* cd = coldef->v.column;
  cd->heading_platform = TRUE;
  cd->column_platform  = TRUE;

  for (h = new_header.get(0), i = 0; h; h = new_header.get(), i++)
  {                  
    long attr = XI_ATR_VISIBLE | XI_ATR_ENABLED | XI_ATR_AUTOSCROLL; 
    if (_read_only)
      attr |= XI_ATR_READONLY | XI_ATR_SELECTABLE;
    if (_type[i] == 'R') 
      attr |= XI_ATR_RJUST;
    coldef = xi_add_column_def(listdef, FIRST_FIELD+i+1000, attr, i+1, 
                               v_width[i] * XI_FU_MULTIPLE, m_width[i], (char*)h);

    coldef->app_data = (long)this;
    cd = coldef->v.column;  
    cd->heading_platform = TRUE;
    cd->center_heading = TRUE;
    if (multi_line)
      cd->wrap_text = _type[i] != 'R'; 
  }
                                
  RCT rd; xi_get_def_rect(listdef, &rd);
  if ((rd.right - rd.left) > (rct.right - rct.left))
    l->width = rct.right - rct.left;

  _obj = xi_create(itf, listdef);     // Create the whole thing!
  xi_dequeue();                       // Flush events in XOL
  xi_tree_free(listdef);              // Free definitions
               
  CHECKD(_obj, "Can't create list control ", cid);   
  update_tab_cid();
  
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  for (i = 0; i < num; i++) 
  {
    RCT rct; xi_get_rect(column[i], &rct);
    _default_width[i] = rct.right - rct.left;
  }
} 

// Converts a record number in the correspondig row number
int TGrid_control::rec2row(long record) const
{
  int rows;
  const long* rec = xi_get_list_info(_obj, &rows);
  int r = rows > 0 ? int(record - rec[0]) : -1;
  if (r < 0 || r >= rows) 
    r = -1;
  return r;
}

// Converts a row number in the correspondig record number
long TGrid_control::row2rec(int row) const
{
  CHECK(row >= 0, "Negative grid row?");

  int rows;
  const long* handle = xi_get_list_info(_obj, &rows);
  
  long rec;
  if (rows > 0)
  {
    if (row >= rows) 
      rec = handle[rows-1] + row - rows + 1;
    else
      rec = handle[row];
  }
  else
    rec = -1;

  if (rec < 0 || rec >= items())
    rec = -1;
  
  return rec;
} 

int TGrid_control::visible_rows() const
{
  return xi_get_visible_rows(_obj, NULL, NULL);
}

bool TGrid_control::is_visible(long rec) const
{ 
  int first, last;
  xi_get_visible_rows(_obj, &first, &last);

  int rows;
  const long* handle = xi_get_list_info(_obj, &rows);
  
  bool yes = rec >= handle[first] && rec <= handle[last];
  return yes;
}


void TGrid_control::update(long n)
{
  if (n >= 0)
  {
    const int riga = rec2row(n);
    if (riga >= 0)
    {                 
      XI_OBJ row;
      XI_MAKE_ROW(&row, _obj, riga);
      xi_cell_request(&row);           
    }
  }
  else
  {
    int num = 0;
    const long* handle = xi_get_list_info(_obj, &num);

    bool scroll_first = items() == 0;
    if (!scroll_first)
    {
      int first = 0, last = 0;
      xi_get_visible_rows(_obj, &first, &last);   
      n = handle[first];
      scroll_first = n > items();
    }
    
    if (scroll_first)
      xi_scroll(_obj, XI_SCROLL_FIRST);
    else
      xi_scroll_rec(_obj, n, NORMAL_COLOR, XI_ATR_ENABLED, 0);
  }  
}

bool TGrid_control::select(long rec)
{ 
  bool ok, sel;
  if (rec >= 0)               
  {
    ok = try_to_select(rec);
    sel = ok;
  }  
  else  
  {
    ok = _cur_rec >= 0 && _cur_rec < items() && _grid->off_record(_cur_rec);
    sel = FALSE;
  }  

  if (ok)
  {
    if (sel)
    {                                          
      int first, last;
      xi_get_visible_rows(_obj, &first, &last);
      // Controllo che la nuova riga sia completamente visibile
      const int next_row = rec2row(rec);      
      if (next_row >= first && next_row <= last) 
      {   
        if (_read_only)
        {
          XI_OBJ riga; XI_MAKE_ROW(&riga, _obj, next_row);
          long attr = xi_get_attrib(&riga);
          attr |= XI_ATR_SELECTED;
          xi_set_attrib(&riga, attr);
        }  
      }
      else
      {
        long attr = XI_ATR_ENABLED;
        if (_read_only)
          attr |= XI_ATR_SELECTED;
        xi_scroll_rec(_obj, rec, NORMAL_COLOR, attr, 0);
      }
  
      if (!_read_only)
      { 
        const int next_row = rec2row(rec);
        XI_OBJ cella; XI_MAKE_CELL(&cella, _obj, next_row, 1);
        xi_set_focus(&cella);
      }
    }  // end if (sel)
    
    // Deseleziona record precedente se ancora visibile
    if (_read_only)
    {                                 
      const int cur_row = rec2row(_cur_rec);
      if (cur_row >= 0)
      {
        XI_OBJ riga; XI_MAKE_ROW(&riga, _obj, cur_row);
        long attr = xi_get_attrib(&riga);
        attr &= ~XI_ATR_SELECTED;     
        xi_set_attrib(&riga, attr);
      }
      xi_dequeue();
    }
  
    if (rec < 0 || rec >= items())
      rec = -1;
    _cur_rec = rec;
  }           // end if (ok)
  
  return ok;
}

short TGrid_control::col2cid(int pos) const
{
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  CHECKD(pos >= 0 && pos < num, "Bad column ", pos);
  const short cid = column[pos]->cid - 1000;
  return cid;
}

bool TGrid_control::try_to_select(long rec) const
{  
  bool ok = rec >= 0 && rec < items();
  if (ok && rec != _cur_rec)
  {         
    if (_cur_rec >= 0 && _cur_rec < items())
      ok = _grid->off_record(_cur_rec);  
    if (ok)
      ok = _grid->on_record(rec);
  }              
  return ok;
}

void TGrid_control::update_selection(XI_EVENT* xiev)
{
  const bool is_curr = xiev->v.rec_request.data_rec == _cur_rec;
  if (_read_only)
  {
    if (is_curr)
      xiev->v.rec_request.attrib |= XI_ATR_SELECTED;
    else
      xiev->v.rec_request.attrib &= ~XI_ATR_SELECTED;
  }
  else
    xiev->v.rec_request.has_focus = is_curr;
}

// Certified 75%
bool TGrid_control::event_handler(XI_OBJ* itf, XI_EVENT *xiev)
{
  BOOLEAN& refused = xiev->refused;   
  
  const bool handled = _grid->handler(xiev);
  if (handled)
    return !refused;

  switch (xiev->type)
  {
  case XIE_GET_FIRST:
    if (items() > 0L)
    {
      long n = items() * (long)xiev->v.rec_request.percent / 100L;
      if (n < 0L) n = 0L;
      xiev->v.rec_request.data_rec = n;
      update_selection(xiev);
    }
    else
      refused = TRUE;
    break;
  case XIE_GET_LAST:
    xiev->v.rec_request.data_rec = items()-1;
    update_selection(xiev);
    break;
  case XIE_GET_PREV:
  case XIE_GET_NEXT:
    {
      const long n = xiev->v.rec_request.spec_rec + (xiev->type == XIE_GET_NEXT ? +1 : -1) ;
      if (n >= 0 && n < items())
      {
        xiev->v.rec_request.data_rec = n;
        update_selection(xiev);
      }  
      else
        refused = TRUE;
    }
    break;
  case XIE_GET_PERCENT:
    {
      const long rec = xiev->v.get_percent.record;
      long n = items(); if (n <= 0) n = 1;
      xiev->v.get_percent.percent = short(rec * 100L / n);
    }
    break;
  case XIE_COL_MOVE:           
    // Rifiuta di spostare una colonna nelle o dalle colonne fisse
    if (xiev->v.column.in_fixed || 
        xiev->v.column.col_nbr < xi_get_fixed_columns(xiev->v.column.list))
      refused = TRUE;
    else
      _columns_order = 1;  
    break;  
  case XIE_COL_SIZE:            
    {
      const short cid = col2cid(xiev->v.column.col_nbr);
      if (_grid->on_resize_column(cid, xiev->v.column.new_col_width))
        _columns_order = 1;
      else
        refused = TRUE;  
    }                           
    break;  
  case XIE_SELECT:                                
    if (xiev->v.select.xi_obj->type == XIT_ROW) // Considero solo le righe
    {                                         
      if (xiev->v.select.selected)              // Sto selezionando
      {
        const long rec = row2rec(xiev->v.select.xi_obj->v.row_data.row);
        if (try_to_select(rec))
        {
          if (xiev->v.select.column == 0)
          {
             _cur_rec = rec;  // Assegno subito il record corrente
             _grid->on_record_button(rec);
          }  
          else
          {  
            if (_read_only && rec == _cur_rec)
            {                                    
              const short cid = col2cid(xiev->v.select.column);
              _grid->on_dbl_cell(rec, cid);
            }
            _cur_rec = rec;   // Assegno solo ora il record corrente
          }    
        }  
        else  
          refused = TRUE;
      }  
    }  
    break;
  case XIE_CELL_REQUEST:
    {                        
      const long& rec = xiev->v.cell_request.rec;       
      if (rec >= 0 && rec < items())
      {
        TGrid_cell cell(xiev);
        const short cid = col2cid(cell.get_column());
        if (cid >= FIRST_FIELD)
        {
          _grid->cell_request(rec, cid, cell);
        }  
        else
        {
          if (cell.size() > 2)
          {
            cell.set(rec+1);
            // Setto il colore del testo altrimenti verrebbe grigio:
            // non uso la set_color perche' ignora NORMAL_COLOR
            xiev->v.cell_request.color = NORMAL_COLOR;  
          }  
        }  
      }
      else
        refused = TRUE;       // Ogni tanto succede
    }  
    break;
  case XIE_ON_ROW:
    { // Qui ci passa solo se non e' _read_only                 
      const long rec = row2rec(xiev->v.xi_obj->v.row);
      if (rec >= 0)
      {
        if (_grid->on_record(rec))
          _cur_rec = rec;
        else
          refused = TRUE;  
      }  
      else  
      {
        NFCHECK("You are entering an invalid row: %d", xiev->v.xi_obj->v.row);
        refused = TRUE;
      }  
    }  
    break;
  case XIE_OFF_ROW:
    // Qui ci passa solo se non e' _read_only                 
    if (_cur_rec >= 0 && _cur_rec < items())
      refused = !_grid->off_record(_cur_rec);
    break;
  case XIE_ON_CELL:
    break;  
  case XIE_DBL_CELL:
    {
      const long rec = row2rec(xiev->v.xi_obj->v.cell.row);
      if (try_to_select(rec))
      {
        const short cid = col2cid(xiev->v.xi_obj->v.cell.column);
        _grid->on_dbl_cell(rec, cid);
      }    
    }  
    break;   
  case XIE_BUTTON:  
    if (xiev->v.xi_obj->type == XIT_LIST)
    {
      _grid->on_grid_button();
    }  
    else
    {
      const XI_CELL_DATA& cell = xiev->v.xi_obj->v.cell;
      const long rec = row2rec(cell.row);
      if (try_to_select(rec))
      {
        const short cid = col2cid(cell.column);
        _grid->on_cell_button(rec, cid);
      }  
      else
        NFCHECK("You are clicking an invalid cell: %d", cell.row);
    }  
    break;
  default:
    break;
  }
  
  return !refused;
}

XI_OBJ* TGrid_control::find_column(short cid) const
{
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  for (int i = num-1; i >= 0; i--) 
  {
    if (column[i]->cid == cid)
      break;
  }         
  return i >= 0 ? column[i] : NULL;
}

XI_OBJ* TGrid_control::find_column(const char* head) const
{
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  
  TString80 text;
  for (int i = num-1; i >= 0; i--) 
  { 
    xi_get_text(column[i], text.get_buffer(), text.size());
    if (text == head)
      break;
  }         
  return i >= 0 ? column[i] : NULL;
}

void TGrid_control::set_columns_order(TToken_string* order)
{                                 
  XI_OBJ* itf = get_interface();
  XI_OBJ* focus = xi_get_focus(itf);
  xi_set_focus(itf);
  
  int num_cols;
  XI_OBJ** column = xi_get_member_list(_obj, &num_cols);
  
  // Costante da sottrarre nella xi_column_set_pixel_width altrimenti la somma due volte!
  const int offset = 2 * (int)xi_get_pref(XI_PREF_COLUMN_OFFSET);
  const int fixed = xi_get_fixed_columns(_obj);
  if (fixed > 1)
    xi_set_fixed_columns(_obj, 1);
  
  if (order == NULL)
  {
    for (int index = 1; index < num_cols; index++)
    {                                
      const short cid = FIRST_FIELD + 1000 + index - 1;
      XI_OBJ* col = find_column(cid);
      if (col)
      {
        xi_move_column(col, index); 
        RCT rct; xi_get_rect(col, &rct);
        if (_default_width[index] != rct.right - rct.left)
          xi_column_set_pixel_width(col, _default_width[index]-offset);
      }    
    }  
    _columns_order = 0x3;
  }
  else
  {              
    TToken_string col(8, ',');
    int pos = 0;
    for (col = order->get(0); !col.blank(); col = order->get(), pos++)
    {
      const char* head = esc(col.get(0));
      const int width = col.get_int();
      XI_OBJ* column = find_column(head);
      if (column)                        // Controlla che esista ancora
      {             
        if (pos > 0 && pos < num_cols)
          xi_move_column(column, pos);   // Sposta la colonna se possibile
        if (width > XI_FU_MULTIPLE)      // Se ha una larghezza valida
          xi_column_set_pixel_width(column, width - offset);
      }
    }  
  }
  
  if (fixed > 1)
    xi_set_fixed_columns(_obj, fixed);
  
  if (focus)
    xi_set_focus(focus);
}      

HIDDEN TFilename& field2parag(const TMask_field& f, TFilename& name)
{ 
  const TMask& m = f.mask();
  name = m.source_file();      
  name.ext("");                // Nome della maschera senza estensione
  const int index = m.number();
  CHECKD(index >= 0 && index <= 8, "Bad mask index:", index);
  if (index > 0)          // Aggiunge l'eventuale numero di sotto-maschera
    name << '(' << index << ')';
  return name;
}

void TGrid_control::load_columns_order()
{
  TFilename parag; field2parag(*_grid, parag);
  TConfig config(CONFIG_USER, parag);
  TToken_string order = config.get("Browse", NULL, id());
  if (order.empty_items())
    config.remove("Browse", id());
  else
    set_columns_order(&order);
  _columns_order = 0;  
}    

void TGrid_control::save_columns_order() const
{
  if (_columns_order)
  {
    TFilename parag; field2parag(*_grid, parag);
    TConfig config(CONFIG_USER, parag);   // Apre il file di configurazione

    TToken_string order(127);             // Nuovo ordine delle colonne
    if (_columns_order == 1)              // Se vale 3 devo solo resettare
    {
      int num;
      XI_OBJ** column = xi_get_member_list(_obj, &num);
      TString80 head;
      for (int i = 0; i < num; i++)       // Scorre tutte le colonne
      {             
        xi_get_text(column[i], head.get_buffer(), head.size());
        const int acapo = head.find('\n');
        if (acapo > 0)
        {
          head[acapo] = '\\';
          head.insert("n", acapo+1);
        }
        order.add(head);
        RCT rct; xi_get_rect(column[i], &rct);
        order << ',' << rct.right - rct.left;
      }
      config.set("Browse", order, NULL, TRUE, id());
    }  
    else
      config.remove("Browse", id());
  }  
}

///////////////////////////////////////////////////////////
// TGrid_cell
///////////////////////////////////////////////////////////

TGrid_cell::TGrid_cell(XI_EVENT* xiev) 
          : TFixed_string(xiev->v.cell_request.s, xiev->v.cell_request.len), 
            _xiev(xiev) 
{ }


// Setta il testo di una cella (Mai piu' testo troppo lungo!)
// Se c'e' gia' un'icona la elimina
TString& TGrid_cell::set(const char* txt)
{ 
  strncpy(txt, size());
  if (not_empty())
  {
    int& icon = _xiev->v.cell_request.icon_rid;
    if (icon)
      icon = 0;
  }    
  return *this;
}

TString& TGrid_cell::set(long num)
{
  char buff[16];
  sprintf(buff, "%ld", num);
  return set(buff);
}

// Setta l'icona di una cella
// Se c'e' gia' un testo lo elimina
void TGrid_cell::set_icon(int id)
{
  _xiev->v.cell_request.icon_rid = id;
  if (id)
    _xiev->v.cell_request.s[0] = '\0';
}

void TGrid_cell::show_button(bool on)
{                         
  _xiev->v.cell_request.button = on;
  _xiev->v.cell_request.button_on_focus = on;
}

void TGrid_cell::set_back_color(COLOR col)
{                                        
  if (col != NORMAL_BACK_COLOR)
    _xiev->v.cell_request.back_color = col;
}

void TGrid_cell::set_fore_color(COLOR col)
{
  if (col != NORMAL_COLOR)
    _xiev->v.cell_request.color = col;
}

void TGrid_cell::set_colors(COLOR back, COLOR fore)
{
  if (back != NORMAL_BACK_COLOR)
    _xiev->v.cell_request.back_color = back;
  if (fore != NORMAL_COLOR)
    _xiev->v.cell_request.color = fore;
}

///////////////////////////////////////////////////////////
// TGrid_field
///////////////////////////////////////////////////////////

TGrid_field::TGrid_field(TMask* m) 
           : TOperable_field(m) 
{ }      

word TGrid_field::class_id() const
{
  return CLASS_GRID_FIELD;
}  

void TGrid_field::update(long n) 
{ grid().update(n); }

void TGrid_field::parse_head(TScanner& scanner)
{
  _ctl_data._width  = scanner.integer();
  _ctl_data._height = scanner.integer();
  if (_ctl_data._height == 0) 
    _ctl_data._height = -1;
}

void TGrid_field::create(WINDOW parent)
{ 
  _ctl = new TGrid_control(parent, dlg(), 
                           _ctl_data._x, _ctl_data._y, 
                           _ctl_data._width, _ctl_data._height, 
                           _ctl_data._flags, _ctl_data._park,
                           this);
  grid().load_columns_order();
}

bool TGrid_field::parse_item(TScanner& scanner)
{
  if (scanner.key() == "IT")
  {
    _ctl_data._park.add(scanner.string());
    return TRUE;
  }
  return TMask_field::parse_item(scanner);
}   


bool TGrid_field::handler(XI_EVENT* xiev)
{ 
  return FALSE;
}

long TGrid_field::items() const
{
  return 100000L;
}

int TGrid_field::visible_rows() const
{
  return grid().visible_rows();
}

void TGrid_field::cell_request(long rec, short id, TGrid_cell& cell)
{
  cell.set("Cell");
}

long TGrid_field::selected() const
{ return grid().selected(); }

bool TGrid_field::select(long rec)
{ return grid().select(rec); }

void TGrid_field::reset_columns_order()
{ 
  grid().reset_columns_order();
}

void TGrid_field::save_columns_order() const 
{
  grid().save_columns_order();
}

///////////////////////////////////////////////////////////
// Da qui in poi e' tutta roba specializzata del programma
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
// Maschere per colori
///////////////////////////////////////////////////////////

class TRow_mask : public TMask
{ 
public:
  virtual void update();

  TRow_mask();
  virtual ~TRow_mask() { }
};

class TColor_mask : public TVariable_mask
{        
  static TRow_mask* _sheet_mask;
  static TMask* get_mask(int, TMask&) { return _sheet_mask; }
  
  COLOR _mas_back, _mas_fore;
  COLOR _con_back, _con_fore;

protected:
  static bool color_handler(TMask_field& f, KEY k);

  void type2colors(char tipo, COLOR& back, COLOR& fore) const;
  
public:
  void get_cur_colors(COLOR& back, COLOR& fore) const;  
  void set_cur_colors(COLOR back, COLOR fore); 
  
  void get_colors(COLOR& mb, COLOR& mf, COLOR& cb, COLOR& cf) const;

  TColor_mask(COLOR mb, COLOR mf, COLOR cb, COLOR cf);
  virtual ~TColor_mask();
};

///////////////////////////////////////////////////////////
// TRow_mask
///////////////////////////////////////////////////////////

TRow_mask* TColor_mask::_sheet_mask = NULL;

TRow_mask::TRow_mask()
         : TMask("cg2100k", 1)
{
}

void TRow_mask::update()
{               
  TSheet_field* s = get_sheet();
  TColor_mask& m = (TColor_mask&)s->mask();
  COLOR back, fore;
  m.get_cur_colors(back, fore);
  
  set_pen(COLOR_BLACK);
  set_brush(back);
  frame(2, 4, 16, 6, 0);
  set_brush(fore);
  frame(20, 4, 34, 6, 0);
}

///////////////////////////////////////////////////////////
// TColor_mask
///////////////////////////////////////////////////////////

void TColor_mask::type2colors(char tipo, COLOR& back, COLOR& fore) const
{
  if (toupper(tipo) == 'M')
  {
    back = _mas_back;
    fore = _mas_fore;
  }
  else
  {
    back = _con_back;
    fore = _con_fore;
  }
}

void TColor_mask::get_cur_colors(COLOR& back, COLOR& fore) const
{
  TSheet_field& s = (TSheet_field&)fld(0);
  TToken_string& row = s.row(s.selected());
  const char tipo = row[5];
  type2colors(tipo, back, fore);
}

void TColor_mask::set_cur_colors(COLOR back, COLOR fore)
{
  TSheet_field& s = (TSheet_field&)fld(0);
  const int cur = s.selected();
  TToken_string& row = s.row(cur);
  const char tipo = toupper(row[5]);
  if (tipo == 'M')
  {
    _mas_back = back;
    _mas_fore = fore;
  }
  else
  {
    _con_back = back;
    _con_fore = fore;
  }
  s.set_back_and_fore_color(back, fore, cur); 
  s.force_update(cur);
}
  
bool TColor_mask::color_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {                                               
    TMask& m = f.mask();
    TColor_mask& cm = (TColor_mask&)m.get_sheet()->mask();
    
    COLOR back, fore;
    cm.get_cur_colors(back, fore);     
    const bool use_back = f.dlg() == 100;
    
    const COLOR col = choose_color(use_back ? back : fore, m.win());
    if (col != COLOR_INVALID)
    {                   
      if (use_back)
        back = col;
      else
        fore = col;  
      cm.set_cur_colors(back, fore);
      cm._sheet_mask->update();
    }  
  }
  return TRUE;
}

void TColor_mask::get_colors(COLOR& mb, COLOR& mf, COLOR& cb, COLOR& cf) const
{
  mb = _mas_back;
  mf = _mas_fore;
  cb = _con_back;
  cf = _con_fore;
}

TColor_mask::TColor_mask(COLOR mb, COLOR mf, COLOR cb, COLOR cf) 
           : TVariable_mask("cg2100k"),
             _mas_back(mb), _mas_fore(mf), _con_back(cb), _con_fore(cf)
{              
  CHECK(_sheet_mask == NULL, "One color at time, please");
  _sheet_mask = new TRow_mask;
  _sheet_mask->set_handler(99, color_handler);
  _sheet_mask->set_handler(100, color_handler);

  TVariable_sheet_field& s = (TVariable_sheet_field&)fld(0);
  s.set_getmask(get_mask);

  const char* const tipi = "MC"; 
  int row = 0;
  for (const char* c = tipi; *c; c++)
  {
    COLOR back, fore;
    type2colors(*c, back, fore);
    TToken_string& riga = s.row(-1);
    riga << "Riga " << (*c == 'M' ? "mastrino" : "contropartita");
    s.set_back_and_fore_color(back, fore, row++); 
  }
}

TColor_mask::~TColor_mask() 
{
  delete _sheet_mask;
  _sheet_mask = NULL;
}

///////////////////////////////////////////////////////////
// TMastrini_grid
///////////////////////////////////////////////////////////

class TMastrini_grid : public TGrid_field
{                      
  TMastrino _mastrino;    
  
  TDecoder _causali;
  TEsercizi_contabili _esercizi;
  
  COLOR _mas_back, _mas_fore;
  COLOR _con_back, _con_fore;
  
protected: // TGrid_field  
  virtual bool on_record(long rec);
  virtual void cell_request(long rec, short id, TGrid_cell& cell);
  virtual void on_grid_button();
  virtual bool on_resize_column(short id, int new_size);
  virtual void on_dbl_cell(long rec, short id);
                
  void update_mask() const;

public:                                                   
  virtual void on_record_button(long rec);
  virtual long items() const { return _mastrino.items(); }

  void destroy();
  void read(const TBill& conto, 
            int annoes, const TDate& dd, const TDate& ad,
            const TString& dc, const TString& ac);
  void reread();          
            
  TMastrino& mastrino() { return _mastrino; }          

  void save_colors();
  void load_colors();
  void set_colors();
  
  TMastrini_grid(TMask* m);
  virtual ~TMastrini_grid() { }
};

TMastrini_grid::TMastrini_grid(TMask* m)
              : TGrid_field(m), _causali(LF_CAUSALI, CAU_DESCR)
{
  load_colors();
}

void TMastrini_grid::destroy() 
{ 
  _mastrino.destroy();    
  grid().select(-1);
}

HIDDEN void set_imp(TMask_field& f, const TImporto& imp)
{
  if (!imp.is_zero())
  {
    TString80 str;
    str = imp.valore().string(".");
    str << ' ' << imp.sezione();
    f.set(str);
  }  
  else
    f.reset();
}

void TMastrini_grid::cell_request(long rec, short id, TGrid_cell& cell)
{                                    
  const TRiga_mastrino& riga = _mastrino[rec];
  
  switch (id)
  {
  case 101:
    if (riga.tipo() == riga_mastrino)
    {
      const TRectype& mov = _mastrino.testata(rec);
      cell = riga.data().string();
      cell << ' ' << mov.get(MOV_DATADOC);
    }  
    break;
  case 102:
    if (riga.tipo() == riga_mastrino)
    { 
      const TRectype& mov = _mastrino.testata(rec);
      const int anno = _esercizi.date2esc(riga.data());
      const int eser = mov.get_int(MOV_ANNOES);
      const comp = anno != eser;         
      cell.set_icon(comp ? ICO_CHECK_ON : ICO_CHECK_OFF);
    }
    break;
  case 103:
    if (riga.tipo() == riga_mastrino)
    {
      const TRectype& mov = _mastrino.testata(rec);
      cell.set(_causali.decode(mov.get(MOV_CODCAUS)));
    }  
    else
    {
      const TRectype& rmov = _mastrino.riga(rec);
      cell.format("%03d.%03d.%06ld",
                  rmov.get_int(RMV_GRUPPO), 
                  rmov.get_int(RMV_CONTO),
                  rmov.get_long(RMV_SOTTOCONTO));
    }
    break;      
  case 104:
    if (riga.tipo() == riga_mastrino)
    {                                  
      const TRectype& mov = _mastrino.testata(rec);
      cell.set(mov.get(MOV_DESCR));
      if (cell.empty())
      {
        const TRectype& rmov = _mastrino.riga(rec);
        cell.set(rmov.get(RMV_DESCR));
      }
    }  
    else
    {
      const TRectype& rmov = _mastrino.riga(rec);
      cell.set(rmov.get(RMV_DESCR));
      if (cell.empty())
      {
        const TBill conto(rmov);
        cell.set(conto.descrizione());
      }
    }
    break;  
  case 105:
    {
      const TRectype& rmov = _mastrino.riga(rec);
      if (rmov.get_char(RMV_SEZIONE) == 'D')
        cell = rmov.get_real(RMV_IMPORTO).string(".");
    }    
    break;
  case 106:    
    {
      const TRectype& rmov = _mastrino.riga(rec);
      if (rmov.get_char(RMV_SEZIONE) == 'A')
        cell = rmov.get_real(RMV_IMPORTO).string(".");
    }    
    break;
  case 107:
    if (riga.tipo() == riga_mastrino)
    {
      const TRectype& mov = _mastrino.testata(rec);
      cell = mov.get(MOV_NUMDOC); cell.left_just(7);
      cell << ' ' << mov.get(MOV_PROTIVA);
    }  
    break;
  case 108:
    { 
      const TRectype& rmov = _mastrino.riga(rec);
      cell = rmov.get_real(RMV_IMPORTO).string(".");
      cell << ' ' << rmov.get(RMV_SEZIONE);
    }  
    break;
  case 109:
    if (riga.tipo() == riga_mastrino)
    {
      const long next_row = _mastrino.succ(rec, riga_mastrino);
      bool stampa = next_row >= _mastrino.items();
      if (!stampa)
      {
        const TDate& data = _mastrino[next_row].data();
        stampa = riga.data() != data;
      }
      if (stampa)
      {
        TImporto imp = riga.saldo();
        imp += _mastrino.saldo_iniziale();
        imp.normalize();
        cell = imp.valore().string(".");
        cell << ' ' << imp.sezione();
      }  
    }  
    break;
  default:
    break;
  }
  
  if (riga.tipo() == riga_mastrino)
    cell.set_colors(_mas_back, _mas_fore);
  else
    cell.set_colors(_con_back, _con_fore);
}

bool TMastrini_grid::on_record(long rec)
{
  if (_mastrino[rec].tipo() != riga_mastrino)
    rec = _mastrino.pred(rec, riga_mastrino);

  TRiga_mastrino& riga = _mastrino[rec];
  TMask& gm = mask();
  set_imp(gm.field(F_TOTRIG_SAL), riga.saldo());
  gm.set(F_TOTRIG_DAR, riga.dare());
  gm.set(F_TOTRIG_AVE, riga.avere());

  return TRUE;
}

void TMastrini_grid::on_dbl_cell(long rec, short id)
{
  if (_mastrino.expandable(rec)) 
    _mastrino.expand(rec);
  else
    _mastrino.collapse(rec);
  update();  
}

void TMastrini_grid::on_grid_button()
{                           
  const long total = _mastrino.items();                      
  if (total > 0)
  {
    TProgind* pi = NULL;
      
    if (total > 50)
      pi = new TProgind(total, "Aggiornamento contropartite ...", FALSE, TRUE, 48);
    else
      begin_wait();  
    
    // Cerca l'ultima contropartita
    const long last_con = _mastrino.last(riga_contropartita);
    // Se non esistono contropartite devo espandere le righe
    const bool expand = last_con < 0;
    
#ifdef DBG
    const clock_t clock_start = clock();
#endif    
    
    if (expand)
    {                         
      long step = 0;
      for (long n = _mastrino.first(riga_mastrino); 
           n < _mastrino.items(); n = _mastrino.succ(n, riga_mastrino))
      {
        if (_mastrino.expandable(n))
          _mastrino.expand(n);
        if (pi)
        {
          pi->setstatus(++step); 
#ifdef DBG               
          if ((step & 0x7F) == 0)
          {
            const double sec = (clock() - clock_start) / CLOCKS_PER_SEC;
            if (sec > 0.0)
            {
              TString80 msg;
              msg.format("%ld records at %ld rec/sec", step, long(step/sec));
              pi->set_text(msg);
            }  
          }
#endif          
        }  
      }       
    }      
    else
    {                                             
      for (long n = last_con; n > 0; n = _mastrino.pred(n, riga_contropartita))
      {
        _mastrino.collapse(n);
        if (pi)
          pi->setstatus(total - n + 1);
      }    
    }
      
    if (pi)
      delete pi;
    else
      end_wait();  

    update();
  }
}

void TMastrini_grid::on_record_button(long rec)
{
  begin_wait();
    
  const TRectype& testata = _mastrino.testata(rec);
  TString text;
  text << "1|" << testata.get(MOV_NUMREG);
      
  const char* const appname = "cg2 -0";
  TMessage pn(appname, MSG_LN, text);  
  pn.send();

  TExternal_app a(appname);
  a.run();

  TMailbox mail;  
  if (mail.next_s(MSG_LN) != NULL)
  {
    if (yesno_box("Si desidera aggiornare il mastrino?"))
      reread();
  }                

  end_wait();
}

// Posso ridimensionare solo le descrizioni, le altre devono rimanere fisse per
// non perdere la formattazione su due righe
bool TMastrini_grid::on_resize_column(short cid, int new_size) 
{ 
  return cid == 103 || cid == 104;
}


void TMastrini_grid::read(const TBill& conto, 
                          int annoes, const TDate& dd, const TDate& ad,
                          const TString& dc, const TString& ac)
{
  destroy();
  _mastrino.read(conto, annoes, dd, ad, dc, ac);
  update();    
  update_mask();
}                          

void TMastrini_grid::reread()
{
  destroy();
  _mastrino.reread();
  update();
  update_mask();
}

void TMastrini_grid::update_mask() const
{
  TMask& gm = mask();    
  
  gm.set(F_ESERCIZIO, _mastrino.esercizio());
  gm.set(F_DADATA, _mastrino.inizio_periodo());
  gm.set(F_ADATA, _mastrino.fine_periodo());
  
  set_imp(gm.field(F_TOTPRO_SAL), _mastrino.saldo_iniziale());
  gm.set(F_TOTPRO_DAR, _mastrino.progressivo_dare_iniziale());
  gm.set(F_TOTPRO_AVE, _mastrino.progressivo_avere_iniziale());
  
  gm.reset(F_TOTRIG_SAL);
  gm.reset(F_TOTRIG_DAR);
  gm.reset(F_TOTRIG_AVE);

  set_imp(gm.field(F_TOTPER_SAL), _mastrino.saldo_periodo());
  gm.set(F_TOTPER_DAR, _mastrino.progressivo_dare_periodo());
  gm.set(F_TOTPER_AVE, _mastrino.progressivo_avere_periodo());

  set_imp(gm.field(F_TOTATT_SAL), _mastrino.saldo_finale());
  gm.set(F_TOTATT_DAR, _mastrino.progressivo_dare_finale());
  gm.set(F_TOTATT_AVE, _mastrino.progressivo_avere_finale());
  
  gm.enable(DLG_LINK, _mastrino.items() > 0);
}

void TMastrini_grid::load_colors()
{
  TConfig conf(CONFIG_USER, "cg3600b");
  _mas_back = conf.get_color("ColorMB", NULL, -1, FOCUS_BACK_COLOR);
  _mas_fore = conf.get_color("ColorMF", NULL, -1, FOCUS_COLOR); 
  _con_back = conf.get_color("ColorCB", NULL, -1, NORMAL_BACK_COLOR);
  _con_fore = conf.get_color("ColorCF", NULL, -1, NORMAL_COLOR); 
}              

void TMastrini_grid::save_colors()
{
  TConfig conf(CONFIG_USER, "cg3600b");
  conf.set_color("ColorMB", _mas_back);
  conf.set_color("ColorMF", _mas_fore); 
  conf.set_color("ColorCB", _con_back);
  conf.set_color("ColorCF", _con_fore); 
}              

void TMastrini_grid::set_colors()
{
  TColor_mask m(_mas_back, _mas_fore, _con_back, _con_fore);
  if (m.run() == K_ENTER)
  {
    m.get_colors(_mas_back, _mas_fore, _con_back, _con_fore);
    save_colors();
  }  
}

///////////////////////////////////////////////////////////
// TGrid_mask
///////////////////////////////////////////////////////////

class TGrid_mask : public TMask
{
  TMastrini_grid* _grid;
  
protected:  // TMask
  virtual TMask_field* parse_field(TScanner& sc);
  virtual bool on_key(KEY k);
  virtual void handler(WINDOW win, EVENT* ep);
  
  static bool link_handler(TMask_field& f, KEY k);
  
public:
  TMastrini_grid& grid() { CHECK(_grid, "What's grid?"); return *_grid; }

  TGrid_mask();
  virtual ~TGrid_mask() { }
}; 

TGrid_mask::TGrid_mask() 
          : _grid(NULL)
{
  read_mask("cg3600b", 0, 0);
  set_handler(DLG_LINK, link_handler);
}

TMask_field* TGrid_mask::parse_field(TScanner& sc)
{         
  TMask_field* f;
  if (sc.key() == "SP")
    f = _grid = new TMastrini_grid(this);
  else
    f = TMask::parse_field(sc);
  return f;  
} 

bool TGrid_mask::link_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {
    TGrid_mask& gm = (TGrid_mask&)f.mask();
    TMastrini_grid& grid = gm.grid();
    const long rec = grid.selected();
    if (rec >= 0 && rec < grid.items())
      grid.on_record_button(rec);
  }  
  return TRUE;
} 

bool TGrid_mask::on_key(KEY k)
{
  long rec = grid().selected();
  switch (k)
  {                         
  case K_LHOME:                                      
    rec = 0; 
    break;  
  case K_PREV: 
    rec -= _grid->visible_rows(); 
    if (rec < 0) rec = 0; 
    break;
  case K_UP:                                         
    if (rec > 0)
      rec--; 
    break;  
  case K_DOWN:                                         
    if (rec < _grid->items()-1)
      rec++; 
    break;  
  case K_NEXT: 
    rec += _grid->visible_rows(); 
    if (rec >= _grid->items())
      rec = _grid->items()-1;
    break;
  case K_LEND:  
    rec = _grid->items()-1; 
    break;
  default:     
    break;
  }
  if (rec != grid().selected()) 
  {
    grid().select(rec);        
    return TRUE;
  }  
    
  return TMask::on_key(k);
}  

void TGrid_mask::handler(WINDOW win, EVENT* ep)
{   
  static TGrid_field* _last_grid = NULL;
                      
  if (ep->type == E_MOUSE_DOWN && ep->v.mouse.button == 1)
  {   
    _last_grid = NULL;

    RCT rct; _grid->get_rect(rct);
    if (xvt_rect_has_point(&rct, ep->v.mouse.where))
      _last_grid = _grid;
  
    if (_last_grid)
    {
      TGrid_field& sht = (TGrid_field&)*_last_grid;
#if (XVT_PTK_VERSION_MAJOR > 4) || (XVT_PTK_VERSION_MAJOR == 4 && XVT_PTK_VERSION_MINOR >= 50)
      MENU_ITEM* menu = xvt_res_get_menu(BROWSE_BAR);
      if (menu)
      {         
        const PNT& p = ep->v.mouse.where;
        RCT cr; xvt_vobj_get_client_rect(win, &cr);
        XVT_POPUP_ALIGNMENT pa = XVT_POPUP_CENTER;
        if (p.h < cr.right / 3)
          pa = XVT_POPUP_LEFT_ALIGN;
        else
          if (p.h > 2 * cr.right / 3)
            pa = XVT_POPUP_RIGHT_ALIGN;
              
        xvt_menu_popup(menu->child, win, p, pa, NULL);
        xvt_res_free_menu_tree(menu);       
      }  
#else     
      ASK_RESPONSE r = xvt_dm_post_ask("Annulla", "Ripristina", "Salva", 
                                       "Ordinamento delle colonne");
      if (r == RESP_2)                                 
        sht.reset_columns_order();
      if (r == RESP_2 || r == RESP_3)  
        sht.save_columns_order();
#endif
      return;
    }
  }      
  if (ep->type == E_COMMAND)
  {
    if (_last_grid)
    {
      switch (ep->v.cmd.tag)
      {     
      case M_EDIT_UNDO:
        _last_grid->reset_columns_order();
      case M_EDIT_COPY:
        _last_grid->save_columns_order();
        break;            
      case M_EDIT_SEARCH:
        _last_grid->on_key(K_F11);
        break;
      default:
        break;  
      }
      return;
    }
  }
  TMask::handler(win, ep);
}  


///////////////////////////////////////////////////////////
// TQuery_mask
///////////////////////////////////////////////////////////
         
class TQuery_mask : public TMask
{ 
protected:    
  static bool esercizio_handler(TMask_field& f, KEY k);
  static bool data_handler(TMask_field& f, KEY k);
  static bool find_handler(TMask_field& f, KEY k);
  
public:
  void do_query(TGrid_mask& gm);

  TQuery_mask();
  virtual ~TQuery_mask() { }
};
  
TQuery_mask::TQuery_mask()
           : TMask("cg3600a")
{ 
  set_handler(F_ESERCIZIO, esercizio_handler);
  set_handler(F_DADATA, data_handler);
  set_handler(F_ADATA, data_handler);
  set_handler(DLG_FINDREC, find_handler);
}

bool TQuery_mask::esercizio_handler(TMask_field& f, KEY k)
{
  if (k == K_TAB && f.focusdirty())
  {  
    TEsercizi_contabili esc;                 
    const int anno = atoi(f.get());
    if (esc.exist(anno))
    {
      TMask& m = f.mask();
      TDate dd = m.get(F_DADATA);
      if (esc.date2esc(dd) != anno)
        m.set(F_DADATA, esc[anno].inizio());
      dd = m.get(F_ADATA);
      if (esc.date2esc(dd) != anno)
        m.set(F_ADATA, esc[anno].fine());
    }
    else                                     
    {
      if (anno > 0)
        return error_box("Esercizio inesistente: %d", anno);
    }  
  }
  return TRUE;
}

bool TQuery_mask::data_handler(TMask_field& f, KEY k)
{           
  bool ok = TRUE;
  if (k == K_ENTER)
  {                        
    const TQuery_mask& qm = (const TQuery_mask&)f.mask();
    const TEsercizi_contabili esercizi;
    int codice_esercizio = qm.get_int(F_ESERCIZIO);

    if (codice_esercizio == 0)
    {
      const short id_altra_data = f.dlg() == F_DADATA ? F_ADATA : F_DADATA;
      const TDate d = qm.get(id_altra_data);
      if (d.ok())
        codice_esercizio = esercizi.date2esc(d);
    }              
    
    if (f.empty())
    {
      if (codice_esercizio == 0)
        return error_box("E' necessario specificare almeno una data.");
      return TRUE;  
    }

    const TDate d = f.get();
    const int esercizio = esercizi.date2esc(d);
    if (codice_esercizio != 0)
    {
      if (esercizio != codice_esercizio)
        ok = error_box("La data deve appartenere all'esercizio %d", codice_esercizio);
    }
    else
    {
      if (esercizio == 0)
        ok = error_box("La data deve appartenere ad un esercizio contabile");
    }    
  }
  return ok;
}

bool TQuery_mask::find_handler(TMask_field& f, KEY k)
{
  if (k == K_SPACE)
  {
    const TQuery_mask& qm = (const TQuery_mask&)f.mask();
    short id;
    switch (qm.get(F_TIPO)[0])
    {
    case 'C': id = F_CLIENTE;    break;
    case 'F': id = F_FORNITORE;  break;
    default : id = F_SOTTOCONTO; break;
    }
    qm.field(id).on_key(K_F9);
  }
  return TRUE;
}
   
void TQuery_mask::do_query(TGrid_mask& gm)
{ 
  const char t = get(F_TIPO)[0];
  const int  g = get_int(F_GRUPPO);
  const int  c = get_int(F_CONTO);
  const long s = get_long((t <= ' ') ? F_SOTTOCONTO : ((t == 'C') ? F_CLIENTE : F_FORNITORE));
  const TBill conto(g, c, s, t);

  const int annoes = get_int(F_ESERCIZIO);
  const TDate da_data(get(F_DADATA));
  const TDate a_data(get(F_ADATA));

  const TString& da_caus = get(F_DACAUSALE);
  const TString& a_caus = get(F_ACAUSALE);

  conto.set(gm, F_GRUPPO, F_CONTO, F_SOTTOCONTO, 0, F_DESSOTTOC);
  
  gm.grid().read(conto, annoes, da_data, a_data, da_caus, a_caus);
        
  gm.run();
} 
   
///////////////////////////////////////////////////////////
// TMastrini_video
///////////////////////////////////////////////////////////

class TMastrini_video : public TApplication
{
  TQuery_mask* _qm;
  TGrid_mask* _gm;
  
  TArray _file;

protected:
  virtual bool create();
  virtual bool destroy();
  virtual bool menu(MENU_TAG);
  virtual void on_firm_change();
  
public:
  void open_files(int logicnum, ...);
};

void TMastrini_video::open_files(int logicnum, ...)  
{  
  va_list marker;
  va_start(marker, logicnum);
  while (logicnum > 0)
  {                                
    if (_file.objptr(logicnum))
      NFCHECK("File gia' aperto: %d", logicnum);
    else  
      _file.add(new TLocalisamfile(logicnum), logicnum);
    logicnum = va_arg(marker, int);
  }  
  va_end(marker);
}

bool TMastrini_video::create()
{                          
  xvt_statbar_set("", TRUE);

  open_files(LF_RMOV, LF_MOV, LF_CAUSALI, 0);
  open_files(LF_SALDI, LF_PCON, 0);
  
  _qm = new TQuery_mask;
  _gm = new TGrid_mask;

  dispatch_e_menu(BAR_ITEM(1));
  return TRUE;
} 

bool TMastrini_video::destroy()
{
  delete _gm;
  delete _qm;  

  _file.destroy();     
  
  return TRUE;
}

void TMastrini_video::on_firm_change()
{
  TEsercizi_contabili::update();
}

bool TMastrini_video::menu(MENU_TAG mt)
{                    
  if (mt == BAR_ITEM(1))
  {
    TQuery_mask& qm = *_qm;
    
    const TEsercizi_contabili esercizi;
    const int codice_ultimo_esercizio = esercizi.last();
    if (codice_ultimo_esercizio > 0)
    {
      const TEsercizio& esercizio = esercizi.esercizio(codice_ultimo_esercizio);
      qm.set(F_ESERCIZIO, codice_ultimo_esercizio);
      qm.set(F_DADATA, esercizio.inizio());
      qm.set(F_ADATA, esercizio.fine());
    }
     
    KEY key = 0;
    while (key != K_QUIT)
    {
      qm.reset(F_SOTTOCONTO);  // Azzera sottoconto
      qm.reset(F_CLIENTE);     // Azzera cliente
      qm.reset(F_FORNITORE);   // Azzera fornitore
      qm.reset(-5);            // Azzera causali
      
      key = qm.run();
      switch (key)
      {
      case K_ENTER:
        disable_menu_item(M_FONT);
        qm.do_query(*_gm);
        enable_menu_item(M_FONT);
        break;
      default:
        break;
      }    
    } 
    
    return FALSE;
  } 
  else
  {
    _gm->grid().set_colors();
  }
  
  return TRUE;
}

///////////////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////////////

int cg3600(int argc, char* argv[])
{          
  TMastrini_video mv;
  mv.run(argc, argv, "Mastrini");
  return 0;
}


