/*
 Copyright (c) 2004 Markus Niemistö

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or
 sell copies of the Software, and to permit persons to whom
 the Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall
 be included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 DEALINGS IN THE SOFTWARE.
*/

#include <config.h>

#include <FL/Fl.H>
#include <FL/fl_draw.H>

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include "table.h"

//#define DAMAGE_HEADER  FL_DAMAGE_USER1
//#define DAMAGE_ROWS  FL_DAMAGE_USER2

#define DAMAGE_HEADER  FL_DAMAGE_ALL
#define DAMAGE_ROWS  FL_DAMAGE_ALL

using namespace std;


/*
 * ======================================
 *  void Table.drawHeader(int x, int y);
 * ======================================
 *
 * Draws header buttons starting at (x, y).
 */
void Table::drawHeader(int x, int y) {
  int w;
  struct ColumnInfo col;

  fl_font(FL_HELVETICA, headerHeight - 4);

  /*
   * Draw all header cells that aren't clipped.
   */
  for (int i = leftCol; i <= rightCol; i++) {
    col = header[i];

    if (col.hidden)
      continue;

    // Draw box
    if (pushed != i)
      fl_draw_box(FL_THIN_UP_BOX, x, y, w = col.width, headerHeight, FL_GRAY);
    else
      fl_draw_box(FL_THIN_DOWN_BOX, x, y, w = col.width, headerHeight, FL_GRAY);

    fl_color(FL_FOREGROUND_COLOR);

    // Draw labels
    if (col.title != NULL)
      fl_draw(col.title, x + 2, y - 1, w - 2, headerHeight, col.hdr_align);

    x += w;

    // Draw "the sort arrow", if any.
    if (sortColumn == i) {
      int mod = headerHeight - 10;

      if (ascent)
        fl_polygon(x - mod - 6, y + 5, x - mod / 2 - 6,
                   y + mod + 5, x - 6, y + 5);
      else
        fl_polygon(x - mod - 6, y + mod + 5,
                   x - mod / 2 - 6, y + 5, x - 6, y + mod + 5);
    }
  }
}


/*
 * =============================================================
 *  void Table.drawRow(int row, char *rowData[], int x, int y);
 * =============================================================
 *
 * Draws all items in the row. Starts drawing from (x, y).
 */
void Table::drawRow(int row, char *rowData[], int x, int y) {
  int w;
  ColumnInfo col;

  fl_font(FL_HELVETICA, rowHeight - 4);

  // Draw background box.
  if (row != selected) {
    Fl_Color bg;
    if (!withGrid && row % 2 == 0) // different bg for consecutive rows
      bg = color();
    else {
      bg = fl_color_average(color(), FL_BLACK, .9);
      if (fl_contrast(bg, FL_BLACK) == FL_WHITE) // widget has very dark text bg
        bg = fl_color_average(color(), FL_WHITE, .9);
    }

    fl_rectf(iX, y, tableWidth - hScroll->value(), rowHeight, bg);

    fl_color(FL_FOREGROUND_COLOR);
  }
  else {
    if (Fl::focus() == this) {
      fl_rectf(iX, y, tableWidth - hScroll->value(), rowHeight, selection_color());
      fl_color(FL_FOREGROUND_COLOR);

      // Draw focus
      fl_line_style(FL_DOT);
      fl_rect(iX, y, tableWidth - hScroll->value(), rowHeight);
      fl_line_style(FL_SOLID);
    }
    else
      fl_rectf(iX, y, tableWidth - hScroll->value(), rowHeight, selection_color());

    fl_color(fl_contrast(FL_FOREGROUND_COLOR, selection_color()));
  }


  // Get color from highlighter.
  Fl_Color color;
  if ((highlighter != NULL) && (highlighter(row, rowData, &color)))
    fl_color(color);

  const char *str;

  // Draw the data.
  for (int i = leftCol; i <= rightCol; i++) {
    w = (col = header[i]).width;
    if (col.hidden)
      continue;

    if (withGrid == true) {
      fl_color(FL_FOREGROUND_COLOR);
      fl_line_style(FL_SOLID);
      fl_rect(x,y,w,rowHeight);

      fl_color(FL_FOREGROUND_COLOR);
    }

    if ((str = rowData[i]) != NULL)
      fl_draw(str, x + 1, y - 1, w - 2, rowHeight + 1, col.align);

    x += w;
  }
}


/*
 * =======================================================
 *  Table.Table(int x, int y, int w, int h, char *label);
 * =======================================================
 *
 * This is standard FLTK constructor. See FLTK documentation for
 * more information.
 */
Table::Table(int x, int y, int w, int h, char *label) : 
       Fl_Group(x, y, w, h, label) {

  // Setup variables.
  align((Fl_Align)(FL_ALIGN_LEFT | FL_ALIGN_CLIP));

  box(FL_THIN_DOWN_FRAME);
  color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);

  headerHeight = 18;
  rowHeight = 17;
  scrollbarSize = 16;

  // Create scrollbars.
  vScroll = new Fl_Scrollbar(x + w - scrollbarSize, y, scrollbarSize, h + scrollbarSize);
  vScroll->type(FL_VERTICAL);
  vScroll->linesize(3 * rowHeight);
  vScroll->callback(scrollCallback, (void*)this);
  vScroll->hide();

  hScroll = new Fl_Scrollbar(x, y + h - scrollbarSize, w, scrollbarSize);
  hScroll->type(FL_HORIZONTAL);
  hScroll->callback(scrollCallback, (void*)this);
  hScroll->hide();

  Fl_Group::end();

  // Setup the rest of the variables to reasonable defaults.
  nCols = nRows = 0;
  cPos = 0;

  dragX = 0;
  resizing = -1;
  pushed = -1;

  curRow = NULL;
  highlighter = NULL;

  sortColumn = -1;
  selected = -1;
  canSort = true;
  canResize = true;
  ascent = false;
  noMoreColumns = false;
  dimensionsChanged = false;
  toBeSorted = false;
  headerEnabled = true;
  withGrid = false;

  popupMenu = NULL;
  menuAlloc = false;

  Vscroll = var;
  Hscroll = var;
}


