/** \file
 * \brief Cells Control.
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "iup.h"
#include "iupcbs.h"
#include "iupkey.h"

#include <cd.h>
#include <cd_old.h>
#include <cdiup.h>
#include <cddbuf.h>

#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drv.h"
#include "iup_stdcontrols.h"
#include "iup_controls.h"
#include "iup_cdutil.h"
#include "iup_register.h"


#define ICELLS_OUT -999

struct _IcontrolData
{
  iupCanvas canvas;  /* from IupCanvas (must reserve it) */

  cdCanvas* cd_canvas;       /* image canvas for double buffering     */
  int w;                     /* control width (pixels)                */
  int h;                     /* control height (pixels)               */
  int clipped;               /* cells bounding box clipping activated */
  int boxed;                 /* draw cells bounding box activated     */
  int bufferize;             /* bufferize on                          */
  int non_scrollable_lins;   /* number of non-scrollable lines        */
  int non_scrollable_cols;   /* number of non-scrollable columns      */
  long int bgcolor;          /* Empty area color and bgcolor          */
};


/* Function to inquire application cell data (line height) */
static int iCellsGetHeight(Ihandle* ih, int i)
{
  int size; 

  IFni cb = (IFni)IupGetCallback(ih, "HEIGHT_CB");
  if (!cb)
    return 30;   /* default value */

  size = cb(ih, i);
  if (size < 0)
    size = 0;

  return size;
}

/* Function to inquire application cell data (column width) */
static int iCellsGetWidth(Ihandle* ih, int j)
{
  int size; 

  IFni cb = (IFni)IupGetCallback(ih, "WIDTH_CB");
  if (!cb)
    return 60;   /* default value */

  size = cb(ih, j);
  if (size < 0)
    size = 0;

  return size;
}

/* Function used to calculate a cell limits */
static int iCellsGetLimits(Ihandle* ih, int i, int j, int* xmin, int* xmax, int* ymin, int* ymax)
{
  int result = 1;
  int xmin_sum = 0;
  int ymin_sum = 0;
  int w = ih->data->w;
  int h = ih->data->h;
  int _xmin, _xmax, _ymin, _ymax;

  /* Adjusting the inital position according to the cell's type. If it
   * is non-scrollable, the origin is always zero, otherwise the origin 
   * is the scrollbar position */
  int posx = (j <= ih->data->non_scrollable_cols)? 0: IupGetInt(ih, "POSX");
  int posy = (i <= ih->data->non_scrollable_lins)? 0: IupGetInt(ih, "POSY");
  int idx;

  /* Adding to the origin, the cells' width and height */
  for (idx = 1; idx < j; idx++)
    xmin_sum += iCellsGetWidth(ih, idx);
  
  for (idx = 1; idx < i; idx++)
    ymin_sum += iCellsGetHeight(ih, idx);

  /* Finding the cell origin */
  _xmin = xmin_sum - posx;
  _ymax = h - (ymin_sum - posy) - 1;

  /* Computing the cell limit, based on its origin and size */
  _xmax = _xmin + iCellsGetWidth(ih, j);
  _ymin = _ymax - iCellsGetHeight(ih, i);

  /* Checking if the cell is visible */
  if (_xmax < 0 || _xmin > w || _ymin > h || _ymax < 0)
    result = 0;

  if (xmin != NULL)
    *xmin = _xmin;

  if (xmax != NULL)
    *xmax = _xmax;

  if (ymin != NULL)
    *ymin = _ymin;

  if (ymax != NULL)
    *ymax = _ymax;

  return result;
}

/* Function to inquire application cell data (vertical span) */
static int iCellsGetVspan(Ihandle* ih, int i, int j)
{
  int result = 1;  /* default value */

  IFnii cb = (IFnii)IupGetCallback(ih, "VSPAN_CB");
  if (cb)
    result = cb(ih, i, j);

  if (result < 0)
    result = 1;

  return result;
}

