#define XI_INTERNAL
#include <xi.h>    

#ifdef __cplusplus
extern "C" {
#endif

#include <xiutils.h>

#ifdef __cplusplus
}
#endif

#include <colors.h>
#include <controls.h>
#include <keys.h>
#include <msksheet.h>
#include <urldefid.h>
#include <utility.h>


///////////////////////////////////////////////////////////
// TSpreadsheet
///////////////////////////////////////////////////////////

// @doc INTERNAL

// @class TSpreadsheet | Classe per la creazione di uno spreadsheet all'interno
//                       di una maschera
//
// @base public | TWindow
class TSpreadsheet : public TControl
{
  // @author:(INTERNAL) Guido

  // @cfriend TSheet_field
  friend class TSheet_field;

  // @access:(INTERNAL) Private Member

  // @cmember:(INTERNAL) Array di TToken_strings contenenti le righe
  TString_array _str;
  // @cmember:(INTERNAL) Array delle colonne disattivate (solo visualizzazione)
  TBit_array _column_disabled;
  // @cmember:(INTERNAL) Array dell celle disattivate (solo visualizzazione)
  TArray _disabled;

  // @cmember:(INTERNAL) Maschera in cui e' contenuto lo spreadsheet
  TMask _mask;
  // @cmember:(INTERNAL) Numero di colonne presenti nello spreadsheet
  int _columns;
  // @cmember:(INTERNAL) Indica se e' attivo lo spreadsheet
  bool _active;

  // @cmember:(INTERNAL) Funzione per la gestione di una riga dello sheet (vedi <t SPREADSHEET_NOTIFY>)
  SPREADSHEET_NOTIFY _notify;

  // @cmember:(INTERNAL) Campo corrente che si sta editando
  TOperable_field* _edit_field;
  // @cmember:(INTERNAL) Coordinata della riga della cella corrente
  int _cur_row;
  // @cmember:(INTERNAL) Numero del record sul file al quale fa riferimento la cella corrente
  int _cur_rec;
  // @cmember:(INTERNAL) Coordinata della colonna della cella corrente
  int _cur_col;
  // @cmember:(INTERNAL) Indica se la riga corrente e' stata modificata
  bool _row_dirty;
  // @cmember:(INTERNAL) Indica se la cella corrente e' stata modificata
  bool _cell_dirty;
  // @cmember:(INTERNAL) Permette di gestire i check OFF_ROW e OFF_CELL
  bool _check_enabled;
  // @cmember:(INTERNAL) Numero della riga che necessita aggiornamento (vengono aggiornate 
  //                                    nella <mf TSpreadsheet::on_idle>)
  int _needs_update;
  // @cmember:(INTERNAL) Numero della riga a cui saltare appena possibile
  int _selection_posted;

  // @cmember:(INTERNAL) Inizializza lo spreadsheet
  void init();
  // @cmember:(INTERNAL) Funzione che intercetta gli eventi dello spreadsheet
  friend void XVT_CALLCONV1 xiev_handler(XI_OBJ *itf, XI_EVENT *xiev);

  // @access Protected Member
protected:
  //@cmember Gestisce gli eventi delle celle (chiamata dal <mf TSpreadsheet::xiev_handler>)
  virtual bool event_handler(XI_OBJ* itf, XI_EVENT* xiev);
               
  //@cmember Copia una cella nel corrispondente campo della maschera e ne ritorna il contenuto
  const char* copy_cell2field(XI_OBJ* cell = NULL);
               
  //@cmember Gestisce l'uscita delle celle (chiamata dal <mf TSpreadsheet::list_handler>)
  bool off_cell_handler(XI_OBJ* cell = NULL);
  
  // @cmember Ritorna il campo della maschera corrispondente alla cella dello
  //          spreadsheet indicata da <p pos>
  TOperable_field* col2field(int pos) const;
  // @cmember Controlla se esiste il campo della maschera corrispondente alla cella dello
  //          spreadsheet indicata da <p pos>
  TOperable_field* test_field(int pos) const;
  // @cmember Ritorna il campo della maschera corrispondente alla cella dello
  //          spreadsheet indicata da <p cell> (chiama <mf TMask::col2field>)
  TOperable_field* cell2field(const XI_OBJ* cell) const;
  // @cmember Ritorna la posizione della colonna con identificatore <p cid>
  int cid2col(short cid) const;
  // @cmember Ritorna la colonna logica con identificatore <p cid>
  int cid2index(short cid) const;
  // @cmember Ritorna la colonna corrispondente al campo <p f> della maschera
  int field2col(const TOperable_field* f) const;

  // @cmember Aggiorna il record sullo spreadsheet
  void update_rec(int rec);
  TOperable_field* field(short id) const;

  // @cmember Converte il numero del record nel corrispondente numero della riga
  int rec2row(int rec);
  // @cmember Converte il numero della riga nel corrispondente
  //          numero del record
  int row2rec(int& row);
  // @cmember Setta la posizione (riga e colonna) del focus sullo spreadsheet.
  //          Ritorna il numero del record corrente
  int set_pos(int row, int col)
  { _cur_col = col; _cur_row = row; return _cur_rec = row2rec(_cur_row); }

  // @cmember Chiama la funzione specificata con la <mf TSpreadsheet::set_notify>
  bool notify(int row, KEY k);
  // @cmember Chiama la funzione specificata con la <mf TSpreadsheet::set_notify>
  //                                    ogni volta che c'e' una modifica nello spreadsheet
  void notify_change();
  // @cmember Permette di fare tutti gli aggiornamenti necessari (indicati in
  //          <p _needs_update>)
  void on_idle();
                      
  // @cmember Cerca la colonna col                    
  XI_OBJ* find_column(int col) const;
  
  TSheet_field& owner() const { return (TSheet_field&)*_fld; }

  // @access Public Member
public:
  // @cmember Modifica a video la riga
  void update(int row); 
  // @cmember Ritorna la disabilitazione della colonna <p col>
  bool column_disabled(int col) const { return _column_disabled[col]; }
  // @cmember Ritorna l' abilitazione della colonna <p col>
  bool column_enabled(int col) const { return !column_disabled(col); }

  // @cmember Ritorna il contenuto della riga <p n>-esima
  TToken_string& row(int n)
  { return _str.row(n); }
  // @cmember Aggiunge una riga allo spreadsheet passata come puntatore
  //          (vedi <mf TArray::add>)
  int add(const TToken_string& s)
  { return _str.add(s); }
  // @cmember Aggiunge una riga allo spreadsheet (vedi <mf TArray::add>)
  int add(TToken_string* s)
  { return _str.add(s); }
  // @cmember Inserisce un record in una posizione stabilita
  int insert(int rec);
  // @cmember Elimina il record <p rec>
  bool destroy(int rec = -1, bool update_sheet = TRUE);
  // @cmember Ritorna l'array di tutte le stringhe delle righe
  TString_array& rows_array()
  { return _str; }
  
  // @cmember Trova una colonna abilitata a partire da colonna            
  int find_enabled_column(int rec, int colonna, int direction) const;
  // @cmember Trova un record abilitato a partire da rec
  int find_enabled_record(int rec, int direction) const;
  // @cmember Permette di mettere il focus su una cella
  void set_focus_cell(int riga, int colonna);
  // @cmember Abilita/disabilita tutto lo spreadsheet (vedi <mf TMask::activate>)
  void activate(bool on);
  // @cmember Permette di abilitare/disabilitare una colonna
  void enable_column(int col, bool on = TRUE);
  // @cmember Permette di eliminare una colonna dallo spreadsheet
  void delete_column(const int col) const;
  // @cmember Sposta la colonna dalla posizione <p fromindex> alla posizione
  //          <p toindex>
  void move_column(const int fromindex, const int toindex) const;
  // @cmember Permette di invertire la posizione di due colonne
  void swap_columns(const int fromid, const int toid) const;
  // @cmember Permette di invertire la posizione di due righe
  void swap_rows(const int fromindex, const int toindex);