/*
 * =================
 *  Table.~Table();
 * =================
 *
 * Destructor.
 */
Table::~Table() {
  delete vScroll;
  delete hScroll;

  menuClear();
  clear();
}


/*
 * ====================================
 *  bool Table.headerOn();
 *  void Table.headerOn(bool enabled);
 * ====================================
 *
 * These methods get or set the value of variable controlling
 * whether header buttons are displayed.
 */
bool Table::headerOn() const {
  return headerEnabled;
}

void Table::headerOn(bool enabled) {
  headerEnabled = enabled;
  dimensionsChanged = true;
  redraw();
}


/*
 * ====================================
 *  bool Table.gridEnabled();
 *  void Table.gridEnabled(bool enabled);
 * ====================================
 *
 * These methods get or set the value of variable controlling
 * whether the table grid is displayed.
 */
bool Table::gridEnabled() const {
  return withGrid;
}

void Table::gridEnabled(bool enabled) {
  withGrid = enabled;
  dimensionsChanged = true;
  redraw();
}


/*
 * =====================================
 *  bool Table.allowResize();
 *  void Table.allowResize(bool allow);
 * =====================================
 *
 * These methods get or set the value of variable controlling
 * whether user may resize columns by dragging the column border.
 */
bool Table::allowResize() const {
  return canResize;
}

void Table::allowResize(bool allow) {
  canResize = allow;
}


/*
 * ===================================
 *  bool Table.allowSort();
 *  void Table.allowSort(bool allow);
 * ===================================
 *
 * These methods get or set the value of variable controlling
 * whether user can determine how data on table is sorted by
 * clicking on the header buttons.
 */
bool Table::allowSort() const {
  return canSort;
}

void Table::allowSort(bool allow) {
  canSort = allow;
}


/*
 * ==================================
 *  int Table.headerSize();
 *  void Table.headerSize(int size);
 * ==================================
 *
 * These methods get or set the value of variable controlling
 * the height of header buttons.
 */
int Table::headerSize() const {
  return headerHeight;
}

void Table::headerSize(int height) {
  headerHeight = height + 4;
  dimensionsChanged = true;
  redraw();
}


/*
 * ===============================
 *  int Table.rowSize();
 *  void Table.rowSize(int size);
 * ===============================
 *
 * These methods get or set the value of variable controlling
 * the height of rows.
 */
int Table::rowSize() const {
  return rowHeight;
}

void Table::rowSize(int height) {
  height += 4;
  rowHeight = height;
  vScroll->linesize(3 * height);
  dimensionsChanged = true;
  redraw();
}


/*
 * ===================================
 *  int Table.scrollbSize();
 *  void Table.scrollbSize(int size);
 * ===================================
 *
 * These methods get or set the value of variable controlling
 * the size (width) of the scrollbars.
 */
int Table::scrollbSize() const {
  return scrollbarSize;
}

void Table::scrollbSize(int size) {
  scrollbarSize = size;
  dimensionsChanged = true;
  redraw();
}


/*
 * =====================================================
 *  Fl_Align Table.columnAlign(int column);
 *  void Table.columnAlign(int column, Fl_Align align);
 * =====================================================
 *
 * These methods get or set the value of variable controlling
 * the alignment of the specified column.
 */
Fl_Align Table::columnAlign(int column) const {
  if ((column < 0) && (column >= nCols))
    return (Fl_Align)(FL_ALIGN_LEFT | FL_ALIGN_CLIP);
    /* NOT REACHED */

  return header[column].align;
}

void Table::columnAlign(int column, Fl_Align align) {
  if ((column < 0) || (column >= nCols))
    return;
    /* NOT REACHED */

  header[column].align = (Fl_Align)(align | FL_ALIGN_CLIP);
  redraw();
}

Fl_Align Table::headerAlign(int column) const {
  if ((column < 0) && (column >= nCols))
    return (Fl_Align)(FL_ALIGN_LEFT | FL_ALIGN_CLIP);
    /* NOT REACHED */

  return header[column].hdr_align;
}

void Table::headerAlign(int column, Fl_Align align) {
  if ((column < 0) && (column >= nCols))
    return;
    /* NOT REACHED */

  header[column].hdr_align = (Fl_Align)(align | FL_ALIGN_CLIP);
  redraw();
}


/*
 * =====================================================
 *  int Table.columnWidth(int column);
 *  void Table.columnWidth(int column, int width);
 * =====================================================
 *
 * These methods get or set the value of variable controlling
 * the width of the specified column.
 */
int Table::columnWidth(int column) const {
  if ((column < 0) && (column >= nCols))
    return 0;
    /* NOT REACHED */

  return header[column].width;
}

void Table::columnWidth(int column, int width) {
  if ((column < 0) && (column >= nCols))
    return;
    /* NOT REACHED */

  header[column].width = width;
  dimensionsChanged = true;
  redraw();
}


/*
 * ========================================================
 *  const char *Table.columnTitle(int column);
 *  void Table.columnTitle(int column, const char *title);
 * ========================================================
 *
 * These methods get or set the value of variable controlling
 * the width of the specified column.
 */