/* Function to inquire application cell data (horizontal span) */
static int iCellsGetHspan(Ihandle* ih, int i, int j)
{
  int result = 1;  /* default value */

  IFnii cb = (IFnii)IupGetCallback(ih, "HSPAN_CB");
  if (cb)
    result = cb(ih, i, j);
  
  if (result < 0)
    result = 1;

  return result;
}

/* Function to inquire application cell data (#columns) */
static int iCellsGetNCols(Ihandle* ih)
{
  int result = 10;  /* default value */

  Icallback cb = IupGetCallback(ih, "NCOLS_CB");
  if (cb)
    result = cb(ih);

  if (result < 0)
    result = 0;

  return result;
}

/* Function to inquire application cell data (# lines) */
static int iCellsGetNLines(Ihandle* ih)
{
  int result = 10;  /* default value */

  Icallback cb = IupGetCallback(ih, "NLINES_CB");
  if (cb)
    result = cb(ih);

  if (result < 0)
    result = 0;

  return result;
}

/* Recalculation of first visible line */
static int iCellsGetFirstLine(Ihandle* ih)
{
  int i, j;
  int nlines = iCellsGetNLines(ih);
  int ncols  = iCellsGetNCols(ih);

  if (ih->data->non_scrollable_lins >= nlines)
    return 1;

  /* Looping the lines until a visible one is found */
  for (i = 1; i <= nlines; i++) { 
     for (j = 1; j <= ncols; j++) { 
        if (iCellsGetLimits(ih, i, j, NULL, NULL, NULL, NULL))
          return i; 
     }
  }
  return ICELLS_OUT;
}

/* Recalculation of first visible column */
static int iCellsGetFirstCol(Ihandle* ih)
{
  int i, j;
  int ncols  = iCellsGetNCols(ih);
  int nlines = iCellsGetNLines(ih);

  if (ih->data->non_scrollable_cols >= ncols)
    return 1;

  /* Looping the columns until a visible one is found */
  for (j = 1; j <= ncols; j++) { 
     for (i = 1; i <= nlines; i++) { 
        if (iCellsGetLimits(ih, i, j, NULL, NULL, NULL, NULL))
          return j;
     }
  }
  return ICELLS_OUT;
}

/* Function used to get the cells groups virtual size */
static void iCellsGetVirtualSize(Ihandle* ih, int* wi, int* he)
{
  int i, j;
  *wi = 0; *he = 0;

  /* Looping through all lines and columns, adding its width and heights
   * to the return values. So, the cells virtual size is computed */
  for (i = 1; i <= iCellsGetNLines(ih); i++)
    *he = *he + iCellsGetHeight(ih, i);
  for (j = 1; j <= iCellsGetNCols(ih); j++)
    *wi = *wi + iCellsGetWidth(ih, j);
}

/* Function used to calculate a group of columns height */
static int iCellsGetRangedHeight(Ihandle* ih, int from, int to)
{
  int i;
  int result = 0;
  
  /* Looping through a column adding the line height */
  for (i = from; i <= to; i++)
    result += iCellsGetHeight(ih, i);

  return result;
}

/* Function used to calculate a group of lines width */
static int iCellsGetRangedWidth(Ihandle* ih, int from, int to)
{
  int j;
  int result = 0;

  /* Looping through a column adding the column width */
  for (j = from; j <= to; j++)
    result += iCellsGetWidth(ih, j);

  return result;
}

/* Function used to turn a cell visible  */
static void iCellsSetFullVisible(Ihandle* ih, int i, int j)
{
  int xmin, xmax, ymin, ymax;
  int posx = IupGetInt(ih, "POSX");
  int posy = IupGetInt(ih, "POSY");
  int dx = 0;
  int dy = 0;
  
  /* Getting the frontiers positions for the visible cell */
  int min_x = iCellsGetRangedWidth(ih, 1, ih->data->non_scrollable_cols);
  int max_y = ih->data->h - iCellsGetRangedHeight(ih, 1, ih->data->non_scrollable_lins); 

  /* Getting the cell's area limit */
  iCellsGetLimits(ih, i, j, &xmin, &xmax, &ymin, &ymax);

  /* Adjusting the diference of the scrollbars' position (horizontal) */
  if (xmax > ih->data->w)
    dx = xmax - ih->data->w;

  /* Giving priority to xmin position. This can be seen by the usage
   * of dx at the left part of the expression (using the last dx).
   * This is the case wher the cell cannot be fitted. */
  if (xmin - dx < min_x)
    dx = - (min_x - xmin); 

  /* Adjusting the diference of the scrollbars' position (horizontal) */
  if (ymax > max_y)
    dy = - (ymax - max_y);
  if (ymin < 0)
    dy = -ymin;

  /* Adding the diference to scrollbars' position */
  posx += dx;
  posy += dy;

  /* Setting iup scrollbars' attributes */
  IupSetInt(ih, "POSX", posx); 
  IupSetInt(ih, "POSY", posy); 
}