  // @cmember Setta la larghezza della colonna
  void set_column_width(const int col, const int width) const;
  // @cmember Setta il titolo della colonna
  void set_column_header(const int col, const TString& header) const;
  // @cmember Setta l'allineamento di una colonna
  void set_column_justify(int col, bool right);
  // @cmember Permette di abilitare/disabilitare una singola cella
  void enable_cell(int row, int column, bool on = TRUE);
  // @cmember Controlla se una cella e' disabilitata
  bool cell_disabled(int row, int column) const;

  // @cmember Ritorna la maschera che appartiene allo spreadsheet
  TMask& sheet_mask() const;

  // @cmember Ritorna la maschera cui appartiene lo spreadsheet
  TMask& mask() const;
  
  // @cmember Ricopia i campi della maschera nel record <p n>-esimo ed
  //          aggiorna il display
  void mask2str(int n);
  // @cmember Ricopia i campi del record <p n>-esimo nella maschera ed
  //          aggiorna il display
  void str2mask(int n);
  // @cmember Apre la maschera della riga <p n>-esima editando la riga
  KEY edit(int n);
  
  // @cmember Ritorna il numero di righe dello sheet
  int items() const
  { return _str.items(); }
  // @cmember Ritorna il record corrente
  int selected() const
  { return _cur_rec; }
  // @cmember Seleziona una riga dandogli il focus
  void select(int r, bool scrollto);
  // @cmember Ritorna il numero di colonne presenti nello spreadsheet
  int columns() const
  { return _columns; }
  
  // @cmember Seleziona una riga appena possibile
  void post_select(int r);

  // @cmember Controlla se e' stato modificato una cella dello spreadsheet
  bool dirty() const
  { return owner().dirty(); }
  // @cmember Permette di indicare se e' stata modificata una cella dello spreadsheet
  void set_dirty(bool spork = TRUE)
  { owner().set_dirty(spork); }
  
  // @cmember Ritorna il valore della variabile active
  bool active() const
  { return _active; }
  
  // @cmember Ritorna se e' possibile lasciare il focus dallo spreadsheet (TRUE se e' possibile)
  bool test_focus_change();

  // @cmember Setta il membro <p _notify> al valore <p n>
  void set_notify(SPREADSHEET_NOTIFY n)
  { _notify = n; }
  
  // @cmember Costruttore
  TSpreadsheet(WINDOW parent, short dlg, short x, short y, short dx, short dy, const char* maskname, int maskno, const char* head, TSheet_field* owner);
  // @cmember Distruttore
  virtual ~TSpreadsheet();
};

// @doc INTERNAL

// @mfunc Costruttore
TSpreadsheet::TSpreadsheet(
  WINDOW parent,         // @parm Finestra alla quale appartiene lo spreadsheet
  short dlg,             // @parm Identificatore del campo
  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* maskname,  // @parm Nome del file della maschera
  int maskno,            // @parm Numero identificativo della maschera nel file
  const char* head,      // @parm Titolo delle colonne
  TSheet_field* o)       // @parm Indica il campo della maschera che contiene lo spreadsheet
: 
  _mask(maskname, maskno), _notify(NULL), 
  _cur_row(0), _cur_col(1), _cur_rec(0), _edit_field(NULL), _active(TRUE),
  _row_dirty(FALSE), _cell_dirty(FALSE), _check_enabled(TRUE), 
  _needs_update(-1), _selection_posted(-1)
{
  const int NUMBER_WIDTH = 3;
  const int MAX_COL = 32;
  int m_width[MAX_COL], v_width[MAX_COL];
  int fixed_columns = 1;                               // Number of fixed columns
  
  TControl::_fld = o;
  sheet_mask().set_sheet(o);
  
  // Calcolo larghezza massima tabella

  TToken_string header(head);
  TToken_string new_header(256);
  int i = 0, tot_width = NUMBER_WIDTH+1;
  int f_width = tot_width<<1;                          // 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);

    const int cid = FIRST_FIELD+i;                    // Column & Field ID
    const TOperable_field & f = (TOperable_field &) _mask.field(cid);            // Field on mask
 //  CHECKD(f, "The spreadsheet mask needs ALSO field ", cid);

    TString testa(h);
    const int at = testa.find('@');
    const int m = f.size();                 // Memory width
    int v = m;                               // Video width
    if (at >= 0)
    {
      const TString& wi = testa.mid(at+1);
      v = atoi(wi);
      if (wi.find('F') >= 0) 
      {
        fixed_columns++;
        f_width += v+1;
      }  
      testa.cut(at);
      v = max(at, v+(f.has_query_button() ? 1 : 0));
    } 
    else 
    {                                                     
      v = max(testa.len(), m+(f.has_query_button() ? 1 : 0));
    }
    if (v > 69)
      v = 69;  

    m_width[i] = m+1;                                 // m = number of allowed chars
    v_width[i] = v+1;                                 // v = width of column
    if (v >= max_width) max_width = v+1;
    
    tot_width += v_width[i];
    
    new_header.add(testa);
  }
  _columns = i;
  
  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, dlg,
                                        rct.top, rct.left, rct.bottom-rct.top,
                                        XI_ATR_ENABLED | XI_ATR_VISIBLE,
                                        NORMAL_COLOR, NORMAL_BACK_COLOR,    // normal
                                        NORMAL_COLOR, DISABLED_BACK_COLOR,  // disabled
                                        FOCUS_COLOR,                        // active
                                        0);
  
  CHECK(listdef, "Can't create spreadsheet definition");
  listdef->app_data = (long)this;
  XI_LIST_DEF* l = listdef->v.list;
  l->width = rct.right - rct.left;
  l->min_heading_height = xi_button_calc_height_font_id(xvt_default_font());
  l->sizable_columns = TRUE;
  l->movable_columns = TRUE;
  l->scroll_bar = TRUE;                                  
  l->scroll_bar_button = TRUE;
  l->fixed_columns = fixed_columns;
  l->active_back_color = FOCUS_BACK_COLOR;
  l->white_space_color = MASK_DARK_COLOR;     
  l->rule_color        = MASK_DARK_COLOR;
  
  // Definizione della prima colonna (numero di riga)
  word attr = XI_ATR_RJUST;                                            
  if (sheet_mask().id2pos(FIRST_FIELD -1) != -1)
    attr |= XI_ATR_SELECTABLE;                         
  
  XI_OBJ_DEF* coldef = xi_add_column_def(listdef, 0, attr, 0, 
                                         NUMBER_WIDTH * XI_FU_MULTIPLE, NUMBER_WIDTH+1, 
                                         attr & XI_ATR_SELECTABLE ? "X" : "");

  coldef->app_data = (long)this;
  coldef->v.column->heading_platform = TRUE;
  coldef->v.column->column_platform = TRUE;
  
  if (attr & XI_ATR_SELECTABLE)
  { 
    coldef->v.column->icon_rid = ICO_SEARCH;
    coldef->v.column->icon_x = 2;          
    if (listdef->v.list->min_heading_height < 20)
      listdef->v.list->min_heading_height = 20;
  }  
  else
    coldef->v.column->center_heading = TRUE; 


  for (h = new_header.get(0), i = 0; h; h = new_header.get(), i++)
  {                  
    const TString testo(h);
    const int cid = FIRST_FIELD+i;                     // Column & Field ID
    const TOperable_field & f = (const TOperable_field &)_mask.field(cid);      // Field on mask
    const int acqua = f.class_id();

    long flags = XI_ATR_EDITMENU | XI_ATR_AUTOSCROLL | XI_ATR_FOCUSBORDER;
    
    if (AUTOSELECT)
      flags |= XI_ATR_AUTOSELECT;
    
    switch (acqua)
    {
    case CLASS_EDIT_FIELD:
      if (f.right_justified())    
        flags |= XI_ATR_RJUST;
      break;
    case CLASS_REAL_FIELD:
      flags |= XI_ATR_RJUST; 
      break;
    default:
      break;
    }      
    if (f.active()) flags |= XI_ATR_ENABLED;
    else _column_disabled.set(i);

    coldef = xi_add_column_def(listdef, cid, flags, cid, 
                               v_width[i] * XI_FU_MULTIPLE, m_width[i], 
                               (char*)(const char*)testo);

    coldef->app_data = (long)this;
    coldef->v.column->heading_platform = TRUE;
    coldef->v.column->center_heading = TRUE;
  }

  // Create the whole thing!
  _obj = xi_create(itf, listdef);
  xi_dequeue();                       // Flush events in XOL
  xi_tree_free(listdef);              // Free definitions
  
  CHECKD(_obj, "Can't create spreadsheet ", owner().dlg());   
  
  update_tab_cid();
}