const char *Table::columnTitle(int column) {
  if ((column < 0) && (column >= nCols))
    return NULL;
    /* NOT REACHED */

  return header[column].title;
}

void Table::columnTitle(int column, const char *title) {
  if ((column < 0) && (column >= nCols))
    return;
    /* NOT REACHED */

  free((void*)header[column].title);
  header[column].title = strdup(title);

  damage(DAMAGE_HEADER);
}


/*
 * ===================================================
 *  bool Table.columnHidden(int column);
 *  void Table.columnHidden(int column, bool hidden);
 * ===================================================
 *
 * These methods get or set the value of variable controlling
 * whether column is visible or not.
 */
bool Table::columnHidden(int column) {
  if ((column < 0) && (column >= nCols))
    return false;
    /* NOT REACHED */

  return header[column].hidden;
}

void Table::columnHidden(int column, bool hidden) {
  if ((column < 0) && (column >= nCols))
    return;
    /* NOT REACHED */

  header[column].hidden = hidden;
  dimensionsChanged = true;
  damage(DAMAGE_HEADER | DAMAGE_ROWS);
}


/*
 * ===================
 *  int Table.rows();
 * ===================
 *
 * Returns the number of rows in table.
 */
int Table::rows() {
  return nRows;
}


/*
 * ======================
 *  int Table.columns();
 * ======================
 *
 * Returns the number of columns in table.
 */
int Table::columns() {
  return nCols;
}


/*
 * ==================================
 *  void Table.value(int selection);
 * ==================================
 *
 * Sets the currently selected row.
 */
void Table::value(int selection) {
  if ((selection >= 0) && (selection < nRows))
    selected = selection;
  damage(DAMAGE_ROWS);
}


/*
 * ====================
 *  int Table.value();
 * ====================
 *
 * Returns the number of the currently selected row.
 */
int Table::value() {
  return selected;
}


/*
 * ====================================================================
 *  void Table.addColumn(const char *label, int width, Fl_Align align,
 *      int (*comparator)(const char*, const char*));
 * ====================================================================
 *
 * Adds column with label as title, width, align and comparator as
 * sort function.
 */
void Table::addColumn(const char *label, int width, Fl_Align align,
    int (*comparator)(const char*, const char*)) {
  if (!noMoreColumns) {
    struct ColumnInfo col;

    dimensionsChanged = true;

    col.title = strdup(label);
    col.width = width;
    col.hidden = false;
    col.align = (Fl_Align)(align | FL_ALIGN_CLIP);
                col.hdr_align = (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_CLIP);
    col.comparator = comparator;
    col.callback = NULL;
    header.push_back(col);
    nCols++;
  }
}


void Table::colcomparator(int col,
      int (*comparator)(const char*, const char*))
{
  header[col].comparator = comparator;
}

void Table::colcallback (int col, void (*callback)())
{
   header[col].callback = callback;
}

/*
 * ================================================
 *  void Table.addHiddenColumn(const char *label);
 * ================================================
 *
 * Adds a nonvisible column with label as title.
 */
void Table::addHiddenColumn(const char *label) {
  if (!noMoreColumns) {
    struct ColumnInfo col;

    col.title = strdup(label);
    col.width = 0;
    col.hidden = true;
    col.align = FL_ALIGN_LEFT;
    col.comparator = NULL;
    header.push_back(col);
    nCols++;
  }
}


/*
 * =================================
 *  void Table.addCell(char *data);
 * =================================
 *
 * Adds a cell with data to the table.
 */
void Table::addCell(char *data) {
  if (!noMoreColumns)
    noMoreColumns = true;

  if ((cPos >= nCols) || (curRow == NULL)) {
    this->data.push_back(curRow = new char*[nCols]);
    dimensionsChanged = true;
    nRows++;
    cPos = 0;
  }

  if (data != NULL)
    curRow[cPos] = strdup(data);
  else
    curRow[cPos] = strdup("");

  if (cPos == sortColumn)
    toBeSorted = true;
  cPos++;
}


/*
 * ===================================
 *  void Table.addRow(int cols, ...);
 * ===================================
 *
 * Adds cols number of cells to table.
 */
void Table::addRow(int cols, ...) {
  char *temp;

  if (!noMoreColumns)
    noMoreColumns = true;

  if ((cPos != 0) || (curRow == NULL)) {
    this->data.push_back(curRow = new char*[nCols]);
    dimensionsChanged = true;
    nRows++;
    cPos = 0;
  }

  if (cols > nCols)
    cols = nCols;

  va_list ap;
  va_start(ap, cols);
  for (int c = 0; c < cols; c++, cPos++) {
    if (cPos >= nCols) {
      this->data.push_back(curRow = new char*[nCols]);
      dimensionsChanged = true;
      nRows++;
      cPos = 0;
    }

    if ((temp = va_arg(ap, char *)) != NULL)
      curRow[cPos] = strdup(temp);
    else
      curRow[cPos] = strdup("");
  }
  va_end(ap);

  toBeSorted = true;
  dimensionsChanged = true;
}


/*
 * ================================
 *  void Table.removeRow(int row);
 * ================================
 *
 * Removes row referenced by row.
 */
void Table::removeRow(int row) {
  if ((row == -1) && (selected >= 0))
    row = selected;
  if ((row >= 0) && (row < nRows)) {
    char **rowData = data[row];
    if (rowData == curRow)
      curRow = NULL;
    for (int i = 0; i < nCols; i++)
      free(rowData[i]);
    data.erase(row + data.begin());
    nRows--;
    dimensionsChanged = true;
    toBeSorted = true;
    selected = -1;
  }
  redraw ();
}