/* Function used to make a cell the first visible one */
static void iCellsAdjustOrigin(Ihandle* ih, int lin, int col)
{ 
  int xmin_sum, ymin_sum;

  /* If the origin line is a non-scrollable one, the scrollbar position is
   * set to zero. Otherwise, the sum of the previous widths will be
   * set to the scrollbar position. This algorithm is applied to both
   * scrollbars */
  if (lin <= ih->data->non_scrollable_lins)
  {
     IupSetAttribute(ih, "POSY", "0"); 
  }
  else if (lin <= iCellsGetNLines(ih))
  { 
     ymin_sum = iCellsGetRangedHeight(ih, ih->data->non_scrollable_lins+1, lin-1);
     IupSetInt(ih, "POSY", ymin_sum); 
  }

  /* As said before... */
  if (col <= ih->data->non_scrollable_cols)
  {
     IupSetAttribute(ih, "POSX", "0"); 
  }
  else if (col <= iCellsGetNCols(ih))
  { 
     xmin_sum = iCellsGetRangedWidth(ih, ih->data->non_scrollable_cols+1, col-1);
     IupSetInt(ih, "POSX", xmin_sum); 
  }
}

/* Function used for the scrollbar's update; usually needed when the
 * object has modified its size or the cells sizes has changed.   */
static void iCellsAdjustScroll(Ihandle* ih, int canvas_w, int canvas_h)
{ 
  int virtual_height, virtual_width;

  /* Getting the virtual size */
  iCellsGetVirtualSize(ih, &virtual_width, &virtual_height); 

  IupSetInt(ih, "XMAX", virtual_width);
  IupSetInt(ih, "YMAX", virtual_height);

  /* Setting the object scrollbar position */
  IupSetInt(ih, "DX", canvas_w);
  IupSetInt(ih, "DY", canvas_h);
}

/* Function used to call the client; is used when a cell must be repainted. */
static void iCellsCallDrawCb(Ihandle* ih, int xmin, int xmax, int ymin, int ymax, int i, int j)
{
  int oldxmin, oldxmax, oldymin, oldymax, oldclip;
  int w = ih->data->w;
  int h = ih->data->h;
  IFniiiiiiC draw_cb;
  cdCanvas* old_cnv = cdActiveCanvas();

  /* Getting clipping area for post restore */
  oldclip = cdCanvasClip(ih->data->cd_canvas, CD_QUERY);
  cdCanvasGetClipArea(ih->data->cd_canvas, &oldxmin, &oldxmax, &oldymin, &oldymax);

  if (ih->data->clipped)  /* Clipping the cell area */
  { 
    int cxmin, cxmax, cymin, cymax;
    cdCanvasClip(ih->data->cd_canvas, CD_CLIPAREA);
    cxmin = xmin < 0 ? 0 : xmin;
    cymin = ymin < 0 ? 0 : ymin;
    cxmax = xmax > w ? w : xmax;
    cymax = ymax > h ? h : ymax;
    cdCanvasClipArea(ih->data->cd_canvas, cxmin, cxmax, cymin, cymax);
  }

  draw_cb = (IFniiiiiiC)IupGetCallback(ih, "DRAW_CB");
  if (draw_cb)
  {
    if (old_cnv != ih->data->cd_canvas) /* backward compatibility code */
      cdActivate(ih->data->cd_canvas);

    draw_cb(ih, i, j, xmin, xmax, ymin, ymax, ih->data->cd_canvas);

    if (old_cnv && old_cnv != ih->data->cd_canvas)
    {
      cdActivate(old_cnv);
      cdCanvasActivate(ih->data->cd_canvas);
    }
  }

  cdCanvasClip(ih->data->cd_canvas, oldclip);
  cdCanvasClipArea(ih->data->cd_canvas, oldxmin, oldxmax, oldymin, oldymax);
}