TSpreadsheet::~TSpreadsheet() 
{                    
}             

TMask& TSpreadsheet::sheet_mask() const
{ 
  return ((TSpreadsheet*)this)->_mask;  
}                                  
 

// Converts a row number in the correspondig record number
int TSpreadsheet::row2rec(int& row)
{
  int rows;
  const long* handle = xi_get_list_info(_obj, &rows);
  
  if (row < 0) 
    row = 0;
  else 
  {
    if (row >= rows) 
      row = rows-1;
  }    
  const int r = (int)handle[row];
  
  CHECKD(r >= 0 && r < items(), "Sheet line out of range: ", row);

  return r;
}


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

// Retrieves the corresponding field of the mask from a spredsheet cell
TOperable_field* TSpreadsheet::test_field(int pos) const
{
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  CHECKD(pos >= 0 && pos < num, "Bad column number", pos);

  TOperable_field* good = NULL;
  for (short id = column[pos]->cid; ; id += 100)
  {
    TOperable_field* f = field(id);
    if (f == NULL) break;             // Search failed
    good = f;                         // We've found a field with the proper ID ...
    if (f->active()) break;           // ... and it's active: end of search
  }                                 
  return good;
}

// Retrieves the corresponding field of the mask from a spredsheet cell
TOperable_field* TSpreadsheet::col2field(int pos) const
{
  TOperable_field* good = test_field(pos);

  CHECKD(good, "Can't find field corresponding to column ", pos);
  return good;
}


// Retrieves the corresponding field of the mask from a spredsheet cell
TOperable_field* TSpreadsheet::cell2field(const XI_OBJ* cell) const
{ 
  XI_OBJ cella;
  if (cell == NULL)                                     
  {
    XI_MAKE_CELL(&cella, _obj, _cur_row, _cur_col);
    cell = &cella;
  }
  return col2field(cell->v.cell.column);
}

int TSpreadsheet::cid2col(short cid) const
{                                                 
  CHECKD(cid >= FIRST_FIELD, "Bad column id ", cid);
  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  for (int c = num-1; c > 0; c--)
  {
    if (column[c]->cid == cid)
      return c;
  }
  return 0;
}

int TSpreadsheet::cid2index(short cid) const
{                                                 
  CHECKD(cid >= FIRST_FIELD, "Bad column id ", cid);
  return (cid % 100) - 1;
}

int TSpreadsheet::field2col(const TOperable_field* f) const
{         
  const short cid = FIRST_FIELD + cid2index(f->dlg());
  return cid2col(cid);
}

void TSpreadsheet::update_rec(int rec)
{                 
  const int riga = rec2row(rec);
  if (riga >= 0)
  {                 
    const bool has_focus = rec == selected() && mask().focus_field().dlg() == owner().dlg();
    
//    if (has_focus) xi_set_focus(get_interface());          
    
    XI_OBJ row;
    XI_MAKE_ROW(&row, _obj, riga);
    xi_cell_request(&row);              // Update internal values
    
    if (has_focus)  
    {
      str2mask(_cur_rec);
      set_focus_cell(riga, _cur_col);
    }  
  }  
  
  if (_needs_update == rec)
    _needs_update = -1;
}

// Cerca una colonna abilitata a partire da colonna
// La prima cella utilizzabile ha indice 1
// rec e' un numero di record assoluto
// colonna e' un numero di colonna a video: puo' succedere che la 3 corrisponda al campo 107
int TSpreadsheet::find_enabled_column(int rec, int colonna, int direction) const
{                      
  CHECKD(direction == +1 || direction == -1, "Bad column search direction", direction);

  int num;
  XI_OBJ** column = xi_get_member_list(_obj, &num);
  if (colonna <= 0 || colonna >= num) 
    colonna = 1;
  
  int c = colonna;
  do
  { 
    const short n = column[c]->cid - FIRST_FIELD;
    if (!cell_disabled(rec, n))
      return c;
    
    c += direction;  
    if (c >= num) 
      c = 1; 
    else
      if (c <= 0) 
        c = num-1;
  } 
  while (c != colonna);
  
  return 0;  
}

int TSpreadsheet::find_enabled_record(int rec, int direction) const
{
  for (int r = rec+direction; r >= 0 && r < items(); r += direction)
  {
    if (find_enabled_column(r, 1, +1) > 0)
      return r;
  }
  return -1;
}

// riga (da 0), colonna (0 = numero, 1 = prima cella, ...)
void TSpreadsheet::set_focus_cell(int riga, int colonna)
{                                  
//  xi_set_focus(get_interface());            

  const int rec = row2rec(riga);   
  colonna = find_enabled_column(rec, colonna, +1);

  if (colonna > 0)
  {               
    WINDOW win = xvt_scr_get_focus_vobj();     // Puo' essere NULL per cui poi non funziona ...
    WINDOW par = parent();                     // ... la xi_set_focus(&cell)
    if (win != par)
    {
      xvt_scr_set_focus_vobj(par);  
      xvt_vobj_raise(par);          
    }
  
    XI_OBJ cell;     
    XI_MAKE_CELL(&cell, _obj, riga, colonna);

    xi_set_focus(&cell);          
    
    if (rec != _cur_rec)
    {      
      _cur_rec = rec;
      _cur_row = riga;            
/* Guy!      str2mask(_cur_rec); */
      _row_dirty = FALSE;
    }  
    _edit_field = col2field(_cur_col = colonna); // qui
  } 
}

// @doc INTERNAL

// @mfunc Inserisce un record in una posizione stabilita
//
// @rdesc Ritorna la posizione nella quale e' stato inserito il record. Se non riesce ad inserirlo
//                              ritorna -1.
int TSpreadsheet::insert(
  int rec) // @parm Numero del record da inserire nello spreadsheet
  
  // @comm Non e' possibile inserire un nuovo record nel caso nello spreadsheet vi siano
  //                     almeno 999 righe oppure se lo spreadsheet non e' attivo.
{ 
  static bool ininsert = FALSE;
  
  if (ininsert || items() >= 999)
    return -1;
  
  if (rec < 0 && items() > 0 && !owner().append() )
    rec = _cur_rec + 1;

  ininsert = TRUE;
  int r = _str.insert(new TToken_string(80), rec);

  const bool ok = notify(r, K_INS);
  if (ok) 
  {  
    _disabled.insert(NULL, r);  
    owner().post_insert(r);
    xi_insert_row(_obj, INT_MAX);
  
    // Notifica che l'inserimento h terminato
    notify(r, K_CTRL + K_INS);
    xi_cell_request(_obj);             
  } 
  else
  {
    _str.destroy(r);
    r = -1;
  }

  ininsert = FALSE;
  return r;
}

// @doc INTERNAL

// @mfunc Elimina una riga
//
// @rdesc Ritorna il risultato dell'operazione:
//
// @flag TRUE | Se la riga esisteve e quindi e' stata eliminata
// @flag FALSE | Se la riga non esisteve
bool TSpreadsheet::destroy(
  int rec, // @parm Numero della riga da eliminare
  bool update_sheet) // @parm Aggiornamento visuale dell sheet
  
  // @comm Se il parametro <p rec> assume valore -1 vengono eliminate tutte le righe presenti
  //             nello spreadsheet       
{   
  static bool indestroy = FALSE;
  
  if ( indestroy )
    return FALSE;

  indestroy = TRUE;
  bool ok = TRUE;
  
  if (rec < 0)
  {
    _disabled.destroy();
    _str.destroy();
    set_dirty(_row_dirty = FALSE);
  }
  else
  {
    _disabled.destroy(rec, TRUE);       // Destroy enable info
    ok = _str.destroy(rec, TRUE);       // Destroy line
  }  

  if (ok && mask().is_running() && update_sheet) 
    update(-1);

  indestroy = FALSE;
  return ok;
}