/*
 * =======================================
 *  void Table.clear(bool removeColumns);
 * =======================================
 *
 * Frees all data in table. If removeColumns is true, frees also header
 * structures.
 */
void Table::clear(bool removeColumns) {
  nRows = 0;
  curRow = NULL;
  cPos = 0;

  // Delete row data.
  vector<char**>::iterator end = data.end();
  char **row;
  for (vector<char**>::iterator i = data.begin(); i < end; ++i) {
    row = *i;
    for (int i = 0; i < nCols; i++)
      free(row[i]);
    delete [] row;
  }
  data.clear();

  if (removeColumns) {
    // Delete header data.
    vector<struct ColumnInfo>::iterator end = header.end();
    for (vector<struct ColumnInfo>::iterator i = header.begin();
        i < end; ++i)
      free((void*)(*i).title);

    header.clear();
    nCols = 0;
  }

  selected = -1;
  dimensionsChanged = true;
  redraw ();
}


/*
 * ============================================
 *  char *Table.valueAt(int row, int column);
 *  int Table.intValueAt(int row, int column);
 * ============================================
 *
 * Returns value in cell referenced by row and column.
 */
char *Table::valueAt(int row, int column) {
  if ((row >= 0) && (row < nRows) && (column >= 0) && (column < nCols))
    return data[row][column];
  else if ((row == -1) && (selected >= 0) && (column >= 0) && (column < nCols))
    return data[selected][column];
  else
    return NULL;
}


int Table::intValueAt(int row, int column) {
  if ((row == -1) && (selected >= 0))
    row = selected;

  if ((row >= 0) && (row < nRows) && (column >= 0) && (column < nCols))
    return strtol(data[row][column], NULL, 10);
  else
    return 0;
}


/*
 * ======================================================
 *  void Table.valueAt(int row, int column, char *data);
 *  void Table.valueAt(int row, int column, int data);
 * ======================================================
 *
 * Sets alue in cell referenced by row and column.
 */
void Table::valueAt(int row, int column, char *data) {
  if ((row == -1) && (selected >= 0))
    row = selected;

  if ((row >= 0) && (row < nRows) && (column >= 0) && (column < nCols)) {
    if (column == sortColumn)
      toBeSorted = true;
    if (this->data[row][column] != NULL)
      free(this->data[row][column]);
    this->data[row][column] = strdup(data);
  }
}


void Table::valueAt(int row, int column, int data) {
  if ((row == -1) && (selected >= 0))
    row = selected;

  if ((row >= 0) && (row < nRows) && (column >= 0) && (column < nCols)) {
    if (column == sortColumn)
      toBeSorted = true;
    if (this->data[row][column] != NULL)
      free(this->data[row][column]);
    string temp = "";
    temp += data;
    strcpy(this->data[row][column] = (char*)malloc(temp.length()),
        temp.c_str());
  }
}


/*
 * =====================================
 *  const char **Table.getRow(int row);
 * =====================================
 *
 * Returns pointer to the data of the row number row.
 */
const char **Table::getRow(int row) {
  if ((row == -1) && (selected >= 0))
    row = selected;

  if ((row >= 0) && (row < nRows))
    return (const char**)data[row];
  else
    return NULL;
}


/*
 * ==============
 *  Menu methods
 * ==============
 *
 * These work in the same way as in class Fl_Menu_ (methods menu,
 * copy and clear). These are used for handling the popup menu.
 */
const Fl_Menu_Item *Table::menu() {
  return popupMenu;
}

void Table::menu(const Fl_Menu_Item *m) {
  menuClear();
  popupMenu = m;
}

void Table::menuCopy(const Fl_Menu_Item *m) {
  int n = m->size();

  Fl_Menu_Item* newMenu = new Fl_Menu_Item[n];
  memcpy(newMenu, m, n * sizeof(Fl_Menu_Item));
  menu(newMenu);
  menuAlloc = true;
}

void Table::menuClear() {
  if (menuAlloc)
    delete[] popupMenu;
  popupMenu = NULL;
}


/*
 * ================================================================
 *  Table.where(int x, int y, int &row, int &column, int &resize);
 * ================================================================
 *
 * Finds corresponding row and column for x and y coordinates. This function
 * uses Fl::event_inside() method.
 *
 * row = -1 means header and row = -2 means that coordinates don't
 * correspond any cell.
 */
void Table::where(int x, int y, int &row, int &column, int &resize) {
  int temp, temp2;

  // Inside the header
  if ((nCols > 0) && headerEnabled &&
       Fl::event_inside(oX, oY, iW, headerHeight)) {
    row = -1;
    temp = leftColX + iX - hScroll->value();

    // Scan visible columns until found one that matches.
    for (column = leftCol; column <= rightCol; column++ ) {
      if (header[column].hidden)
        continue;

      temp2 = temp;

      // Near the left border of the column header
      if ((x >= temp) && (x <= temp + 3)) {
        resize = 1;
        return;
        /* NOT REACHED */
      }

      // Near the right border of the column header
      else if ((x >= (temp += header[column].width) - 3) &&
          (x < temp)) {
        resize = 2;
        return;
        /* NOT REACHED */
      }

      // Somewhere else
      else if ((x >= temp2) && (x < temp)) {
        resize = 0;
        return;
        /* NOT REACHED */
      }
    }
  } // Header


  /*
   * Now the harder one. X and Y lie somewhere in the table.
   * Find correct row and column.
   */
  else if ((nRows > 0) && Fl::event_inside(iX, iY, iW, iH)) {
    temp = topRowY;
    int yMod = iY - vScroll->value();
    int leftX = leftColX + iX - hScroll->value();

    // Scan rows
    for (row = topRow; row <= bottomRow; row++) {
      int temp2 = leftX;
      for (column = leftCol; column <= rightCol; column++) {
        if (header[column].hidden)
          continue;

        if (Fl::event_inside(temp2, temp + yMod,
            header[column].width, rowHeight))
          return;
          /* NOT REACHED */
        temp2 += header[column].width;
      }
      temp += rowHeight;
    }
  }
  row = column = -2;
}