/* Render function for one cell in a given coordinate.  */
static void iCellsRenderCellIn(Ihandle* ih, int i, int j, int xmin, int xmax, int ymin, int ymax)
{
  int k;
  int w = ih->data->w;
  int h = ih->data->h;
  int hspan = 1;
  int vspan = 1;

  /* Checking if the cells is out of range. (no span will affect it!) */
  if (xmin > w || ymax < 0)
    return;
 
  /* Calculating cell spans */
  hspan = iCellsGetHspan(ih, i, j);
  vspan = iCellsGetVspan(ih, i, j);

  /* if any span is set to zero, then another cell invaded its space and
   * the cell does not need to draw itself */
  if (hspan == 0 || vspan == 0)
    return;

  /* Increasing cell's width and height according to its spans */
  for(k = 1; k < hspan; k++)
    xmax += iCellsGetWidth(ih, j+k);
  for(k = 1; k < vspan; k++)
    ymin -= iCellsGetHeight(ih, i+k);

  /* Checking if the cell expanded enough to appear inside the canvas */
  if (xmax < 0 || ymin > h)
    return;

  /* Calling application's draw callback */
  iCellsCallDrawCb(ih, xmin, xmax, ymin, ymax, i, j);

  /* Drawing a box in cell's area */
  if (ih->data->boxed)
  { 
     cdCanvasForeground(ih->data->cd_canvas, CD_BLACK);
     cdCanvasRect(ih->data->cd_canvas, xmin, xmax, ymin, ymax);
  }
}

/* Repaint function for all cells in a given range */
static void iCellsRenderRangedCells(Ihandle* ih, int linfrom, int linto, int colfrom, int colto)
{
  int i, j;
  int xmin, xmax, ymin, ymax;
  int refxmin, refxmax;

  /* Getting first cell limits: this function is slow to be called everytime */
  iCellsGetLimits(ih, linfrom, colfrom, &xmin, &xmax, &ymin, &ymax);

  /* Initializing current reference position */
  refxmin = xmin; 
  refxmax = xmax;

  /* Looping through the cells adding next cell width and height */
  for (i = linfrom; i <= linto; i++)
  { 
    xmin = refxmin; 
    xmax = refxmax;
    for (j = colfrom; j <= colto; j++)
    { 
      iCellsRenderCellIn(ih, i, j, xmin, xmax, ymin, ymax);
      xmin = xmax;
      xmax = xmin + (j == colto ? 0 : iCellsGetWidth(ih, j+1));
    }
    ymax = ymin;
    ymin = ymax - (i == linto ? 0 : iCellsGetHeight(ih, i+1));
  }
}

/* Repaint function for all cells */
static void iCellsRenderCells(Ihandle* ih)
{
  int sline  = ih->data->non_scrollable_lins;
  int scol   = ih->data->non_scrollable_cols;
  int nlines = iCellsGetNLines(ih);
  int ncols  = iCellsGetNCols(ih);

  cdCanvasBackground(ih->data->cd_canvas, ih->data->bgcolor); 
  cdCanvasClear(ih->data->cd_canvas);

  /* Repainting the four parts of the cells: common cells, non-scrollable
   * columns, non-scrollable lines, and non-scrollable margin 
   * (line and column) */
  iCellsRenderRangedCells(ih, sline+1, nlines, scol+1, ncols);
  iCellsRenderRangedCells(ih, sline+1, nlines, 1, scol);
  iCellsRenderRangedCells(ih, 1, sline, scol+1, ncols);
  iCellsRenderRangedCells(ih, 1, sline, 1, scol);
}