// @doc INTERNAL

// @mfunc Modifica a video la riga
void TSpreadsheet::update(
  int rec) // @parm Numero della riga da modificare
  
  // @comm Se il valore di <p row> e' minore di 0 viene aggiornato l'intero spreadsheet          
{                        
  if (rec < 0)
  {                 
    
    int num = 0;
    const long* handle = xi_get_list_info(_obj, &num);

    int first = 0, last = 0;
    bool scroll = items() == 0; // || !owner().mask().is_running();
    if (!scroll)
    {
      xi_get_visible_rows(_obj, &first, &last);   
      scroll = items() <= handle[first];
    }
    
    if (scroll)
      xi_scroll(_obj, XI_SCROLL_FIRST);
    else  
    {
      const long as = AUTOSELECT ? XI_ATR_AUTOSELECT : 0;
      xi_scroll_rec(_obj, handle[first], NORMAL_COLOR, XI_ATR_ENABLED | as, 0);
    }  
      
    _needs_update = -1;                                 // Clear pending row update
  }
  else
    update_rec(rec);  
}


void TSpreadsheet::notify_change()
{
  if (!_row_dirty)
  {               
    str2mask(_cur_rec);                
    _edit_field = cell2field(NULL);    // Ricalcola correttamente il campo corrente
    
    notify(_cur_rec, K_SPACE);
    _row_dirty = _cell_dirty = TRUE;
    set_dirty();
  }
}

const char* TSpreadsheet::copy_cell2field(XI_OBJ* cell)
{       
  const char* txt;
  
  if (cell == NULL)
  {
    XI_OBJ cella;
    XI_MAKE_CELL(&cella, _obj, _cur_row, _cur_col);
    txt = xi_get_text(&cella, NULL, -1);
  }
  else                                  
    txt = xi_get_text(cell, NULL, -1);
  
  if (_edit_field->is_editable())
  {                      
    if (_edit_field->class_id() == CLASS_ZOOM_FIELD)
      _edit_field->set(row(_cur_row).get(_cur_col - 1)); 
    const char* val = _edit_field->is_kind_of(CLASS_LIST_FIELD) ? txt :
                      (const char*)((TEditable_field*)_edit_field)->win2raw(txt);
                        
    _edit_field->set(val);  
    _edit_field->set_dirty();        // Get it dirty!
  }
  return _edit_field->get();
}

bool TSpreadsheet::off_cell_handler(XI_OBJ *cell)
{
  bool ok = TRUE;
  if (_edit_field != NULL)
  {
    const char* nuo = copy_cell2field(cell);
    if (_edit_field->on_key(_edit_field->is_edit() ? K_TAB : K_SPACE) == FALSE)   // Test it
      ok = *nuo == '\0';             // Se e' vuoto lascia stare
    else      
      _cell_dirty = FALSE;
    if (_row_dirty)  
      mask2str(_cur_rec);            // Update sheet row
  }  
  return ok;
}      

bool TSpreadsheet::test_focus_change()
{
  bool ok = xi_move_focus(get_interface()) ? TRUE : FALSE;
  return ok;
}