void Table::FirstRow()
{
  if (nRows == 0 || selected == 0) return;
  scrollTo (selected = 0);
}

void Table::PrevPage ()
{
// Does it make sense to move up?
  if (selected > 0) {
// Number of rows on the 'page'
    int step = iH / rowHeight;
// Change selection
    if (selected >= step)
      selected -= step;
    else
      selected = 0;
    scrollTo(selected * rowHeight);
  }
}

void Table::PrevRow()
{
  int newpos, oldpos;
  if (nRows == 0) return;
  selected = (selected > 0) ? selected - 1 : 0;
  oldpos = vScroll->value();
  newpos = rowHeight * selected;
  if (newpos - oldpos > 0)
    scrollTo (oldpos);
  else
    scrollTo (newpos);
}

void Table::NextRow()
{
  int newpos, oldpos, lastrow;
  if (nRows == 0 || selected == (nRows - 1)) return;
  lastrow = nRows -1;
  selected = (selected < lastrow) ? selected + 1 : lastrow;
  oldpos = vScroll->value();
  newpos = rowHeight *(selected + 1) - iH;
  if (newpos - oldpos < 0)
    scrollTo (oldpos);
  else
    scrollTo (newpos);
}

void Table::NextPage ()
{
  if ((selected >= 0) && (selected < (nRows - 1))) {
    int step = iH / rowHeight;
    if ((selected += step) >= nRows)
      selected = nRows - 1;
    scrollTo(rowHeight * (selected + 1) - iH);
  }
}

void Table::LastRow()
{
  if (nRows == 0) return;
  selected = nRows - 1;
  scrollTo (rowHeight * (selected + 1) - iH);
}

void Table::GotoRow(int n)
{
  if (n >= 0 && (n < nRows)) {
    selected = n;
    scrollTo(rowHeight * (selected + 1) - iH);
  }
}

/*
 * ==============================
 *  int Table.handle(int event);
 * ==============================
 *
 * FLTK internal. Handles incoming events.
 */
int Table::handle(int event) {
  int ret = 0;
  static int row, prev_row;
  int column, resize;

  if (event != FL_KEYDOWN)
    ret = Fl_Group::handle(event);

  /*
   * MAIN SWITCH
   */
  switch (event) {

  /*
   * PUSH event
   */
  case FL_PUSH:
    // Which row/column are we over?
    where(Fl::event_x(), Fl::event_y(), row, column, resize);

    switch (row) {
    // Push on nothing... Not interested
    case -2:
      if (selected != -1) {
//        selected = -1;
        damage(DAMAGE_ROWS);
      }
      break;

    // Push on header.
    case -1:
      if ((canResize) && (Fl::event_button() == 1) &&
          (resize != 0)) {
        resizing = (resize == 1) ? column - 1 : column;
        dragX = Fl::event_x();
        ret = 1;
      }
      else  if ((canSort) && (Fl::event_button() == 1)) {
        pushed = column;
        damage(DAMAGE_HEADER);
        ret = 1;
      }
      break;

    // Push on cell.
          default:
      bool changed = selected != row;
      selected = row;

      // Create new selection
      int len = 0;
      char **tableRow = data[selected];
      char *buffer;

      for (int col = 0; col < nCols; col++)
        len += strlen(tableRow[col]) + 1;

      // Create a tab separated list from data.
      buffer = (char*)malloc(len);
      strcpy(buffer, tableRow[0]);
      for (int col = 1; col < nCols; col++) {
        strcat(buffer, "\t");
        strcat(buffer, tableRow[col]);
      }
      Fl::selection(*this, buffer, len);
      free(buffer);

      // Update view.
      damage(DAMAGE_ROWS);
      take_focus();

      // Show popup menu
      if ((Fl::event_button() == 3) && (popupMenu != NULL)) {
        const Fl_Menu_Item *m;
        m = popupMenu->popup(Fl::event_x(),
            Fl::event_y());
        if (m != NULL)
          m->do_callback(this, m->user_data());
	ret = 1;
	break;
      }


      // Callback
      if ((Fl::event_clicks() != 0) && !changed &&
          (when() & TABLE_WHEN_DCLICK)) {
            Fl::event_is_click(0);
        do_callback();
      }
      else if (changed && (when() & FL_WHEN_CHANGED))
        do_callback();
      else if (!changed && (when() & FL_WHEN_NOT_CHANGED))
	do_callback();
      ret = 1;
      break;
    } // switch(row)
    break;


  /*
   * DRAG event
   */
  case FL_DRAG:
    // Resizing...
    if (resizing > -1 ) {
      int offset = dragX - Fl::event_x();
      int newWidth = header[resizing].width - offset;

      // Width must be at least 1.
      if (newWidth < 1)
        newWidth = 1;

      // Test if column really is resized.
      if (header[resizing].width != newWidth) {
        header[resizing].width = newWidth;
        dragX = Fl::event_x();
        resized();
        redraw();
      }
      ret = 1;
    }
    else {
      prev_row = row;
      where(Fl::event_x(), Fl::event_y(), row, column, resize);
      if (row < 0 || pushed != -1) {
        ret = 1;
      	break;
      }
      if (prev_row != row) {
        selected = row;
        damage(DAMAGE_ROWS);
        take_focus();
        if (when() & FL_WHEN_CHANGED)
          do_callback();
      }
    }
    break;


  /*
   * RELEASE event
   */
  case FL_RELEASE:
    // Which row/column are we over?
    where(Fl::event_x(), Fl::event_y(), row, column, resize);

    // Restore cursor and end resizing.
    if (Fl::event_button() == 1) {
      fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
      if ((pushed == column) && canSort) {

if (this->header[pushed].callback != NULL)
  (this->header[pushed].callback)();

if (this->header[pushed].comparator != NULL) {
        if (sortColumn == pushed) {
          if (ascent)
            ascent = false;
          else
            sortColumn = -1;
        }
        else {
          ascent = true;
          sortColumn = pushed;
        }
        sort();
}
        redraw();
      }
      pushed = -1;
      resizing = -1;
      ret = 1;
    }

    // Callback.
    if ((row >= 0) && (when() & FL_WHEN_RELEASE))
        do_callback();
    break;


  /*
   * MOVE event
   */
  case FL_MOVE:
    // Which row/column are we over?
    where(Fl::event_x(), Fl::event_y(), row, column, resize);

    // If near header boundary.
    if ((row == -1) && canResize && resize)
      fl_cursor(FL_CURSOR_WE, FL_BLACK, FL_WHITE);
    else
      fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
    ret = 1;
    break;

  case FL_ENTER:
  case FL_LEAVE:
    if (event == FL_LEAVE)
      fl_cursor(FL_CURSOR_DEFAULT, FL_BLACK, FL_WHITE);
    ret = 1;
    break;

  case FL_FOCUS:
  case FL_UNFOCUS:
    if (Fl::visible_focus()) {
      damage(DAMAGE_ROWS);
      ret = 1;
    }
    break;

  /*
   * KEYDOWN event
   */
  case FL_KEYDOWN:
    switch(Fl::event_key())  {
    case FL_Enter:
      if ((selected > -1) && ((when() & TABLE_WHEN_DCLICK) ||
          (when() & FL_WHEN_ENTER_KEY)))
        do_callback();
      ret = 1;
      break;

    case FL_Home:
      FirstRow();
      ret = 1;
      break;

    case FL_Up: 
      PrevRow();
      ret = 1;
      break;

    case FL_Down:
      NextRow();
      ret = 1;
      break;

    case FL_End:
      LastRow();
      ret = 1;
      break;

    case FL_Page_Up:
      PrevPage ();
      ret = 1;
      break;

    case FL_Page_Down:
      NextPage ();
      ret = 1;
      break;

    }
    break;

  }
  return ret;
}