static void iCellsRepaint(Ihandle* ih)
{
  if (ih->data->cd_canvas == NULL)
    return;

  /* If object is buffering, it will not be drawn */
  if (ih->data->bufferize == 1)
    return;

  /* update render */
  iCellsRenderCells(ih);

  /* update display */
  cdCanvasFlush(ih->data->cd_canvas);
}

static int iCellsRedraw_CB(Ihandle* ih)
{
  if (!ih->data->cd_canvas)
    return IUP_DEFAULT;

  /* update display */
  cdCanvasFlush(ih->data->cd_canvas);
  return IUP_DEFAULT;
}

/* Function used to calculate the cell coordinates limited by a hint */
static int iCellsGetRangedCoord(Ihandle* ih, int x, int y, int* lin, int* col, int linfrom, int linto, int colfrom, int colto)
{
  int i, j, k;
  int rxmax, rymin;
  int xmin, xmax, ymin, ymax;
  int refxmin, refxmax;
  int w = ih->data->w;
 
  /* Getting the first cell's  limit -- based on the range */
  iCellsGetLimits(ih, linfrom, colfrom, &xmin, &xmax, &ymin, &ymax);

  /* Initializing reference position */
  refxmin = xmin; 
  refxmax = xmax;
  
  /* Looping through the cells adding next cell width and height */
  for (i = linfrom; i <= linto; i++)
  { 
    xmin = refxmin; 
    xmax = refxmax;
    if (!(ymax < 0))
    { 
      for (j = colfrom; j <= colto; j++)
      { 
        if (!(xmin > w))
        { 
          int hspan = iCellsGetHspan(ih, i, j);
          int vspan = iCellsGetVspan(ih, i, j);
          if (hspan != 0 && vspan != 0)
          { 
            rxmax = xmax; 
            rymin = ymin;
            for(k = 1; k < hspan; k++)
              rxmax += iCellsGetWidth(ih, j+k);
            for(k = 1; k < vspan; k++)
              rymin -= iCellsGetHeight(ih, i+k);

            /* A cell was found */
            if (x >= xmin && x <= rxmax && y >= rymin && y <= ymax)
            { 
              *lin = i; 
              *col = j; 
              return 1; 
            }
          }
          xmin = xmax;
          xmax = xmin + (j == colto ? 0 : iCellsGetWidth(ih, j+1));
        }
      }
    }
    ymax = ymin;
    ymin = ymax - (i == linto ? 0 : iCellsGetHeight(ih, i+1));
  }

  /* No cell selected... */
  *lin = ICELLS_OUT;
  *col = ICELLS_OUT;
  return 0;
}

/* Function used to calculate the cell coordinates */
static int iCellsGetCoord(Ihandle* ih, int x, int y, int* lin, int* col)
{
  int pck = 0;
  int sline  = ih->data->non_scrollable_lins;
  int scol   = ih->data->non_scrollable_cols;
  int nlines = iCellsGetNLines(ih);
  int ncols  = iCellsGetNCols(ih);

  /* Trying to pick a cell (raster coordinates) at the four 
   * parts of the cells (reverse order of the repainting):
   * non-scrollable margin (line and column), non-scrollable 
   * columns, non-scrollable lines, and common cells. */
  pck = iCellsGetRangedCoord(ih, x, y, lin, col, 1, sline, 1, scol);
  if (pck)
    return 1;

  pck = iCellsGetRangedCoord(ih, x, y, lin, col, 1, sline, scol+1, ncols);
  if (pck)
    return 1;

  pck = iCellsGetRangedCoord(ih, x, y, lin, col, sline+1, nlines, 1, scol);
  if (pck)
    return 1;

  pck = iCellsGetRangedCoord(ih, x, y, lin, col, 1, nlines, 1, ncols);
  return pck;
}