// Certified 75%
bool TSpreadsheet::event_handler(XI_OBJ* itf, XI_EVENT *xiev)
{
  static KEY _lastab = K_TAB;    
  
  BOOLEAN& refused = xiev->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;
    }
    else
      refused = TRUE;
    break;
  case XIE_GET_LAST:
    xiev->v.rec_request.data_rec = items()-1;
    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())
      refused = TRUE;
    else
      xiev->v.rec_request.data_rec = n;
  }
    break;
  case XIE_CELL_REQUEST:
  {
    const int rec = (int)xiev->v.cell_request.rec;
    const int maxlen = xiev->v.cell_request.len;

    const char* src = NULL;
    int nm;
    XI_OBJ** obj = xi_get_member_list(xiev->v.cell_request.list, &nm);
    const int num = xiev->v.cell_request.col_nbr;
    const int cid = obj[num]->cid;

    if (cid >= FIRST_FIELD)
    {
      if (rec < items())
      {   
        const int col = cid - FIRST_FIELD;
        const int curr = _cur_rec;
        _cur_rec = rec;
        const TOperable_field* f = field(cid);
        _cur_rec = curr;
        if (f)
        {
          const TEditable_field* e = (const TEditable_field*)f;
          src = row(rec).get(col);                // Set value for cell
          if (src && *src)
          {
            if (e->is_kind_of(CLASS_LIST_FIELD))
              /* src = src */;                      // Leave code as is
            else
              src = e->raw2win(src);                // Get formatted string
          }  
          
          if (cell_disabled(rec, col))
          {
            xiev->v.cell_request.back_color = DISABLED_BACK_COLOR;
            xiev->v.cell_request.attrib &= ~XI_ATR_ENABLED;
          }  
          
          if (e->has_query_button()) 
          {
            xiev->v.cell_request.button = TRUE;
            xiev->v.cell_request.button_on_focus = TRUE;
          }  
        }    
        else
          xiev->v.cell_request.back_color = DISABLED_BACK_COLOR;
      }  
    } 
    else
    { 
      src = format("%d", rec+1);   
    } 

    char* dst = xiev->v.cell_request.s;
    if (src && *src)
    {
      strncpy(dst, src, maxlen);
      dst[maxlen-1] = '\0';
    }  
    else
      *dst = '\0';
  }
    break;
  case XIE_CHG_CELL:
    if (_edit_field && !_cell_dirty)
    {
      notify_change();
      _cell_dirty = TRUE;
      _edit_field->set_dirty();    
    }  
    break;
  case XIE_BUTTON:
    if (_check_enabled)
    {
      on_idle();    // Termina tutti gli eventuali update in corso
      
      if (xiev->v.xi_obj->type == XIT_CELL)
      {                
        XI_CELL_DATA& cell = xiev->v.xi_obj->v.cell;
        int num;
        XI_OBJ** column = xi_get_member_list(_obj, &num);
        CHECK(cell.column < num, "Bad column");
        int rec = cell.row;
        rec = row2rec(rec);
        const int col = column[cell.column]->cid - FIRST_FIELD;
        if (!cell_disabled(rec, col))
        {
          if (xi_move_focus(xiev->v.xi_obj))  
            dispatch_e_char(parent(), K_F9);
        }    
      }  
      else
        if (xiev->v.xi_obj->type == XIT_LIST)
        {
          owner().mask().notify_focus_field(owner().dlg());
          insert(-1); 
        }  
    }    
    break;
  case XIE_SELECT:
    if (xiev->v.xi_obj->type == XIT_ROW)
    {          
      _check_enabled = FALSE;    
      if (_cell_dirty)
        off_cell_handler();
      
      const int oldrec = _cur_rec;
      set_pos(xiev->v.select.xi_obj->v.row, 1);
      if (oldrec != _cur_rec)
      {
        _row_dirty = FALSE;
        on_idle();            // Forces update delayed by str2mask
      }
      
      const int button_pos = sheet_mask().id2pos(FIRST_FIELD-1);
      if (button_pos >= 0)
      {       
        TMask & sm = owner().sheet_mask();
        TMask_field& button = sm.fld(button_pos);  
        if (button.active())
        {
          str2mask(_cur_rec);
          button.on_hit();   
          if (sm.dirty())
          { 
            notify_change();
            mask2str(_cur_rec);
          } 
        }  
      }
      _check_enabled = TRUE;    
      owner().highlight();
    }    
    refused = TRUE;
    break;
  case XIE_DBL_CELL:
  {
    _check_enabled = FALSE;
    
    const int oldrec = _cur_rec;
    if ( xiev->v.xi_obj != NULL )
    {
      set_pos(xiev->v.xi_obj->v.cell.row, xiev->v.xi_obj->v.cell.column);
    }
    
    if (oldrec != _cur_rec || !_row_dirty)
    {
      _row_dirty = FALSE;
      notify_change();
    }
    const KEY k = edit(_cur_rec);
    if (k == K_ENTER)
    {
//      update_rec(_cur_rec);
      _row_dirty = TRUE;
    } else
      if (k == K_DEL) 
      {
        _row_dirty = _cell_dirty = FALSE;

        if (_cur_rec >= items())
        {
          _row_dirty = _cell_dirty = FALSE;
          _cur_rec = items()-1;
          _cur_row = 0; _cur_col = 1;        
        }
        if (_cur_rec >= 0 && _cur_rec < items())
          set_focus_cell(_cur_row, _cur_col);
      }

    _check_enabled = TRUE;
  }
    break;
  case XIE_ON_LIST:
    owner().mask().notify_focus_field(owner().dlg());
    break;
  case XIE_OFF_LIST:
    on_idle();
    break;
  case XIE_ON_ROW:
    if (_check_enabled)
    {                     
      const int row = xiev->v.xi_obj->v.row; // Riga in cui si sta entrando
      int rows;                              // Numero totale di righe attive
      const long* handle = xi_get_list_info(_obj, &rows);
      
      // Calcola il numero del record corrispondente alla riga
      const bool exist = row >= 0 && row < rows;
      int next_rec = exist ? (int)handle[row] : items()-1;
      
      // Se la riga non esiste o non ha nessuna cella valida abilitata ...
      // ... cerca la prossima riga valida e rifiuta l'ingresso in questa
      if (!exist || find_enabled_column(next_rec, 1, +1) <= 0)
      {
        next_rec = find_enabled_record(next_rec, next_rec >= _cur_rec ? +1 : -1);
        post_select(next_rec);
        refused = TRUE;
        break;
      }
    
      // Setta _cur_rec in base a alla riga e cella correnti
      set_pos(xiev->v.xi_obj->v.row, _cur_col);
      if (_cur_rec < items() && notify(_cur_rec, K_TAB))
      {
        /* Guy! str2mask(_cur_rec); */
        _row_dirty = FALSE;
      }
      else
      {
        _cur_row = _cur_rec = 0;
        refused = TRUE;
      }  
    }
    break;
  case XIE_OFF_ROW:
    if (_check_enabled)
    {                 
      _check_enabled = FALSE;                   // Avoid recursion!
      if (_row_dirty && active())
      {  
        bool ok = owner().sheet_mask().check_fields();
        if (ok) 
        {                   
          mask2str(_cur_rec);                   // Update sheet with mask contents
          ok = notify(_cur_rec, K_ENTER);       // Notify edit
          _row_dirty = FALSE;                   // Avoid double notifications!
        } 
        if (ok)
        {
          xvt_statbar_refresh();
        }  
        else
        {
          refused = TRUE;
        }  
      }                                 
      if (!refused) // Notifica l'abbandono della riga
        refused = !notify(_cur_rec, K_CTRL+K_TAB);

      _check_enabled = TRUE; 
    }
    break;
  case XIE_ON_CELL:
    if (_check_enabled)
    {                 
      const int physical_column = xiev->v.xi_obj->v.cell.column;
      TOperable_field* f = test_field(physical_column);
      bool disabled = TRUE;
      if (f)
      {
        const int logical_column = (f->dlg()-FIRST_FIELD) % 100;
        disabled = cell_disabled(_cur_rec, logical_column);     // If the cell is disabled ...
      }                   
      if (disabled)
      {                                              
        const int dir = _lastab == K_TAB ? +1 : -1;
        const int nex = find_enabled_column(_cur_rec, physical_column, dir);
        if (nex > 0)      // If at least one enabled cell exists
          set_focus_cell(_cur_row, nex);
        else
          refused = TRUE;
      } 
      else
      {  
        _edit_field = f;
        _cur_col = physical_column;
        _edit_field->set_focusdirty(_cell_dirty = FALSE);
        
        // Azzera il flag di update_pending
        EVENT e; e.type = E_UPDATE;
        xi_eh(_obj->itf->v.itf->xvt_win, &e);
      }  
    }
    break;
  case XIE_OFF_CELL:
    if (_edit_field && _check_enabled && _cell_dirty)
    {
      _check_enabled = FALSE;
      XI_OBJ* cell = xiev->v.xi_obj;
      refused = !off_cell_handler(cell);
      _check_enabled = 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 = int(rec * 100L / n);
  }
    break;
  case XIE_CLEANUP:
    break;
  case XIE_CHAR_CELL:
    if (_edit_field) 
    {
      const KEY k = xiev_to_key(xiev);
      switch(k)
      {
        case K_F8:
        case K_F9:
          if (_edit_field != NULL)
          {                
            notify_change();
            copy_cell2field();
          }
        case K_F2:
        case K_F11: 
          if (_check_enabled && active())
          {
            _check_enabled = FALSE;                             // Disable checks
            notify_change();                                                             
            bool ok = TRUE;

            if (k == K_F9 && _edit_field->is_kind_of(CLASS_LIST_FIELD)) // list
            { 
              XI_OBJ cell; XI_MAKE_CELL(&cell, _obj, _cur_row, _cur_col); 
              // droppa giu'       
              TList_field& lst = (TList_field&)(*_edit_field);
              TDropDownList ddl(&cell, lst.get_codes(), lst.get_values());
              ddl.open();   
              while (ddl.is_open()) 
                do_events();      
              // sdroppa su
              copy_cell2field();
            }
            else // edit_field
            { 
              // Notifica il corretto campo col focus sulla maschera
              sheet_mask().notify_focus_field(_edit_field->dlg());
              
              ok = _edit_field->on_key(k); 
                              
              if (!ok && k == K_F9)                    // Ricerca non completata?
              {
                _edit_field = &owner().sheet_mask().focus_field();
                const int col = field2col(_edit_field);  
               
                if (col != _cur_col)                   // Ricerca alternativa
                {
                  _cur_col = col;
                  dispatch_e_char(parent(), K_F9);
                }  
              } 
            } 
            if (ok)
            {
              mask2str(_cur_rec);
              on_idle();                               // Update immediately!
            }    
            else
              set_focus_cell(_cur_row, _cur_col);
            
            _check_enabled = TRUE;                     // Re-enable checks
          }  
          break;
        case K_CTRL + '-':
          { 
            if (owner().mask().id2pos(DLG_DELREC) && notify(_cur_rec, K_DEL))
            {
              int rec = _cur_rec;
              destroy(rec);
              if (rec < items())
                str2mask(rec);
              notify(rec, K_CTRL+K_DEL);        // Notifica l'avvenuta cancellazione
              if (rec >= items())
                rec = items() - 1;
              if (rec >= 0)
                select(rec, FALSE);
            }
            refused = TRUE;
          }      
          break;     
        case K_CTRL + '+':                
          owner().mask().notify_focus_field(owner().dlg());
          insert(-1); 
          refused = TRUE;
          break;        
        case K_CTRL+K_PREV:                
          xi_scroll(_obj, XI_SCROLL_PGUP);
          break;
        case K_CTRL+K_NEXT:
          xi_scroll(_obj, XI_SCROLL_PGDOWN);
          break;
        case K_CTRL+K_HOME:
          xi_scroll(_obj, XI_SCROLL_FIRST);
          break;
        case K_CTRL+K_END:
          xi_scroll(_obj, XI_SCROLL_LAST);
          break;
        default:
          if (k > K_CTRL)
          {     
            refused = TRUE;
          } else
          if (is_edit_key(k) && !_edit_field->on_key(k)) 
          {
            refused = TRUE;
            beep();
          } else 
          if (_edit_field->is_kind_of(CLASS_LIST_FIELD))
          {      
            notify_change();    // Aggiorna valore corrente listbox
            
            TList_field& lst = ((TList_field&)*_edit_field);
            bool changed = FALSE;
            switch (k)         
            {        
            case K_RIGHT:
              changed = lst.select_next();
              break;
            case K_LEFT:
              changed = lst.select_prev();
              break;
            default:
              if (k >= ' ' && k <= 'z')        
                changed = lst.select_by_initial(k);
              break;  
            }   
            if (changed)
            {
              XI_OBJ cell; XI_MAKE_CELL(&cell, _obj, _cur_row, _cur_col); 
              xi_set_text(&cell, (char*)(const char*)lst.get());
              _cell_dirty = TRUE;
            }
            refused = TRUE;
          }
          break;
      }
    }  
    break;
  case XIE_XVT_EVENT:
    if (xiev->v.xvte.type == E_CHAR)
    {
      const KEY k = e_char_to_key(&xiev->v.xvte);
      switch (k)
      {
      case K_ROWEDIT:
        xiev->type = XIE_DBL_CELL;
        xiev->v.xi_obj = NULL;
        event_handler(itf, xiev);
        break;
      case K_TAB:
      case K_BTAB:
        _lastab = k;
        break;
      case K_UP:       
      case K_DOWN:
        _lastab = (_cur_col == 2) ? K_BTAB : K_TAB;
        break;
      case K_ENTER:
      case K_SHIFT+K_ENTER:
        {
          const int next_rec = find_enabled_record(_cur_rec, k == K_ENTER ? +1 : -1);
          if (next_rec >= 0)
          {
            dispatch_e_char(parent(), k == K_ENTER ? K_DOWN : K_UP);
          }
          else
          {
//          if (xi_move_focus(get_interface()))  // Test di uscita dalla cella corrente
//          {                 
//            xi_set_focus(_obj);                // Riporta il focus sullo sheet
            dispatch_e_char(parent(), k == K_ENTER ? K_F3 : K_F4);
//          }  
            refused = TRUE;
          }
        }  
        break;
      case K_ESC:
        if (xi_move_focus(get_interface()))
        {
          owner().mask().on_key(k);
          refused = TRUE;
        }  
        break;  
      case K_CTRL + '-':
      case K_CTRL + '+':                
      case K_CTRL+K_PREV:                
      case K_CTRL+K_NEXT:
      case K_CTRL+K_HOME:
      case K_CTRL+K_END:
        break;
      default:
        if (k > K_CTRL)
        {
          if (xi_move_focus(get_interface()))
            owner().mask().on_key(k);
          refused = TRUE; 
        }  
        break;    
      }  
    }
    break;
  default:
    break;
  }
  
  return !refused;
}