/*
 * ===============================
 *  void Table.scrollTo(int pos);
 * ===============================
 *
 * Scrolls table to given position.
 */
void Table::scrollTo(int pos) {

  if (vScroll->visible() || nRows > (iH / rowHeight)) {
    int max = rowHeight * nRows - iH;
//printf ("pos %d, max %d\n", pos, max); fflush (stdout);
    if (pos < 0 || max < 0) pos = 0;
    if (pos > max) pos = max;

    vScroll->Fl_Valuator::value(1.0*pos);
    vScroll->damage (FL_DAMAGE_ALL);
    vScroll->redraw ();
    scrolled();
  }
  damage(DAMAGE_ROWS);
  if (when() & FL_WHEN_CHANGED)
    do_callback();

}

int Table::scrollPos() const
{
  return (int)vScroll->value();
}

/*
 * ===========================================
 *  void Table.sort(int column, bool ascent);
 * ===========================================
 *
 * Sets sortColumn and ascent and sorts table. Does not redraw.
 */
void Table::sort(int column, bool ascent) {
  if ((column < -1) || (column >= nCols))
    return;

  sortColumn = column;
  this->ascent = ascent;
  sort();
}


void Table::aSort(int start, int end,
    int (*compare)(const char *, const char*)) {
  int i, j;
  const char *x;
  char **temp;

  x = data[(start + end) / 2][sortColumn];
  i = start;
  j = end;

  for (;;) {
    while ((i < end) && (compare(data[i][sortColumn], x) < 0))
      i++;
    while ((j > 0) && (compare(data[j][sortColumn], x) > 0))
      j--;

    while ((i < end) && (i != j) &&
       (compare(data[i][sortColumn], data[j][sortColumn]) == 0))
      i++;

    if (i == j)
      break;

    temp = data[i];
    data[i] = data[j];
    data[j] = temp;
  }

  if (start < --i)
    aSort(start, i, compare);

  if (end > ++j)
    aSort(j, end, compare);
}


void Table::dSort(int start, int end,
    int (*compare)(const char *, const char*)) {
  int i, j;
  const char *x;
  char **temp;

  x = data[(start + end) / 2][sortColumn];
  i = start;
  j = end;

  for (;;) {
    while ((i < end) && (compare(data[i][sortColumn], x) > 0))
      i++;
    while ((j > 0) && (compare(data[j][sortColumn], x) < 0))
      j--;

    while ((i < end) && (i != j) &&
       (compare(data[i][sortColumn], data[j][sortColumn]) == 0))
      i++;

    if (i == j)
      break;

    temp = data[i];
    data[i] = data[j];
    data[j] = temp;
  }

  if (start < --i)
    dSort(start, i, compare);

  if (end > ++j)
    dSort(j, end, compare);
}


/*
 * ====================
 *  void Table.sort();
 * ====================
 *
 * Sorts table according sortColumn and ascent. Does not redraw.
 */