static int iCellsScroll_CB(Ihandle* ih)
{
  IFnii cb;
  int ret = IUP_DEFAULT;

  /* Checking the existence of a scroll bar callback. If the application
   * has set one, it will be called now. If the application returns
   * IUP_DEFAULT, the cells will be repainted. */
  cb = (IFnii)IupGetCallback(ih, "SCROLLING_CB");
  if (cb)
  {
    int fline = iCellsGetFirstLine(ih);
    int fcol  = iCellsGetFirstCol(ih);
    ret = cb(ih, fline, fcol);
  }
  
  if (ret == IUP_DEFAULT)
    iCellsRepaint(ih);

  return IUP_DEFAULT;
}

static int iCellsResize_CB(Ihandle* ih, int w, int h)
{
  /* recalculate scrollbars limits */
  iCellsAdjustScroll(ih, w, h);  

  /* This could have changed the scrollbar visibility, 
     so the canvas client size can change, so updated it twice. */
  IupGetIntInt(ih, "DRAWSIZE", &w, &h);
  IupSetInt(ih, "DX", w);
  IupSetInt(ih, "DY", h);

  /* update canvas size */
  cdCanvasActivate(ih->data->cd_canvas);
  cdCanvasGetSize(ih->data->cd_canvas, &ih->data->w, &ih->data->h, NULL, NULL);

  /* update render */
  iCellsRenderCells(ih);

  return IUP_DEFAULT;
}

static int iCellsButton_CB(Ihandle* ih, int b, int m, int x, int y, char* r)
{
  int i, j;
  IFniiiiiis cb;

  y = cdIupInvertYAxis(y, ih->data->h);

  /* Treating the button event. The application will receive
   * a button press callback. */
  cb = (IFniiiiiis)IupGetCallback(ih, "MOUSECLICK_CB");
  if (!cb)
    return IUP_DEFAULT;

  iCellsGetCoord(ih, x, y, &i, &j);
  return cb(ih, b, m, i, j, x, y, r);
}

static int iCellsMotion_CB(Ihandle* ih, int x, int y, char* r)
{
  int i, j;
  IFniiiis cb;

  y = cdIupInvertYAxis(y, ih->data->h);

  /* Checking the existence of a motion bar callback. If the application
  * has set one, it will be called now. However, before calling the
  * callback, we need to find out which cell is under the mouse
  * position. */
  cb = (IFniiiis)IupGetCallback(ih, "MOUSEMOTION_CB");
  if (!cb)
    return IUP_DEFAULT;

  iCellsGetCoord(ih, x, y, &i, &j);
  return cb(ih, i, j, x, y, r);
}

static char* iCellsGetImageCanvasAttrib(Ihandle* ih)
{
  return (char*)ih->data->cd_canvas;
}

static char* iCellsGetCanvasAttrib(Ihandle* ih)
{
  cdCanvas* cd_canvas_front = (cdCanvas*)IupGetAttribute(ih, "_CD_CANVAS");  /* front buffer canvas */
  return (char*)cd_canvas_front;
}

static char* iCellsGetFirstLineAttrib(Ihandle* ih)
{
  return iupStrReturnInt(iCellsGetFirstLine(ih));
}

static char* iCellsGetFirstColAttrib(Ihandle* ih)
{
  return iupStrReturnInt(iCellsGetFirstCol(ih));
}

static char* iCellsGetLimitsAttrib(Ihandle* ih, int i, int j)
{
  int xmin, xmax, ymin, ymax;
  iCellsGetLimits(ih, i, j, &xmin, &xmax, &ymin, &ymax);
  return iupStrReturnStrf("%d:%d:%d:%d", xmin, xmax, ymin, ymax);
}

static int iCellsSetBufferizeAttrib(Ihandle* ih, const char* value)
{
  if (iupStrBoolean(value))
    ih->data->bufferize = 1;
  else
  { 
    ih->data->bufferize = 0;
    iCellsAdjustScroll(ih, ih->data->w, ih->data->h);
    iCellsRepaint(ih);
  }

  return 0;
}

static char* iCellsGetBufferizeAttrib(Ihandle* ih)
{
  return iupStrReturnBoolean (ih->data->bufferize); 
}

static int iCellsSetRepaintAttrib(Ihandle* ih, const char* value)
{
  (void)value;  /* not used */
  ih->data->bufferize = 0;
  iCellsAdjustScroll(ih, ih->data->w, ih->data->h);
  iCellsRepaint(ih);
  return 0;  /* do not store value in hash table */
}