void TSpreadsheet::activate(bool on)
{
  _active = on;
  
  const dword old = xi_get_attrib(_obj);
  dword att = on ? (old & ~XI_ATR_NAVIGATE) : (old | XI_ATR_NAVIGATE);
  if (old != att) 
  {
    int num;
    XI_OBJ** columns = xi_get_member_list(_obj, &num);
    
    xi_move_focus(get_interface());                  // Set focus to interface
    if (on)
      att |= XI_ATR_TABWRAP;
    else 
      att &= ~XI_ATR_TABWRAP;
    
    xi_set_attrib(_obj, att);        
    
    for (int col = 1; col < num; col++)
    {
      XI_OBJ* column = columns[col];
      att = xi_get_attrib(column);
      if (on) 
      {
        att &= ~XI_ATR_READONLY;
        if (AUTOSELECT)
          att |= XI_ATR_AUTOSELECT;
      }  
      else    
      {
        att |= XI_ATR_READONLY;
        att &= ~XI_ATR_AUTOSELECT;
      }  
      xi_set_attrib(column, att);          // Set new attributes
    }  
  }  
}

void TSpreadsheet::select(int rec, bool scrollto)
{                 
  if (!scrollto)
  { 
    int rows;
    const long* handle = xi_get_list_info(_obj, &rows);

    int first = 0, last = 0;
    xi_get_visible_rows(_obj, &first, &last);
    
    scrollto = rec < handle[first] || rec > handle[last];
  }                 
  
  if (scrollto)
  {
    const long as = AUTOSELECT ? XI_ATR_AUTOSELECT : 0;
    xi_scroll_rec(_obj, rec, NORMAL_COLOR, XI_ATR_ENABLED | as, 0);
  }  

  const int row = rec2row(rec);
  const bool has_focus = mask().focus_field().dlg() == owner().dlg();
  if (has_focus)
  {
    _cur_rec = -1;
    set_focus_cell(row, 1); 
  }
  else
  {
    _edit_field = col2field(_cur_col = 1);
    _cur_rec = rec;
    _cur_row = row;            
    str2mask(_cur_rec);
    _row_dirty = FALSE;
  }
  notify(rec, K_TAB);
}

void TSpreadsheet::post_select(int rec)
{
  _selection_posted = rec;
}

void TSpreadsheet::on_idle()
{
  if (_needs_update >= 0)
  {
    if (_needs_update < items()) 
      update_rec(_needs_update);
    _needs_update = -1;
  }
  
  if (_selection_posted >= 0)
  { 
    const int next_row = _selection_posted;
    _selection_posted = -1;
    if (next_row < items())
      select(next_row, FALSE);
  }
}

// @doc INTERNAL

// @mfunc Cerca la colonna col
XI_OBJ* TSpreadsheet::find_column(
  int col) const    // @parm  Indice o identificatore colonna
{     
  CHECKD(col >= 0, "Bad column ", col);
  if (col < FIRST_FIELD)         // Se e' un indice trasformalo in identificatore
    col += FIRST_FIELD;
  else
    if (col >= FIRST_FIELD+100)  // Riportalo nel range 101 - 199
      col = FIRST_FIELD + cid2index(col);
  
  int num;
  XI_OBJ** columns = xi_get_member_list(_obj, &num);
  for (int c = num-1; c > 0; c--)
  {          
    if (columns[c]->cid == col)
      break;
  }  

  if (c <= 0)
  {
    yesnofatal_box("Can't find column with id=%d", col);
    c = 1;
  }  
  
  return columns[c];
}


TMask& TSpreadsheet::mask() const
{
  TMask* m = (TMask*)xvt_vobj_get_data(parent());
  return *m;
}


// Ritorna il campo con l'identificatore dato della maschera dello sheet
TOperable_field* TSpreadsheet::field(short id) const
{                                   
  const TMask & sm = owner().sheet_mask();
  const int pos = sm.id2pos(id);
  return pos < 0 ? NULL : (TOperable_field*)&sm.fld(pos);
}


// Ricopia i campi della maschera nel record dato ed aggiorna il display
void TSpreadsheet::mask2str(int rec)
{                 
  TToken_string& r = row(rec);

  owner().mask2row(rec, r);

  if (_needs_update != rec)
  {
    if (_needs_update >= 0)
      update_rec(_needs_update); // Double update!
    _needs_update = rec;
  }  
}


// Certified 50%
// @doc INTERNAL
                                                          
// @mfunc Permette di abilitare/disabilitare una singola cella
void TSpreadsheet::enable_cell(
  int row,    // @parm Riga della cella da abilitare/disabilitare
  int column, // @parm Colonna della cella da abilitare/disabilitare
  bool on)    // @parm Indica l'operazione da effettuare sulla cella:
  //
  // @flag TRUE | La cella viene abilitata (default)
  // @flag FALSE| La cella viene disabilitata
{
  if (column >= FIRST_FIELD)
    column = cid2index(column);
  TBit_array* ba = (TBit_array*)_disabled.objptr(row);
  if (ba == NULL)
  {
    if (on) return;   // Don't waste time and memory
    ba = new TBit_array(column);
    _disabled.add(ba, row);
  }

  if (column >= 0)
  {
    ba->set(column, !on);
  }  
  else
  {
    if (on)
      _disabled.destroy(row, FALSE);  // Let's save some memory!
    else
    {
      ba->set(_columns); // Force right array size
      ba->set();         // Set all bits
    }
  }
}

// @doc INTERNAL