void Table::sort() {
  if ((sortColumn == -1) || !canSort)
    return;
    /* NOT REACHED */

  toBeSorted = false;

  int (*compare)(const char *, const char*);

  // Get comparator function or set it to the default.
  if (this->header[sortColumn].comparator == NULL)
//    compare = strcasecmp;
          return;
  else
    compare = header[sortColumn].comparator;

  // Sort in descending order.
  if ((nRows > 1) && ascent)
    aSort(0, nRows - 1, compare);

  // Sort in ascending order.
  else if (nRows > 1)
    dSort(0, nRows - 1, compare);
}


/*
 * ====================================================
 *  void Table.getSort(int &sortColumn, bool &ascent);
 * ====================================================
 *
 * Set sortColumn and ascent according to current sort policy.
 */
void Table::getSort(int &sortColumn, bool &ascent) {
  sortColumn = this->sortColumn;
  ascent = this->ascent;
}


/*
 * =====================================================
 *  int compareInt(const char *val1, const char *val2);
 * =====================================================
 *
 * This function compares values as numbers instead of strings. Solves
 * problem with string sorting (eg. 1 - 10 - 11 - 12 - 2 - 3 ...).
 */
int compareInt(const char *val1, const char *val2) {
  return strtol(val1, NULL, 0) - strtol(val2, NULL, 0);
}


/*
 * ==============================================================================
 *  void Table.setHighlighter(bool (*highliter)(int, char **, Fl_Color &color));
 * ==============================================================================
 *
 * Sets highlighter function to highlighter. Highlighter is used to determine
 * text color in Table.drawRow().
 */
void Table::setHighlighter(bool (*highlighter)(int, char **, Fl_Color *)) {
  this->highlighter = highlighter;
}


/*
 * ====================
 *  void Table.draw();
 * ====================
 *
 * FLTK internal. Called when Table widget needs to be drawn.
 */
void Table::draw() {
  int damage;

  if (dimensionsChanged) {
    dimensionsChanged = false;
    resized();
  }

  if (toBeSorted)
    sort();

  damage = Fl_Widget::damage();

  // Draw children.
  if (damage & (FL_DAMAGE_ALL | FL_DAMAGE_CHILD)) {
    fl_push_clip(oX, oY, oW, oH);
    Fl_Group::draw();
    fl_pop_clip();
  }

  // Draw box.
  if (damage & FL_DAMAGE_ALL) {
    // Draw box.
    draw_box(box(), x(), y(), w(), h(), FL_GRAY);

    // Draw label.
    draw_label();
  }

  // Draw header.
  int xPos = leftColX + iX - hScroll->value();
  if (headerEnabled && (damage & (FL_DAMAGE_ALL | DAMAGE_HEADER)) &&
      (nCols > 0)) {
    fl_push_clip(iX, oY, iW, headerHeight);
    drawHeader(xPos, oY);
    fl_pop_clip();
  }

  // Draw all the cells.
  if ((damage & (FL_DAMAGE_ALL | DAMAGE_ROWS)) && (nRows > 0) &&
      (nCols > 0)) {
    fl_push_clip(iX, iY, iW, iH);

    int yMod = iY - vScroll->value();
    for (int row = topRow, rowY = topRowY; row <= bottomRow;
        row++, rowY += rowHeight)
      drawRow(row, data[row], xPos, rowY + yMod);
    fl_pop_clip();
  }

  fl_push_clip(oX, oY, oW, oH);

  if (tableWidth < iW)
    fl_rectf(iX + tableWidth, oY, iW - tableWidth, oH, FL_GRAY);

  // Table height smaller than window? Fill remainder with rectangle
  if (tableHeight < iH)
    fl_rectf(iX, iY + tableHeight, iW, iH - tableHeight, FL_GRAY);

  if (vScroll->visible()) {
                vScroll->damage (FL_DAMAGE_ALL);
    vScroll->redraw();
  }
  if (hScroll->visible()) {
    hScroll->damage (FL_DAMAGE_ALL);
    hScroll->redraw();
  }

  // Both scrollbars? Draw little box in lower right
  if (vScroll->visible() && hScroll->visible())
    fl_rectf(vScroll->x(), hScroll->y(), vScroll->w(), hScroll->h(), FL_GRAY);
  fl_pop_clip();
}


/*
 * ================================================
 *  void Table.resize(int x, int y, int w, int h);
 * ================================================
 *
 * FLTK internal. Called when Table widget is resized.
 */
void Table::resize(int x, int y, int w, int h) {
  Fl_Widget::resize(x, y, w, h);
  resized();
  damage(FL_DAMAGE_ALL);
}


/*
 * ==============================
 *  void Table.calcDimensions();
 * ==============================
 *
 * Calculates table dimensions.
 */
void Table::calcDimensions() {
  // Calculate width and height of the table (in pixels).
  tableWidth = 0;
  for (int i = 0; i < nCols; i++)
    if (!header[i].hidden)
      tableWidth +=header[i].width;

  tableHeight = nRows * rowHeight;
  Fl_Boxtype b;

  iX = oX = x() + Fl::box_dx(b = box());
  iY = oY = y() + Fl::box_dy(b);
  iW = oW = w() - Fl::box_dw(b);
  iH = oH = h() - Fl::box_dh(b);

  // Trim inner size if header enabled.
  if (headerEnabled) {
    iY += headerHeight;
    iH -= headerHeight;
  }

  // Hide scrollbars if window is large enough
  int hideV, hideH;

  hideV = (tableHeight <= iH),
  hideH = (tableWidth <= iW);

  if (!hideH & hideV)
    hideV = (tableHeight - iH - scrollbarSize) <= 0;
  if (!hideV & hideH)
    hideH = (tableWidth - iW + scrollbarSize) <= 0;

  if (Vscroll == always) {
    vScroll->show();
    iW -= scrollbarSize;
  } else if (Vscroll == never) {
    vScroll->hide();
    vScroll->Fl_Valuator::value(0);
  } else if (hideV) {
    vScroll->hide();
    vScroll->Fl_Valuator::value(0);
  }
  else {
    vScroll->show();
    iW -= scrollbarSize;
  }

  if (Hscroll == always) {
    hScroll->show();
    iH -= scrollbarSize;
  } else if (Hscroll == never) {
    hScroll->hide();
    hScroll->Fl_Valuator::value(0);
  } else if (hideH) {
    hScroll->hide();
    hScroll->Fl_Valuator::value(0);
  }
  else {
    hScroll->show();
    iH -= scrollbarSize;
  }
}