static int iCellsSetBgColorAttrib(Ihandle* ih, const char* value)
{
  if (!value)
    value = iupControlBaseGetParentBgColor(ih);

  ih->data->bgcolor = cdIupConvertColor(value);
  
  iCellsRepaint(ih); 
  return 1;
}

static int iCellsSetOriginAttrib(Ihandle* ih, const char* value)
{
  int lin = -9;
  int col = -9;
  iupStrToIntInt(value, &lin, &col, ':');
  iCellsAdjustOrigin(ih, lin, col);
  iCellsRepaint(ih);
  return 1;
}

static int iCellsSetNonScrollableColsAttrib(Ihandle* ih, const char* value)
{
  if (iupStrToInt(value, &ih->data->non_scrollable_cols))
    iCellsRepaint(ih);
  return 0;
}

static char* iCellsGetNonScrollableColsAttrib(Ihandle* ih)
{
  return iupStrReturnInt(ih->data->non_scrollable_cols);
}

static int iCellsSetNonScrollableLinesAttrib(Ihandle* ih, const char* value)
{
  if (iupStrToInt(value, &ih->data->non_scrollable_lins))
    iCellsRepaint(ih);
  return 0;
}

static char* iCellsGetNonScrollableLinesAttrib(Ihandle* ih)
{
  return iupStrReturnInt(ih->data->non_scrollable_lins);
}

static int iCellsSetBoxedAttrib(Ihandle* ih, const char* value)
{
  ih->data->boxed = iupStrBoolean(value);
  iCellsRepaint(ih);
  return 0;
}

static char* iCellsGetBoxedAttrib(Ihandle* ih)
{
  return iupStrReturnBoolean(ih->data->boxed);
}

static int iCellsSetClippedAttrib(Ihandle* ih, const char* value)
{
  ih->data->clipped = iupStrBoolean(value);
  iCellsRepaint(ih);
  return 0;
}

static char* iCellsGetClippedAttrib(Ihandle* ih)
{
  return iupStrReturnBoolean(ih->data->clipped);
}

static int iCellsSetFullVisibleAttrib(Ihandle* ih, const char* value)
{
  int i, j;
  if (iupStrToIntInt(value, &i, &j, ':') != 2)
    return 0;

  iCellsSetFullVisible(ih, i, j);
  iCellsRepaint(ih);
  return 0;  /* do not store value in hash table */
}


/****************************************************************************/


static int iCellsMapMethod(Ihandle* ih)
{
  ih->data->cd_canvas = cdCreateCanvas(CD_IUPDBUFFER, ih);
  if (!ih->data->cd_canvas)
    return IUP_ERROR;

  return IUP_NOERROR;
}

static void iCellsUnMapMethod(Ihandle* ih)
{
  if (ih->data->cd_canvas)
  {
    cdKillCanvas(ih->data->cd_canvas);
    ih->data->cd_canvas = NULL;
  }
}

static int iCellsCreateMethod(Ihandle* ih, void **params)
{
  (void)params;

  /* free the data allocated by IupCanvas */
  free(ih->data);
  ih->data = iupALLOCCTRLDATA();

  /* change the IupCanvas default values */
  iupAttribSet(ih, "SCROLLBAR", "YES");
  iupAttribSet(ih, "BORDER", "NO");

  /* default values */
  ih->data->boxed = 1;
  ih->data->clipped = 1;

  /* IupCanvas callbacks */
  IupSetCallback(ih, "RESIZE_CB", (Icallback)iCellsResize_CB);
  IupSetCallback(ih, "ACTION",    (Icallback)iCellsRedraw_CB);
  IupSetCallback(ih, "BUTTON_CB", (Icallback)iCellsButton_CB);
  IupSetCallback(ih, "MOTION_CB", (Icallback)iCellsMotion_CB);
  IupSetCallback(ih, "SCROLL_CB", (Icallback)iCellsScroll_CB);

  return IUP_NOERROR;
}