// @mfunc Permette di abilitare/disabiltare una colonna
void TSpreadsheet::enable_column(
  int col,  // @parm Numero della colonna da abilitare/disabilitare
  bool on)  // @parm Indica l'operazione da effettuare sulla colonna:
  //
  // @flag TRUE | Abilita la colonna (default)
  // @flag FALSE| Disabilita la colonna
{
  if (col >= FIRST_FIELD)
    col = cid2index(col);  
    
  const bool change = _column_disabled[col] == on;
  _column_disabled.set(col, !on);

  if (change)
  {
    XI_OBJ* column = find_column(col);
    dword attr = xi_get_attrib(column);
    if (on) attr |= XI_ATR_ENABLED;
    else    attr &= ~XI_ATR_ENABLED;

    xi_set_attrib(column, attr);          // Set new attributes
//    update(-1);                         // Guy: Meno andi e rivieni
  }
}
         
void TSpreadsheet::delete_column( const int col ) const
{   
  XI_OBJ* column = find_column(col);
  xi_delete( column );
}


void TSpreadsheet::move_column( const int fromindex, const int toindex) const
{
  int num;
  XI_OBJ** columns = xi_get_member_list(_obj, &num);
  CHECKD(fromindex+1 < num, "Can't move column ", fromindex);
  XI_OBJ* column = columns[fromindex+1];
  
  xi_move_column( column, toindex );
}

void TSpreadsheet::swap_columns(const int fromid, const int toid) const
{
  int num;
  XI_OBJ** columns = xi_get_member_list(_obj, &num);
  
  XI_OBJ* from_column = XI_NULL_OBJ;
  XI_OBJ* to_column = XI_NULL_OBJ;
  int from_pos = 0;      
  int to_pos = 0;      
  
  for (int c = num-1; c > 0; c--)
  {
    XI_OBJ* column = columns[c];
    if (column->cid == fromid) 
    {
      from_column = column; 
      from_pos = c;
    } 
    if (column->cid == toid)
    {
      to_column = column; 
      to_pos = c;
    };
  }
  CHECKD(from_pos, "Can't swap column ", fromid);
  CHECKD(to_pos,   "Can't swap column ", toid);
  xi_move_column(from_column, to_pos);
  xi_move_column(to_column, from_pos);
} 

void TSpreadsheet::swap_rows( const int fromindex, const int toindex)
{
  _str.swap(fromindex, toindex);
  _disabled.swap(fromindex, toindex);
}

void TSpreadsheet::set_column_width(const int col, const int width) const
{
  XI_OBJ* column = find_column(col);
  xi_set_column_width(column, width);   // Force redraw
}

void TSpreadsheet::set_column_header(const int col, const TString&  header) const
{
  XI_OBJ* column = find_column(col);
  xi_set_text(column, (char *)(const char *)header );
  RCT r; xi_get_rect(column, &r); 
  xi_set_column_width(column, (r.right-r.left+1) / CHARX);   // Force redraw
}

void TSpreadsheet::set_column_justify(int col, bool right)
{
  XI_OBJ* column = find_column(col);
  dword attr = xi_get_attrib(column);
  if (right)
    attr |= XI_ATR_RJUST;
  else
    attr &= ~XI_ATR_RJUST;  
  xi_set_attrib(column, attr);          // Set new attribute
  update(-1);
}

// Certified 99%
// @doc INTERNAL

// @mfunc Controlla se una cella o un'intera riga e' disabilitata
//                                
// @rdesc Se column e' minore di zero si considera l'intera riga
// @rdesc Ritorna lo stato della cella indicata:
//
// @flag TRUE | Se la cella e' disabilitata
// @flag FALSE| Se la cella e' abilitata
bool TSpreadsheet::cell_disabled(int row, int column) const
{
  TBit_array* ba = (TBit_array*)_disabled.objptr(row);
  bool d;
  if (column < 0)
    d = (ba == NULL) ? FALSE : (ba->ones() >= columns()-1); 
  else   
  {                                                       
    d = _column_disabled[column];  // Controlla la colonna
    if (d == FALSE && ba != NULL)  // Se la colonna e' disabilitata e' inutile proseguire
      d = (*ba)[column];           // Controlla la cella
  }
  return d;  
}


// Certified 75%
void TSpreadsheet::str2mask(int riga)
{
  if (riga == items())
  {          
    const int curr = _cur_rec;
    _cur_rec = riga;
    owner().sheet_mask().reset();
    _cur_rec = curr;
    mask2str(riga);
    return;
  }

  TToken_string& r = row(riga);
  
  owner().row2mask(riga, r);
}


// Certified 100%
bool TSpreadsheet::notify(int rec, KEY k)
{
  const bool ok = _notify ? _notify(owner(), rec, k) : TRUE;
  if (k == K_ENTER)
    set_dirty(ok ? TRUE : 3);
  return ok;  
}

// Certified 99%
KEY TSpreadsheet::edit(int n)
{
  str2mask(n);
  KEY k = owner().run_editmask(n);

  if (active()) 
  {
    if (k == K_ENTER)
    {
      mask2str(n);
    } 
    else
    {
      if (k == K_DEL)
      {
        const bool ok = notify(n, K_DEL); // Notifica intenzione di cancellare
        if (ok) 
        {
          destroy(n);
          if (n < items())
            str2mask(n);
          notify(n, K_CTRL+K_DEL);        // Notifica l'avvenuta cancellazione
        }           
      } 
      else
        if (k == K_ESC)
        {            
          str2mask(n);                    // Ripristina valori precedenti
        }
    }
  }  
  
  return k;
}

///////////////////////////////////////////////////////////
// TSheet_field
///////////////////////////////////////////////////////////

// Certified 100%
TSheet_field::TSheet_field(TMask* m)
            : TOperable_field(m), _append(TRUE)
{
}

// Certified 100%
word TSheet_field::class_id() const
{
  return CLASS_SHEET_FIELD;
}

bool TSheet_field::is_kind_of(word cid) const
{ return cid == CLASS_SHEET_FIELD || TOperable_field::is_kind_of(cid); }

// Certified 100%
TSheet_field::~TSheet_field()
{
}

// Certified 100%
void TSheet_field::reset()
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl; 
  if (s->items())
    s->select(0, FALSE);
  s->destroy();
  s->sheet_mask().reset();
  set_dirty();                    // Reset any error (dirty = 3)
  set_dirty(mask().is_running()); // Set dirty state based on mask status
  force_update();
}

// Certified 100%
void TSheet_field::destroy(int r, bool update_sheet)
{
  ((TSpreadsheet*)_ctl)->destroy(r, update_sheet);
}

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


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

// Certified 100%
void TSheet_field::create(WINDOW parent)
{                                    
  const TMask& m = mask();
  const TString head(_ctl_data._park);
  _ctl = new TSpreadsheet(parent, dlg(), _ctl_data._x, _ctl_data._y, _ctl_data._width, _ctl_data._height, 
                          m.source_file(), m.sheets(), head, this);

  _ctl->show(shown());
  if (!_flags.enable_default)
  {
    _flags.enabled = TRUE;  // Lo sheet e' sempre operabile anche se non editabile
    disable();
  }  
  const TMask & s = sheet_mask();
  
  for (short id = FIRST_FIELD; ; id++)
    if (s.id2pos(id) < 0) break;
  _last_column_id = id - 1;
}


// Certified 100%
TString_array& TSheet_field::rows_array() const
{
  return ((TSpreadsheet*)_ctl)->rows_array();
}


// Certified 100%
// Ritorna l'indice della prima riga vuota dello sheet
int TSheet_field::first_empty() const
{                             
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  const int max = (int)s->items();
  for (int n = 0; n < max; n++)
    if (s->row(n).empty_items())
      break;
  return n;
}


// @doc EXTERNAL

// @mfunc Ritorna nuova riga dello spreadshhet
//
// @rdesc Ritorna la stringa letta
TToken_string& TSheet_field::row(
  int n) // @parm Numero della riga da leggere/creare

  // @comm Se il parametro <p n> e maggiore del numero massimo di righe presenti
  //       o minore di 0 viene aggiunta una riga vuota in fondo allo spreadsheet
  //       (viene chiamata la <mf TSpreadsheet::add>)
{                            
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  const int max = (int)s->items();
  if (n < 0 || n >= max)
  {
    if (n < 0) n = first_empty();
    if (n >= max) 
      n = (int)s->add(new TToken_string(80));
  }
  return s->row(n);
}