/*
 * ========================
 *  void Table.scrolled();
 * ========================
 *
 * Calculates visible are after scroll or adding data.
 */
void Table::scrolled()
{
  int y, row, voff = vScroll->value();

  // First visible row
  row = voff / rowHeight;
  topRow = (row >= nRows) ? (nRows - 1) : row;
  topRow = (topRow < 0) ? 0 : topRow;

  y = topRow * rowHeight;
  if ((topRow > 0) && (y > voff)) {
    topRow--;
    y -= rowHeight;
  }
  topRowY = y;

  // Last visible row
  row = (voff + iH) / rowHeight;
  bottomRow = (row >= nRows) ? (nRows - 1) : row;

  // First visible column
  int x, col, h = hScroll->value();
  for (col = x = 0; col < nCols; col++) {
    if (header[col].hidden)
      continue;

    x += header[col].width;
    if (x >= h) {
      x -= header[col].width;
      break;
    }
  }
  leftCol = (col >= nCols) ? (nCols - 1) : col;
  leftColX = x;

  // Last visible column
  h += iW;
  for (; col < nCols; col++) {
    if (header[col].hidden)
      continue;

    x += header[col].width;
    if (x >= h)
      break;
  }
  rightCol = (col >= nCols) ? (nCols - 1) : col;
}


/*
 * =======================
 *  void Table.resized();
 * =======================
 *
 * Calculates scrollbar properties after resizing or adding data.
 */
void Table::resized() {
  calcDimensions();

  // Calculate properties for vertical scrollbar.
  if (vScroll->visible()) {
    vScroll->bounds(0, tableHeight - iH);
    vScroll->resize(oX + oW - scrollbarSize, oY, scrollbarSize,
        oH - (hScroll->visible() ? scrollbarSize : 0));
    vScroll->Fl_Valuator::value(vScroll->clamp(vScroll->value()));
    vScroll->slider_size(iH > tableHeight ? 1 : (float)iH / tableHeight);
  }

  // Calculate properties for horizontal scrollbar.
  if (hScroll->visible()) {
    hScroll->bounds(0, tableWidth - iW);
    hScroll->resize(oX, oY + oH - scrollbarSize,
        oW - (vScroll->visible() ? scrollbarSize : 0), scrollbarSize);
    hScroll->Fl_Valuator::value(hScroll->clamp(hScroll->value()));
    hScroll->slider_size(iW > tableWidth ? 1 : (float)iW / tableWidth);
  }
  scrolled();
  dimensionsChanged = false;
}


/*
 * ===========================================================
 *  void Table.scrollCallback(Fl_Widget *widget, void *data);
 * ===========================================================
 *
 * Internal callback for scrollbars. Scrolls view.
 */
void Table::scrollCallback(Fl_Widget *widget, void *data) {
  Table *me = (Table*)data;

  me->scrolled();

  if (widget == me->vScroll)
    me->damage(DAMAGE_ROWS);
  else
    me->damage(DAMAGE_ROWS | DAMAGE_HEADER);
}

#include "re.h"

inline static
bool search_row(const std::vector<char**>& data, int row, int col, int ncols, fre_t& re, bool allcols)
{
  if (unlikely(allcols)) {
    for (col = 0; col < ncols; col++)
      if (re.match(data[row][col]))
	return true;
  }
  else if (re.match(data[row][col]))
    return true;
  return false;
}

/*
 * ==================================================================
 *  void Table.search(int& row, int& col, bool rev, const char* re);
 * ==================================================================
 *
 * Searches Table data starting at `row', in direction indicated by `rev',
 * for column data matching regexp `re'.  Looks in all row columns if `col'
 * is equal to nCols, or just the specified column if 0 <= col < nCols.
 * Returns true if found, in which case the `row' and `col' arguments will
 * point to the matching data.  If false is returned, the contents of
 * `row' and `col' are undefined.
 */

bool Table::search(int& row, int& col, bool rev, const char* re)
{
  if (unlikely(col < 0 || col > nCols || row < 0 || row >= nRows))
    return false;

  bool allcols = col == nCols;
  fre_t sre(re, REG_EXTENDED | REG_ICASE | REG_NOSUB);
  if (!sre)
    return false;


  int r = row;
  if (rev) {
    for (; row >= 0; row--)
      if (search_row(data, row, col, nCols, sre, allcols))
	return true;
    for (row = nRows - 1; row > r; row--)
      if (search_row(data, row, col, nCols, sre, allcols))
	return true;
  }
  else {
    for (; row < nRows; row++)
      if (search_row(data, row, col, nCols, sre, allcols))
	return true;
    for (row = 0; row < r; row++)
      if (search_row(data, row, col, nCols, sre, allcols))
	return true;
  }

  return false;
}