Iclass* iupCellsNewClass(void)
{
  Iclass* ic = iupClassNew(iupRegisterFindClass("canvas"));

  ic->name = "cells";
  ic->format = NULL; /* no parameters */
  ic->nativetype = IUP_TYPECANVAS;
  ic->childtype = IUP_CHILDNONE;
  ic->is_interactive = 1;
  ic->has_attrib_id = 2;  /* has attributes with two IDs that must be parsed */

  /* Class functions */
  ic->New = iupCellsNewClass;
  ic->Create  = iCellsCreateMethod;
  ic->Map     = iCellsMapMethod;
  ic->UnMap   = iCellsUnMapMethod;

  /* Do not need to set base attributes because they are inherited from IupCanvas */

  /* IupCells Callbacks */
  iupClassRegisterCallback(ic, "SCROLLING_CB", "ii");
  iupClassRegisterCallback(ic, "MOUSEMOTION_CB", "iiiis");
  iupClassRegisterCallback(ic, "MOUSECLICK_CB", "iiiiiis");
  iupClassRegisterCallback(ic, "DRAW_CB", "iiiiiiC");
  iupClassRegisterCallback(ic, "VSPAN_CB", "ii");
  iupClassRegisterCallback(ic, "HSPAN_CB", "ii");
  iupClassRegisterCallback(ic, "NCOLS_CB", "");
  iupClassRegisterCallback(ic, "NLINES_CB", "");
  iupClassRegisterCallback(ic, "HEIGHT_CB", "i");
  iupClassRegisterCallback(ic, "WIDTH_CB", "i");

  /* IupCells only */
  iupClassRegisterAttribute(ic, "BOXED", iCellsGetBoxedAttrib, iCellsSetBoxedAttrib, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "CLIPPED", iCellsGetClippedAttrib, iCellsSetClippedAttrib, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "NON_SCROLLABLE_LINES", iCellsGetNonScrollableLinesAttrib, iCellsSetNonScrollableLinesAttrib, IUPAF_SAMEASSYSTEM, "0", IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "NON_SCROLLABLE_COLS", iCellsGetNonScrollableColsAttrib, iCellsSetNonScrollableColsAttrib, IUPAF_SAMEASSYSTEM, "0", IUPAF_NOT_MAPPED);
  iupClassRegisterAttribute(ic, "NO_COLOR", iupControlBaseGetBgColorAttrib, iCellsSetBgColorAttrib, NULL, NULL, IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "BUFFERIZE", iCellsGetBufferizeAttrib, iCellsSetBufferizeAttrib, NULL, NULL, IUPAF_DEFAULT);
  iupClassRegisterAttribute(ic, "ORIGIN", NULL, iCellsSetOriginAttrib, NULL, NULL, IUPAF_DEFAULT);

  iupClassRegisterAttribute(ic, "FULL_VISIBLE", NULL, iCellsSetFullVisibleAttrib, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "REPAINT", NULL, iCellsSetRepaintAttrib, NULL, NULL, IUPAF_WRITEONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttributeId2(ic, "LIMITS", iCellsGetLimitsAttrib, NULL, IUPAF_READONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FIRST_COL", iCellsGetFirstColAttrib, NULL, NULL, NULL, IUPAF_READONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FIRST_LINE", iCellsGetFirstLineAttrib, NULL, NULL, NULL, IUPAF_READONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMAGE_CANVAS", iCellsGetImageCanvasAttrib, NULL, NULL, NULL, IUPAF_NO_STRING|IUPAF_READONLY|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CANVAS", iCellsGetCanvasAttrib, NULL, NULL, NULL, IUPAF_NO_STRING|IUPAF_READONLY|IUPAF_NO_INHERIT);

  /* Overwrite IupCanvas Attributes */
  iupClassRegisterAttribute(ic, "BGCOLOR", iupControlBaseGetBgColorAttrib, iCellsSetBgColorAttrib, NULL, "255 255 255", IUPAF_NO_INHERIT);    /* overwrite canvas implementation, set a system default to force a new default */

  return ic;
}

Ihandle* IupCells(void)
{
  return IupCreate("cells");
}