// @doc EXTERNAL

// @mfunc Forza l'aggiornamento dei dati della riga sullo schermo
void TSheet_field::force_update(
  int r)  // @parm Numero della riga da aggiornare
  
  // @comm Se il parametro <p r> assume valore -1 vengono aggiornate tutte le righe presenti
  //             nello spreadsheet       
  
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->update(r);
}

int TSheet_field::items() const
{                   
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
   return (int)s->items();
}

int TSheet_field::selected() const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl; 
  
  return s != NULL ?(int)s->selected() : -1;
}

void TSheet_field::set_notify(SPREADSHEET_NOTIFY n)
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->set_notify(n);
}

// Certified 50%
void TSheet_field::highlight() const
{
//  TEditable_field::highlight();
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  if (s->_check_enabled)
  {                            
    int rows; xi_get_list_info(s->_obj, &rows);
    if (rows > 0)
    {                  
      if (s->notify(s->_cur_rec, K_TAB))
      {
        s->set_focus_cell(s->_cur_row, s->_cur_col);
        s->str2mask(selected());
      }  
    }  
  }    
}

void TSheet_field::enable(bool on)
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->activate(on);
}

bool TSheet_field::enabled() const
{
//  return items() > 0;
  return TMask_field::enabled();
}

int TSheet_field::cid2index(short cid) const
{                                                              
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  return s->cid2index(cid);
}

void TSheet_field::enable_column(int column, bool on)
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->enable_column(column, on);
}


void TSheet_field::enable_cell(int row, int column, bool on)
{                          
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->enable_cell(row, column, on);
}

bool TSheet_field::column_enabled(int column) const
{                          
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  return s->column_enabled(column);
}

bool TSheet_field::column_disabled(int column) const
{                          
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  return s->column_disabled(column);
}

bool TSheet_field::cell_disabled(int row, int column) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  return s->cell_disabled(row, column);
}

// Matteo
void TSheet_field::delete_column( const int col ) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->delete_column( col );
}

void TSheet_field::move_column( const int fromindex, const int toindex ) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->move_column(fromindex, toindex);
}

void TSheet_field::swap_columns(const int fromid, const int toid) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->swap_columns(fromid, toid);
}

void TSheet_field::swap_rows( const int fromindex, const int toindex)
{                                            
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->swap_rows(fromindex, toindex);
}

void TSheet_field::set_column_width( const int col, const int width ) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->set_column_width(col, width);
}

void TSheet_field::set_column_header( const int col, const TString& header ) const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->set_column_header(col, header);
}

void TSheet_field::set_column_justify(int col, bool right)
{                 
  if (col < FIRST_FIELD)
    col += FIRST_FIELD;
  sheet_mask().field(col).set_justify(right);
  
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->set_column_justify(col, right);
}

TMask& TSheet_field::sheet_mask() const
{
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  return s->sheet_mask();
}

bool TSheet_field::on_hit()
{
  if (!mask().is_running())           // Inizializzazione maschera
  {
    force_update();               
    
    if (items() > 0)
    {
      mask().notify_focus_field(dlg()); // Fa' credere alla maschera che ha il focus ...
      select(0);                        // ... cosi' la set_focus_cell funziona bene
    }

    set_dirty(FALSE);
  }
  return TRUE;  
}  

void TSheet_field::select(int r, bool scrollto)
{ 
  TSpreadsheet* s = (TSpreadsheet*)_ctl;
  s->select(r, scrollto);
}  

bool TSheet_field::on_key(KEY k)
{                   
  if (k == K_TAB && items() > 0)  
  {               
    if (focusdirty())
    {
      TSpreadsheet* s = (TSpreadsheet*)_ctl;
      if (!s->test_focus_change())
        return FALSE;
    }
  }           
  
  if (k == K_ROWEDIT && items() > 0)
  {          
    select(items()-1);
    XI_EVENT xie;
    xie.type = XIE_DBL_CELL;
    xie.v.xi_obj = NULL;
    _ctl->event_handler(NULL, &xie);
    return TRUE;
  }
  
  return TOperable_field::on_key(k);
}

void TSheet_field::on_idle()
{
  ((TSpreadsheet*)_ctl)->on_idle();
}

void TSheet_field::exchange(bool show_value, const real& nuo)
{        
  TMask& m = sheet_mask();

  const real& vec = mask().exchange();
  
  if (vec != nuo)
  {
    TBit_array valuta(32);
    int i = 0;
    for (int f = FIRST_FIELD; ;f++, i++)
    {
      const int pos = m.id2pos(f);
      if (pos < 0) break;
      if (m.fld(pos).class_id() == CLASS_REAL_FIELD)
      {
        if (m.fld(pos).exchangeable())
          valuta.set(i);
      }
    }  
    
    for (int riga = 0; riga < items(); riga++)
    {
      TToken_string& r = row(riga);
      for (const char* s = r.get(i = 0); s; s = r.get(++i))
        if (*s > ' ' && valuta[i])
        {
          real v(s); 
          v *= nuo;
          v /= vec;
          v.round();
          r.add(v.string(), i);
        }
    }    
  }  
  
  m.set_exchange(show_value, nuo);
  
  if (mask().is_running()) 
    force_update();
}  

// Ricopia i campi della maschera nel record dato 
void TSheet_field::mask2row(int n, TToken_string & rec)
{
  const TMask& m = sheet_mask();

  rec.cut(0);
  const TSpreadsheet& s = (const TSpreadsheet&)*_ctl;

  for (short id = FIRST_FIELD; id <= _last_column_id ; id++)
  {                                 
    int pos = m.id2pos(id);
    const int firstpos = pos;
    
    if (pos >= 0)
    {  
      for(int dlg = id; pos >= 0; pos = m.id2pos(dlg += 100))
      {
        const TMask_field& f = m.fld(pos);
        if (f.shown() || f.ghost())
        {       
          rec.add(f.get());       
          if (active() && s.active())
          {
            const int col = cid2index(id);
  
            if (!s.column_disabled(col))
            {
              const bool on = f.enabled();
              enable_cell(n, col, on);
            }  
          }  
          break;
        }  
      }  
#ifdef DBG 
      if (pos < 0 && s.cid2col(id) > 0)
          yesnofatal_box("Mask2row: Non e' visibile il campo %d", id);
#endif    
    }
    if (pos < 0) 
      rec.add(firstpos >= 0 ? m.fld(firstpos).get() : " ");
  }
}


// Ricopia i campi del record dato nella maschera 
void TSheet_field::row2mask(int n, TToken_string & r)
{
  TString val(80);   
  
  TMask& m = sheet_mask();
  const int campi = m.fields();
  const TSpreadsheet& s = (const TSpreadsheet&)*_ctl;
  
  for (int i = 0; i < campi; i++)
  {
    TMask_field& f = m.fld(i);
    const short id = f.dlg();
    
    if (id >= FIRST_FIELD && id <= _last_column_id)
    {             
      const int index = cid2index(id);

      val = r.get(index);
      f.set(val);
      const bool on = s.active() && !cell_disabled(n, index);
      if (f.enabled() != on)
        f.enable(on);
    }
  }
  
  for (i = 0; i < campi; i++)
  {
    TMask_field& f = m.fld(i);
    const short id = f.dlg();

    if (id >= FIRST_FIELD && 
        !f.is_kind_of(CLASS_BUTTON_FIELD) &&
        (f.active() || f.ghost()))
    {
      if (f.has_check()) 
        f.check(STARTING_CHECK);
      f.set_dirty(FALSE);
      f.on_hit();
    }
  }
  
  for (i = 0; i < campi; i++)
  {
    TMask_field& f = m.fld(i);
    const short id = f.dlg();
    if (id > FIRST_FIELD)
    {
      if (f.dirty() == TRUE)
        f.set_dirty(FALSE);
    }    
  }
  
  val.format("Riga %d", n+1);
  m.set_caption(val);
}
