//
// Fl_Table -- A table widget for the Fast Light Tool Kit (FLTK).
//
// Copyright 2002 by Greg Ercolano.
// Copyright (c) 2004 O'ksi'D
// Copyright 2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
//     https://www.fltk.org/bugs.php
//

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

// EXPERIMENTAL
// We use either std::vector or the private class Fl_Int_Vector
// depending on the build option FLTK_OPTION_STD or equivalent.
// This option allows to use std::string and maybe std::vector
// already in FLTK 1.4.x

#if (!FLTK_USE_STD)
#include "Fl_Int_Vector.H"      // Note: MUST NOT be included in Fl_Table.H
#endif

#include <sys/types.h>
#include <string.h>             // memcpy
#include <stdio.h>              // fprintf
#include <stdlib.h>             // realloc/free


/** Sets the vertical scroll position so 'row' is at the top,
    and causes the screen to redraw.
*/
void Fl_Table::row_position(int row) {
  if ( _row_position == row ) return;           // OPTIMIZATION: no change? avoid redraw
  if ( row < 0 ) row = 0;
  else if ( row >= rows() ) row = rows() - 1;
  if ( table_h <= tih ) return;                 // don't scroll if table smaller than window
  double newtop = row_scroll_position(row);
  if ( newtop > vscrollbar->maximum() ) {
    newtop = vscrollbar->maximum();
  }
  vscrollbar->Fl_Slider::value(newtop);
  table_scrolled();
  redraw();
  _row_position = row;  // HACK: override what table_scrolled() came up with
}

/**
  Sets the horizontal scroll position so 'col' is at the left,
  and causes the screen to redraw.
*/
void Fl_Table::col_position(int col) {
  if ( _col_position == col ) return;   // OPTIMIZATION: no change? avoid redraw
  if ( col < 0 ) col = 0;
  else if ( col >= cols() ) col = cols() - 1;
  if ( table_w <= tiw ) return;         // don't scroll if table smaller than window
  double newleft = col_scroll_position(col);
  if ( newleft > hscrollbar->maximum() ) {
    newleft = hscrollbar->maximum();
  }
  hscrollbar->Fl_Slider::value(newleft);
  table_scrolled();
  redraw();
  _col_position = col;  // HACK: override what table_scrolled() came up with
}

/**
  Returns the scroll position (in pixels) of the specified 'row'.
*/
long Fl_Table::row_scroll_position(int row) {
  int startrow = 0;
  long scroll = 0;
  // OPTIMIZATION:
  //     Attempt to use precomputed row scroll position
  //
  if ( toprow_scrollpos != -1 && row >= toprow ) {
    scroll = toprow_scrollpos;
    startrow = toprow;
  }
  for ( int t=startrow; t<row; t++ ) {
    scroll += row_height(t);
  }
  return(scroll);
}

/**
  Returns the scroll position (in pixels) of the specified column 'col'.
*/
long Fl_Table::col_scroll_position(int col) {
  int startcol = 0;
  long scroll = 0;
  // OPTIMIZATION:
  //     Attempt to use precomputed row scroll position
  //
  if ( leftcol_scrollpos != -1 && col >= leftcol ) {
    scroll = leftcol_scrollpos;
    startcol = leftcol;
  }
  for ( int t=startcol; t<col; t++ ) {
    scroll += col_width(t);
  }
  return(scroll);
}

/**
  The constructor for Fl_Table.
  This creates an empty table with no rows or columns,
  with headers and row/column resize behavior disabled.
*/
Fl_Table::Fl_Table(int X, int Y, int W, int H, const char *l) : Fl_Group(X,Y,W,H,l) {
  _rows             = 0;
  _cols             = 0;
  _row_header_w     = 40;
  _col_header_h     = 18;
  _row_header       = 0;
  _col_header       = 0;
  _row_header_color = color();
  _col_header_color = color();
  _row_resize       = 0;
  _col_resize       = 0;
  _row_resize_min   = 1;
  _col_resize_min   = 1;
  _redraw_toprow    = -1;
  _redraw_botrow    = -1;
  _redraw_leftcol   = -1;
  _redraw_rightcol  = -1;
  table_w           = 0;
  table_h           = 0;
  toprow            = 0;
  botrow            = 0;
  leftcol           = 0;
  rightcol          = 0;
  toprow_scrollpos  = -1;
  leftcol_scrollpos = -1;
  _last_cursor      = FL_CURSOR_DEFAULT;
  _resizing_col     = -1;
  _resizing_row     = -1;
  _dragging_x       = -1;
  _dragging_y       = -1;
  _last_row         = -1;
  _auto_drag        = 0;
  current_col       = -1;
  current_row       = -1;
  select_row        = -1;
  select_col        = -1;
  _scrollbar_size   = 0;
  flags_            = 0;        // TABCELLNAV off

#if (FLTK_USE_STDXX)
  _colwidths        = new std::vector<int>;  // column widths in pixels
  _rowheights       = new std::vector<int>;  // row heights in pixels
#else
  _colwidths        = new Fl_Int_Vector();  // column widths in pixels
  _rowheights       = new Fl_Int_Vector();  // row heights in pixels
#endif

  box(FL_THIN_DOWN_FRAME);

  vscrollbar = new Fl_Scrollbar(x()+w()-Fl::scrollbar_size(), y(),
                                Fl::scrollbar_size(), h()-Fl::scrollbar_size());
  vscrollbar->type(FL_VERTICAL);
  vscrollbar->callback(scroll_cb, (void*)this);

  hscrollbar = new Fl_Scrollbar(x(), y()+h()-Fl::scrollbar_size(),
                                w(), Fl::scrollbar_size());
  hscrollbar->type(FL_HORIZONTAL);
  hscrollbar->callback(scroll_cb, (void*)this);

  table = new Fl_Scroll(x(), y(), w(), h());
  table->box(FL_NO_BOX);
  table->type(0);               // don't show Fl_Scroll's scrollbars -- use our own
  table->hide();                // hide unless children are present
  table->end();

  table_resized();
  redraw();

  Fl_Group::end();              // end the group's begin()

  table->begin();               // leave with fltk children getting added to the scroll
}


/**
  The destructor for Fl_Table.
  Destroys the table and its associated widgets.
*/
Fl_Table::~Fl_Table() {
  // The parent Fl_Group takes care of destroying scrollbars
  delete _colwidths;
  delete _rowheights;
}


/**
  Returns the current number of columns.

  This is equivalent to the size of the column widths vector.

  \returns Number of columns.
*/
int Fl_Table::col_size() {
  return int(_colwidths->size());
}

/**
  Returns the current number of rows.

  This is equivalent to the size of the row heights vector.

  \returns Number of rows.
*/
int Fl_Table::row_size() {
  return int(_rowheights->size());
}

/**
  Sets the height of the specified row in pixels,
  and the table is redrawn.
  callback() will be invoked with CONTEXT_RC_RESIZE
  if the row's height was actually changed, and when() is FL_WHEN_CHANGED.
*/
void Fl_Table::row_height(int row, int height) {
  if ( row < 0 ) return;
  if ( row < row_size() && (*_rowheights)[row] == height ) {
    return;             // OPTIMIZATION: no change? avoid redraw
  }
  // Add row heights, even if none yet
  int now_size = row_size();
  if (row >= now_size) {
#if (FLTK_USE_STD)
    _rowheights->resize(row, height);
#else
    _rowheights->size(row);
    while (now_size < row)
      (*_rowheights)[now_size++] = height;
#endif // FLTK_USE_STD
  }
  (*_rowheights)[row] = height;
  table_resized();
  if ( row <= botrow ) {        // OPTIMIZATION: only redraw if onscreen or above screen
    redraw();
  }
  // ROW RESIZE CALLBACK
  if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
    do_callback(CONTEXT_RC_RESIZE, row, 0);
  }
}

/**
  Sets the width of the specified column in pixels, and the table is redrawn.
  callback() will be invoked with CONTEXT_RC_RESIZE
  if the column's width was actually changed, and when() is FL_WHEN_CHANGED.
*/
void Fl_Table::col_width(int col, int width)
{
  if ( col < 0 ) return;
  if ( col < col_size() && (*_colwidths)[col] == width ) {
    return;                     // OPTIMIZATION: no change? avoid redraw
  }
  // Add column widths, even if none yet
  int now_size = col_size();
  if ( col >= now_size ) {
#if (FLTK_USE_STD)
    _colwidths->resize(col+1, width);
#else
    _colwidths->size(col+1);
    while (now_size < col)
      (*_colwidths)[now_size++] = width;
#endif
  }
  (*_colwidths)[col] = width;
  table_resized();
  if ( col <= rightcol ) {      // OPTIMIZATION: only redraw if onscreen or to the left
    redraw();
  }
  // COLUMN RESIZE CALLBACK
  if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
    do_callback(CONTEXT_RC_RESIZE, 0, col);
  }
}

/**
  Return specified row/col values R and C to within the table's
  current row/col limits.
  \returns 0 if no changes were made, or 1 if they were.
*/
int Fl_Table::row_col_clamp(TableContext context, int &R, int &C) {
  int clamped = 0;
  if ( R < 0 ) { R = 0; clamped = 1; }
  if ( C < 0 ) { C = 0; clamped = 1; }
  switch ( context ) {
    case CONTEXT_COL_HEADER:
      // Allow col headers to draw even if no rows
      if ( R >= _rows && R != 0 ) { R = _rows - 1; clamped = 1; }
      break;

    case CONTEXT_ROW_HEADER:
      // Allow row headers to draw even if no columns
      if ( C >= _cols && C != 0 ) { C = _cols - 1; clamped = 1; }
      break;

    case CONTEXT_CELL:
    default:
      // CLAMP R/C TO _rows/_cols
      if ( R >= _rows ) { R = _rows - 1; clamped = 1; }
      if ( C >= _cols ) { C = _cols - 1; clamped = 1; }
      break;
  }
  return(clamped);
}

/**
  Returns the (X,Y,W,H) bounding region for the specified 'context'.
*/
void Fl_Table::get_bounds(TableContext context, int &X, int &Y, int &W, int &H) {
  switch ( context ) {
    case CONTEXT_COL_HEADER:
      // Column header clipping.
      X = tox;
      Y = wiy;
      W = tow;
      H = col_header_height();
      return;

    case CONTEXT_ROW_HEADER:
      // Row header clipping.
      X = wix;
      Y = toy;
      W = row_header_width();
      H = toh;
      return;

    case CONTEXT_TABLE:
      // Table inner dimensions
      X = tix; Y = tiy; W = tiw; H = tih;
      return;

      // TODO: Add other contexts..
    default:
      fprintf(stderr, "Fl_Table::get_bounds(): context %d unimplemented\n", (int)context);
      return;
  }
  //NOTREACHED
}

/**
  Find row/col for the recent mouse event.
  Returns the context, and the row/column values in R/C.
  Also returns 'resizeflag' if mouse is hovered over a resize boundary.
*/
Fl_Table::TableContext Fl_Table::cursor2rowcol(int &R, int &C, ResizeFlag &resizeflag) {
  // return values
  R = C = 0;
  resizeflag = RESIZE_NONE;
  // Row header?
  int X, Y, W, H;
  if ( row_header() ) {
    // Inside a row heading?
    get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
    if ( Fl::event_inside(X, Y, W, H) ) {
      // Scan visible rows until found
      for ( R = toprow; R <= botrow; R++ ) {
        find_cell(CONTEXT_ROW_HEADER, R, 0, X, Y, W, H);
        if ( Fl::event_y() >= Y && Fl::event_y() < (Y+H) ) {
          // Found row?
          //     If cursor over resize boundary, and resize enabled,
          //     enable the appropriate resize flag.
          //
          if ( row_resize() ) {
            if ( Fl::event_y() <= (Y+3-0) ) { resizeflag = RESIZE_ROW_ABOVE; }
            if ( Fl::event_y() >= (Y+H-3) ) { resizeflag = RESIZE_ROW_BELOW; }
          }
          return(CONTEXT_ROW_HEADER);
        }
      }
      // Must be in row header dead zone
      return(CONTEXT_NONE);
    }
  }
  // Column header?
  if ( col_header() ) {
    // Inside a column heading?
    get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
    if ( Fl::event_inside(X, Y, W, H) ) {
      // Scan visible columns until found
      for ( C = leftcol; C <= rightcol; C++ ) {
        find_cell(CONTEXT_COL_HEADER, 0, C, X, Y, W, H);
        if ( Fl::event_x() >= X && Fl::event_x() < (X+W) ) {
          // Found column?
          //     If cursor over resize boundary, and resize enabled,
          //     enable the appropriate resize flag.
          //
          if ( col_resize() ) {
            if ( Fl::event_x() <= (X+3-0) ) { resizeflag = RESIZE_COL_LEFT; }
            if ( Fl::event_x() >= (X+W-3) ) { resizeflag = RESIZE_COL_RIGHT; }
          }
          return(CONTEXT_COL_HEADER);
        }
      }
      // Must be in column header dead zone
      return(CONTEXT_NONE);
    }
  }
  // Mouse somewhere in table?
  //     Scan visible r/c's until we find it.
  //
  if ( Fl::event_inside(tox, toy, tow, toh) ) {
    for ( R = toprow; R <= botrow; R++ ) {
      find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
      if ( Fl::event_y() < Y ) break;           // OPT: thanks lars
      if ( Fl::event_y() >= (Y+H) ) continue;   // OPT: " "
      for ( C = leftcol; C <= rightcol; C++ ) {
        find_cell(CONTEXT_CELL, R, C, X, Y, W, H);
        if ( Fl::event_inside(X, Y, W, H) ) {
          return(CONTEXT_CELL);                 // found it
        }
      }
    }
    // Must be in a dead zone of the table
    R = C = 0;
    return(CONTEXT_TABLE);
  }
  // Somewhere else
  return(CONTEXT_NONE);
}

/**
  Find a cell's X/Y/W/H region for the specified cell in row 'R', column 'C'.
  \returns
    - 0 -- on success, XYWH returns the region of the specified cell.
    - -1 -- if R or C are out of range, and X/Y/W/H will be set to zero.
*/
int Fl_Table::find_cell(TableContext context, int R, int C, int &X, int &Y, int &W, int &H) {
  if ( row_col_clamp(context, R, C) ) {         // row or col out of range? error
    X=Y=W=H=0;
    return(-1);
  }
  X = (int)col_scroll_position(C) - hscrollbar->value() + tix;
  Y = (int)row_scroll_position(R) - vscrollbar->value() + tiy;
  W = col_width(C);
  H = row_height(R);

  switch ( context ) {
    case CONTEXT_COL_HEADER:
      Y = wiy;
      H = col_header_height();
      return(0);

    case CONTEXT_ROW_HEADER:
      X = wix;
      W = row_header_width();
      return(0);

    case CONTEXT_CELL:
      return(0);

    case CONTEXT_TABLE:
      return(0);

      // TODO -- HANDLE OTHER CONTEXTS
    default:
      fprintf(stderr, "Fl_Table::find_cell: unknown context %d\n", (int)context);
      return(-1);
  }
  //NOTREACHED
}

// Enable automatic scroll-selection
void Fl_Table::_start_auto_drag() {
  if (_auto_drag) return;
  _auto_drag = 1;
  Fl::add_timeout(0.3, _auto_drag_cb2, this);
}

// Disable automatic scroll-selection
void Fl_Table::_stop_auto_drag() {
  if (!_auto_drag) return;
  Fl::remove_timeout(_auto_drag_cb2, this);
  _auto_drag = 0;
}

void Fl_Table::_auto_drag_cb2(void *d) {
  ((Fl_Table*)d)->_auto_drag_cb();
}

// Handle automatic scroll-selection if mouse selection dragged off table edge
void Fl_Table::_auto_drag_cb() {
  int lx = Fl::e_x;
  int ly = Fl::e_y;
  if (_selecting == CONTEXT_COL_HEADER)
  { ly = y() + col_header_height(); }
  else if (_selecting == CONTEXT_ROW_HEADER)
  { lx = x() + row_header_width(); }
  if (lx > x() + w() - 20) {
    Fl::e_x = x() + w() - 20;
    if (hscrollbar->visible())
      ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() + 30));
    hscrollbar->do_callback();
    _dragging_x = Fl::e_x - 30;
  }
  else if (lx < (x() + row_header_width())) {
    Fl::e_x = x() + row_header_width() + 1;
    if (hscrollbar->visible()) {
      ((Fl_Slider*)hscrollbar)->value(hscrollbar->clamp(hscrollbar->value() - 30));
    }
    hscrollbar->do_callback();
    _dragging_x = Fl::e_x + 30;
  }
  if (ly > y() + h() - 20) {
    Fl::e_y = y() + h() - 20;
    if (vscrollbar->visible()) {
      ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() + 30));
    }
    vscrollbar->do_callback();
    _dragging_y = Fl::e_y - 30;
  }
  else if (ly < (y() + col_header_height())) {
    Fl::e_y = y() + col_header_height() + 1;
    if (vscrollbar->visible()) {
      ((Fl_Slider*)vscrollbar)->value(vscrollbar->clamp(vscrollbar->value() - 30));
    }
    vscrollbar->do_callback();
    _dragging_y = Fl::e_y + 30;
  }
  _auto_drag = 2;
  handle(FL_DRAG);
  _auto_drag = 1;
  Fl::e_x = lx;
  Fl::e_y = ly;
  Fl::check();
  Fl::flush();
  if (Fl::event_buttons() && _auto_drag) {
    Fl::add_timeout(0.05, _auto_drag_cb2, this);
  }
}

/**
  Recalculate the dimensions of the table, and affect any children.
  Internally, Fl_Group::resize() and init_sizes() are called.
*/
void Fl_Table::recalc_dimensions() {
  // Recalc to* (Table Outer), ti* (Table Inner), wi* ( Widget Inner)
  wix = ( x() + Fl::box_dx(box())); tox = wix; tix = tox + Fl::box_dx(table->box());
  wiy = ( y() + Fl::box_dy(box())); toy = wiy; tiy = toy + Fl::box_dy(table->box());
  wiw = ( w() - Fl::box_dw(box())); tow = wiw; tiw = tow - Fl::box_dw(table->box());
  wih = ( h() - Fl::box_dh(box())); toh = wih; tih = toh - Fl::box_dh(table->box());
  // Trim window if headers enabled
  if ( col_header() ) {
    tiy += col_header_height(); toy += col_header_height();
    tih -= col_header_height(); toh -= col_header_height();
  }
  if ( row_header() ) {
    tix += row_header_width(); tox += row_header_width();
    tiw -= row_header_width(); tow -= row_header_width();
  }
  // Make scroll bars disappear if window large enough
  {
    // First pass: can hide via window size?
    int hidev = (table_h <= tih);
    int hideh = (table_w <= tiw);
    int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
    // Second pass: Check for interference
    if ( !hideh && hidev ) { hidev = (( table_h - tih + scrollsize ) <= 0 ); }
    if ( !hidev && hideh ) { hideh = (( table_w - tiw + scrollsize ) <= 0 ); }
    // Determine scrollbar visibility, trim ti[xywh]/to[xywh]
    if ( hidev ) { vscrollbar->hide(); }
    else { vscrollbar->show(); tiw -= scrollsize; tow -= scrollsize; }
    if ( hideh ) { hscrollbar->hide(); }
    else { hscrollbar->show(); tih -= scrollsize; toh -= scrollsize; }
  }
  // Resize the child table
  table->resize(tox, toy, tow, toh);
  table->init_sizes();
}

/**
  Recalculate internals after a scroll.
  Call this if table has been scrolled or resized.
  Does not handle redraw().
  TODO: Assumes ti[xywh] has already been recalculated.
*/
void Fl_Table::table_scrolled() {
  // Find top row
  int y, row, voff = vscrollbar->value();
  for ( row=y=0; row < _rows; row++ ) {
    y += row_height(row);
    if ( y > voff ) { y -= row_height(row); break; }
  }
  _row_position = toprow = ( row >= _rows ) ? (row - 1) : row;
  toprow_scrollpos = y;         // OPTIMIZATION: save for later use
  // Find bottom row
  voff = vscrollbar->value() + tih;
  for ( ; row < _rows; row++ ) {
    y += row_height(row);
    if ( y >= voff ) { break; }
  }
  botrow = ( row >= _rows ) ? (row - 1) : row;
  // Left column
  int x, col, hoff = hscrollbar->value();
  for ( col=x=0; col < _cols; col++ ) {
    x += col_width(col);
    if ( x > hoff ) { x -= col_width(col); break; }
  }
  _col_position = leftcol = ( col >= _cols ) ? (col - 1) : col;
  leftcol_scrollpos = x;        // OPTIMIZATION: save for later use
  // Right column
  //    Work with data left over from leftcol calculation
  //
  hoff = hscrollbar->value() + tiw;
  for ( ; col < _cols; col++ ) {
    x += col_width(col);
    if ( x >= hoff ) { break; }
  }
  rightcol = ( col >= _cols ) ? (col - 1) : col;
  // First tell children to scroll
  draw_cell(CONTEXT_RC_RESIZE, 0,0,0,0,0,0);
}

/**
  Call this if table was resized, to recalculate internal data.
  Calls recall_dimensions(), and recalculates scrollbar sizes.
*/
void Fl_Table::table_resized() {
  table_h = (int)row_scroll_position(rows());
  table_w = (int)col_scroll_position(cols());
  recalc_dimensions();
  // Recalc scrollbar sizes
  //    Clamp scrollbar value() after a resize.
  //    Resize scrollbars to enforce a constant trough width after a window resize.
  //
  {
    // Vertical scrollbar
    float vscrolltab = ( table_h == 0 || tih > table_h ) ? 1 : (float)tih / table_h;
    float hscrolltab = ( table_w == 0 || tiw > table_w ) ? 1 : (float)tiw / table_w;
    int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
    vscrollbar->bounds(0, table_h-tih);
    vscrollbar->precision(10);
    vscrollbar->slider_size(vscrolltab);
    vscrollbar->resize(wix+wiw-scrollsize, wiy,
                       scrollsize,
                       wih - ((hscrollbar->visible())?scrollsize:0));
    vscrollbar->Fl_Valuator::value(vscrollbar->clamp(vscrollbar->value()));
    // Horizontal scrollbar
    hscrollbar->bounds(0, table_w-tiw);
    hscrollbar->precision(10);
    hscrollbar->slider_size(hscrolltab);
    hscrollbar->resize(wix, wiy+wih-scrollsize,
                       wiw - ((vscrollbar->visible())?scrollsize:0),
                       scrollsize);
    hscrollbar->Fl_Valuator::value(hscrollbar->clamp(hscrollbar->value()));
  }

  // Tell FLTK child widgets were resized
  Fl_Group::init_sizes();

  // Recalc top/bot/left/right
  table_scrolled();

  // DO *NOT* REDRAW -- LEAVE THIS UP TO THE CALLER
  // redraw();
}

/**
  Callback for when someone moves a scrollbar.
*/
void Fl_Table::scroll_cb(Fl_Widget*w, void *data) {
  Fl_Table *o = (Fl_Table*)data;
  o->recalc_dimensions();       // recalc tix, tiy, etc.
  o->table_scrolled();
  o->redraw();
}

/**
  Sets the number of rows in the table, and the table is redrawn.
*/
void Fl_Table::rows(int val) {
  int oldrows = _rows;
  _rows = val;

  int default_h = row_size() > 0 ? _rowheights->back() : 25;
  int now_size = row_size();

#if (FLTK_USE_STD)
  if (now_size != val)
    _rowheights->resize(val, default_h);      // enlarge or shrink as needed
#else
  _rowheights->size(val);                     // enlarge or shrink as needed
  while (now_size < val)
    (*_rowheights)[now_size++] = default_h;   // fill new
#endif

  table_resized();

  // OPTIMIZATION: redraw only if change is visible.
  if ( val >= oldrows && oldrows > botrow ) {
    // NO REDRAW
  } else {
    redraw();
  }
}

/**
  Set the number of columns in the table and redraw.
*/
void Fl_Table::cols(int val) {
  _cols = val;

  int default_w = col_size() > 0 ? (*_colwidths)[col_size()-1] : 80;
  int now_size = col_size();

#if (FLTK_USE_STD)
  if (now_size != val)
    _colwidths->resize(val, default_w);       // enlarge or shrink as needed
#else
  _colwidths->size(val);                      // enlarge or shrink as needed
  while (now_size < val)
    (*_colwidths)[now_size++] = default_w;    // fill new
#endif

  table_resized();
  redraw();
}

/**
  Change mouse cursor to different type
*/
void Fl_Table::change_cursor(Fl_Cursor newcursor) {
  if ( newcursor != _last_cursor ) {
    fl_cursor(newcursor, FL_BLACK, FL_WHITE);
    _last_cursor = newcursor;
  }
}

/**
  Sets the damage zone to the specified row/col values.
  Calls redraw_range().
*/
void Fl_Table::damage_zone(int r1, int c1, int r2, int c2, int r3, int c3) {
  int R1 = r1, C1 = c1;
  int R2 = r2, C2 = c2;
  if (r1 > R2) R2 = r1;
  if (r2 < R1) R1 = r2;
  if (r3 > R2) R2 = r3;
  if (r3 < R1) R1 = r3;
  if (c1 > C2) C2 = c1;
  if (c2 < C1) C1 = c2;
  if (c3 > C2) C2 = c3;
  if (c3 < C1) C1 = c3;
  if (R1 < 0) {
    if (R2 < 0) return;
    R1 = 0;
  }
  if (C1 < 0) {
    if (C2 < 0) return;
    C1 = 0;
  }
  if (R1 < toprow) R1 = toprow;
  if (R2 > botrow) R2 = botrow;
  if (C1 < leftcol) C1 = leftcol;
  if (C2 > rightcol) C2 = rightcol;
  redraw_range(R1, R2, C1, C2);
}

/**
  Moves the selection cursor a relative number of rows/columns specifed by R/C.
  R/C can be positive or negative, depending on the direction to move.
  A value of 0 for R or C prevents cursor movement on that axis.

  If shiftselect is set, the selection range is extended to the new
  cursor position. If clear, the cursor is simply moved, and any previous
  selection is cancelled.

  Used mainly by keyboard events (e.g. Fl_Right, FL_Home, FL_End..)
  to let the user keyboard navigate the selection cursor around.

  The scroll positions may be modified if the selection cursor traverses
  into cells off the screen's edge.

  Internal variables select_row/select_col and current_row/current_col
  are modified, among others.

  \code
  Examples:
      R=1, C=0  -- moves the selection cursor one row downward.
      R=5, C=0  -- moves the selection cursor 5 rows downward.
      R=-5, C=0 -- moves the cursor 5 rows upward.
      R=2, C=2  -- moves the cursor 2 rows down and 2 columns to the right.
  \endcode
*/
int Fl_Table::move_cursor(int R, int C, int shiftselect) {
  if (select_row == -1) R++;
  if (select_col == -1) C++;
  R += select_row;
  C += select_col;
  if (R < 0) R = 0;
  if (R >= rows()) R = rows() - 1;
  if (C < 0) C = 0;
  if (C >= cols()) C = cols() - 1;
  if (R == select_row && C == select_col) return 0;
  damage_zone(current_row, current_col, select_row, select_col, R, C);
  select_row = R;
  select_col = C;
  if (!shiftselect || !Fl::event_state(FL_SHIFT)) {
    current_row = R;
    current_col = C;
  }
  if (R < toprow + 1 || R > botrow - 1) row_position(R);
  if (C < leftcol + 1 || C > rightcol - 1) col_position(C);
  return 1;
}

/**
  Same as move_cursor(R,C,1);
*/
int Fl_Table::move_cursor(int R, int C) {
  return move_cursor(R,C,1);
}

//#define DEBUG 1
#ifdef DEBUG
#include <FL/names.h>
#define PRINTEVENT \
    fprintf(stderr,"Table %s: ** Event: %s --\n", (label()?label():"none"), fl_eventnames[event]);
#else
#define PRINTEVENT
#endif

/**
  Handle FLTK events.
*/
int Fl_Table::handle(int event) {
  PRINTEVENT;
  int ret = Fl_Group::handle(event);    // let FLTK group handle events first
  // Which row/column are we over?
  int R, C;                             // row/column being worked on
  ResizeFlag resizeflag;                // which resizing area are we over? (0=none)
  TableContext context = cursor2rowcol(R, C, resizeflag);
  if (ret) {
    if (Fl::event_inside(hscrollbar) || Fl::event_inside(vscrollbar)) return 1;
    if ( context != CONTEXT_ROW_HEADER &&               // mouse not in row header (STR#2742)
         context != CONTEXT_COL_HEADER &&               // mouse not in col header (STR#2742)
         Fl::focus() != this &&                         // we don't have focus?
         contains(Fl::focus())) {                       // focus is a child?
      return 1;
    }
  }
  // Make snapshots of realtime event states *before* we service user's cb,
  // which may do things like post popup menus that return with unexpected button states.
  int _event_button = Fl::event_button();
  int _event_clicks = Fl::event_clicks();
  int _event_x      = Fl::event_x();
  int _event_y      = Fl::event_y();
  int _event_key    = Fl::event_key();
  int _event_state  = Fl::event_state();
  Fl_Widget *_focus = Fl::focus();
  switch ( event ) {
    case FL_PUSH:
      // Single left-click on table? do user's callback with CONTEXT_TABLE
      if (_event_button == 1 && !_event_clicks) {
        if (_focus == this) {
          take_focus();
          do_callback(CONTEXT_TABLE, -1, -1);
          ret = 1;
        }
        damage_zone(current_row, current_col, select_row, select_col, R, C);
        if (context == CONTEXT_CELL) {
          current_row = select_row = R;
          current_col = select_col = C;
          _selecting = CONTEXT_CELL;
        } else {
          // Clear selection if not resizing row/col
          if ( !resizeflag ) {
            current_row = select_row = -1;
            current_col = select_col = -1;
          }
        }
      }
      // A click on table with user's callback defined?
      //     Need this for eg. right click to pop up a menu
      //
      if ( Fl_Widget::callback() &&             // callback defined?
          resizeflag == RESIZE_NONE ) {         // not resizing?
        do_callback(context, R, C);             // do callback with context (cell, header, etc)
      }
      // Handle selection if handling a left-click
      //    Use snapshot of _event_button we made before servicing user's cb's
      //    to avoid checking realtime state of buttons which may have changed
      //    during the user's callbacks.
      //
      switch ( context ) {
        case CONTEXT_CELL:
          // FL_PUSH on a cell?
          ret = 1;                              // express interest in FL_RELEASE
          break;

        case CONTEXT_NONE:
          // FL_PUSH on table corner?
          if ( _event_button == 1 && _event_x < x() + row_header_width()) {
            current_col = 0;
            select_col = cols() - 1;
            current_row = 0;
            select_row = rows() - 1;
            damage_zone(current_row, current_col, select_row, select_col);
            ret = 1;
          }
          break;

        case CONTEXT_COL_HEADER:
          // FL_PUSH on a column header?
          if ( _event_button == 1) {
            // Resizing? Handle it
            if ( resizeflag ) {
              // Start resize if left click on column border.
              //    "ret=1" ensures we get drag events from now on.
              //    (C-1) is used if mouse is over the left hand side
              //    of cell, so we resize the next column on the left.
              //
              _resizing_col = ( resizeflag & RESIZE_COL_LEFT ) ? C-1 : C;
              _resizing_row = -1;
              _dragging_x = _event_x;
              ret = 1;
            } else {
              // Not resizing? Select the column
              if ( Fl::focus() != this && contains(Fl::focus()) ) return 0;     // STR #3018 - item 1
              current_col = select_col = C;
              current_row = 0;
              select_row = rows() - 1;
              _selecting = CONTEXT_COL_HEADER;
              damage_zone(current_row, current_col, select_row, select_col);
              ret = 1;
            }
          }
          break;

        case CONTEXT_ROW_HEADER:
          // FL_PUSH on a row header?
          if ( _event_button == 1 ) {
            // Resizing? Handle it
            if ( resizeflag ) {
              // Start resize if left mouse clicked on row border.
              //    "ret = 1" ensures we get drag events from now on.
              //    (R-1) is used if mouse is over the top of the cell,
              //    so that we resize the row above.
              //
              _resizing_row = ( resizeflag & RESIZE_ROW_ABOVE ) ? R-1 : R;
              _resizing_col = -1;
              _dragging_y = _event_y;
              ret = 1;
            } else {
              // Not resizing? Select the row
              if ( Fl::focus() != this && contains(Fl::focus()) ) return 0;     // STR #3018 - item 1
              current_row = select_row = R;
              current_col = 0;
              select_col = cols() - 1;
              _selecting = CONTEXT_ROW_HEADER;
              damage_zone(current_row, current_col, select_row, select_col);
              ret = 1;
            }
          }
          break;

        default:
          ret = 0;              // express disinterest
          break;
      }
      _last_row = R;
      break;

    case FL_DRAG:
      if (_auto_drag == 1) {
        ret = 1;
        break;
      }
      if ( _resizing_col > -1 ) {
        // Dragging column?
        //
        //    Let user drag even /outside/ the row/col widget.
        //    Don't allow column width smaller than 1.
        //    Continue to show FL_CURSOR_WE at all times during drag.
        //
        int offset = _dragging_x - _event_x;
        int new_w = col_width(_resizing_col) - offset;
        if ( new_w < _col_resize_min ) new_w = _col_resize_min;
        col_width(_resizing_col, new_w);
        _dragging_x = _event_x;
        table_resized();
        redraw();
        change_cursor(FL_CURSOR_WE);
        ret = 1;
        if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
          do_callback(CONTEXT_RC_RESIZE, R, C);
        }
      }
      else if ( _resizing_row > -1 ) {
        // Dragging row?
        //
        //    Let user drag even /outside/ the row/col widget.
        //    Don't allow row width smaller than 1.
        //    Continue to show FL_CURSOR_NS at all times during drag.
        //
        int offset = _dragging_y - _event_y;
        int new_h = row_height(_resizing_row) - offset;
        if ( new_h < _row_resize_min ) new_h = _row_resize_min;
        row_height(_resizing_row, new_h);
        _dragging_y = _event_y;
        table_resized();
        redraw();
        change_cursor(FL_CURSOR_NS);
        ret = 1;
        if ( Fl_Widget::callback() && when() & FL_WHEN_CHANGED ) {
          do_callback(CONTEXT_RC_RESIZE, R, C);
        }
      } else {
        if (_event_button == 1 &&
            _selecting == CONTEXT_CELL &&
            context == CONTEXT_CELL) {
          // Dragging a cell selection?
          if ( _event_clicks ) break;                   // STR #3018 - item 2
          if (select_row != R || select_col != C) {
            damage_zone(current_row, current_col, select_row, select_col, R, C);
          }
          select_row = R;
          select_col = C;
          ret = 1;
        }
        else if (_event_button == 1 &&
                 _selecting == CONTEXT_ROW_HEADER &&
                 context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
          if (select_row != R) {
            damage_zone(current_row, current_col, select_row, select_col, R, C);
          }
          select_row = R;
          ret = 1;
        }
        else if (_event_button == 1 &&
                 _selecting == CONTEXT_COL_HEADER
                 && context & (CONTEXT_ROW_HEADER|CONTEXT_COL_HEADER|CONTEXT_CELL)) {
          if (select_col != C) {
            damage_zone(current_row, current_col, select_row, select_col, R, C);
          }
          select_col = C;
          ret = 1;
        }
      }
      // Enable autodrag if not resizing, and mouse has moved off table edge
      if ( _resizing_row < 0 && _resizing_col < 0 && _auto_drag == 0 &&
          ( _event_x > x() + w() - 20 ||
            _event_x < x() + row_header_width() ||
            _event_y > y() + h() - 20 ||
            _event_y < y() + col_header_height()
           ) ) {
            _start_auto_drag();
          }
      break;

    case FL_RELEASE:
      _stop_auto_drag();
      switch ( context ) {
        case CONTEXT_ROW_HEADER:                // release on row header
        case CONTEXT_COL_HEADER:                // release on col header
        case CONTEXT_CELL:                      // release on a cell
        case CONTEXT_TABLE:                     // release on dead zone
          if ( _resizing_col == -1 &&           // not resizing a column
              _resizing_row == -1 &&            // not resizing a row
              Fl_Widget::callback() &&          // callback defined
              when() & FL_WHEN_RELEASE &&       // on button release
              _last_row == R ) {                // release on same row PUSHed?
            // Need this for eg. left clicking on a cell to select it
            do_callback(context, R, C);
          }
          break;

        default:
          break;
      }
      if ( _event_button == 1 ) {
        change_cursor(FL_CURSOR_DEFAULT);
        _resizing_col = -1;
        _resizing_row = -1;
        ret = 1;
      }
      break;

    case FL_MOVE:
      if ( context == CONTEXT_COL_HEADER &&             // in column header?
          resizeflag ) {                                // resize + near boundary?
        change_cursor(FL_CURSOR_WE);                    // show resize cursor
      }
      else if ( context == CONTEXT_ROW_HEADER &&        // in row header?
               resizeflag ) {                           // resize + near boundary?
        change_cursor(FL_CURSOR_NS);                    // show resize cursor
      } else {
        change_cursor(FL_CURSOR_DEFAULT);               // normal cursor
      }
      ret = 1;
      break;

    case FL_ENTER:              // See FLTK event docs on the FL_ENTER widget
      if (!ret) take_focus();
      ret = 1;
      //FALLTHROUGH

    case FL_LEAVE:              // We want to track the mouse if resizing is allowed.
      if ( resizeflag ) {
        ret = 1;
      }
      if ( event == FL_LEAVE ) {
        _stop_auto_drag();
        change_cursor(FL_CURSOR_DEFAULT);
      }
      break;

    case FL_FOCUS:
      Fl::focus(this);
      //FALLTHROUGH

    case FL_UNFOCUS:
      _stop_auto_drag();
      ret = 1;
      break;

    case FL_KEYBOARD: {
      ret = 0;
      int is_row = select_row;
      int is_col = select_col;
      switch(_event_key) {
        case FL_Home:
          ret = move_cursor(0, -1000000);
          break;
        case FL_End:
          ret = move_cursor(0, 1000000);
          break;
        case FL_Page_Up:
          ret = move_cursor(-(botrow - toprow - 1), 0);
          break;
        case FL_Page_Down:
          ret = move_cursor(botrow - toprow - 1 , 0);
          break;
        case FL_Left:
          ret = move_cursor(0, -1);
          break;
        case FL_Right:
          ret = move_cursor(0, 1);
          break;
        case FL_Up:
          ret = move_cursor(-1, 0);
          break;
        case FL_Down:
          ret = move_cursor(1, 0);
          break;
        case FL_Tab:
          if ( !tab_cell_nav() ) break;         // not navigating cells? let fltk handle it (STR#2862)
          if ( _event_state & FL_SHIFT ) {
            ret = move_cursor(0, -1, 0);        // shift-tab -> left
          } else {
            ret = move_cursor(0, 1, 0);         // tab -> right
          }
          break;
      }
      if (ret && Fl::focus() != this) {
        do_callback(CONTEXT_TABLE, -1, -1);
        take_focus();
      }
      //if (!ret && Fl_Widget::callback() && when() & FL_WHEN_NOT_CHANGED  )
      if ( Fl_Widget::callback() &&
          (
           ( !ret && when() & FL_WHEN_NOT_CHANGED ) ||
           ( is_row!= select_row || is_col!= select_col )
           )
          ) {
        do_callback(CONTEXT_CELL, select_row, select_col);
        //damage_zone(current_row, current_col, select_row, select_col);
        ret = 1;
      }
      break;
    }

    default:
      change_cursor(FL_CURSOR_DEFAULT);
      break;
  }
  return(ret);
}

/**
  Handle resize events if user resizes parent window.
  This changes the size of Fl_Table, causing it to redraw.
*/
void Fl_Table::resize(int X, int Y, int W, int H) {
  // Tell group to resize, and recalc our own widget as well
  Fl_Group::resize(X, Y, W, H);
  table_resized();
  redraw();
}

// Draw a cell
void Fl_Table::_redraw_cell(TableContext context, int r, int c) {
  if ( r < 0 || c < 0 ) return;
  int X,Y,W,H;
  find_cell(context, r, c, X, Y, W, H); // find positions of cell
  draw_cell(context, r, c, X, Y, W, H); // call users' function to draw it
}

/**
  See if the cell at row \p r and column \p c is selected.
  \returns 1 if the cell is selected, 0 if not.
*/
int Fl_Table::is_selected(int r, int c) {
  int s_left, s_right, s_top, s_bottom;

  if (select_col > current_col) {
    s_left = current_col;
    s_right = select_col;
  } else {
    s_right = current_col;
    s_left = select_col;
  }
  if (select_row > current_row) {
    s_top = current_row;
    s_bottom = select_row;
  } else {
    s_bottom = current_row;
    s_top = select_row;
  }
  if (r >= s_top && r <= s_bottom && c >= s_left && c <= s_right) {
    return 1;
  }
  return 0;
}

/**
  Gets the region of cells selected (highlighted).

  \param[in] row_top   Returns the top row of selection area
  \param[in] col_left  Returns the left column of selection area
  \param[in] row_bot   Returns the bottom row of selection area
  \param[in] col_right Returns the right column of selection area
*/
void Fl_Table::get_selection(int& row_top, int& col_left, int& row_bot, int& col_right) {
  if (select_col > current_col) {
    col_left  = current_col;
    col_right = select_col;
  } else {
    col_right = current_col;
    col_left  = select_col;
  }
  if (select_row > current_row) {
    row_top = current_row;
    row_bot = select_row;
  } else {
    row_bot = current_row;
    row_top = select_row;
  }
}

/**
  Sets the region of cells to be selected (highlighted).

  So for instance, set_selection(0,0,0,0) selects the top/left cell in the table.
  And set_selection(0,0,1,1) selects the four cells in rows 0 and 1, column 0 and 1.

  To deselect all cells, use set_selection(-1,-1,-1,-1);

  \param[in] row_top   Top row of selection area
  \param[in] col_left  Left column of selection area
  \param[in] row_bot   Bottom row of selection area
  \param[in] col_right Right column of selection area
*/
void Fl_Table::set_selection(int row_top, int col_left, int row_bot, int col_right) {
  damage_zone(current_row, current_col, select_row, select_col);
  current_col = col_left;
  current_row = row_top;
  select_col  = col_right;
  select_row  = row_bot;
  damage_zone(current_row, current_col, select_row, select_col);
}

/**
  Draws the entire Fl_Table.
  Lets fltk widgets draw themselves first, followed by the cells
  via calls to draw_cell().
*/
void Fl_Table::draw() {
    int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
  // Check if scrollbar size changed
  if ( ( vscrollbar && (scrollsize != vscrollbar->w()) ) ||
       ( hscrollbar && (scrollsize != hscrollbar->h()) ) ) {
    // handle size change, min/max, table dim's, etc
    table_resized();
  }

  draw_cell(CONTEXT_STARTPAGE, 0, 0,            // let user's drawing routine
            tix, tiy, tiw, tih);                // prep new page

  // Let fltk widgets draw themselves first. Do this after
  // draw_cell(CONTEXT_STARTPAGE) in case user moves widgets around.
  // Use window 'inner' clip to prevent drawing into table border.
  // (unfortunately this clips FLTK's border, so we must draw it explicitly below)
  //
  fl_push_clip(wix, wiy, wiw, wih);
  {
    Fl_Group::draw();
  }
  fl_pop_clip();

  // Explicitly draw border around widget, if any
  draw_box(box(), x(), y(), w(), h(), color());

  // If Fl_Scroll 'table' is hidden, draw its box
  //    Do this after Fl_Group::draw() so we draw over scrollbars
  //    that leak around the border.
  //
  if ( ! table->visible() ) {
    if ( damage() & FL_DAMAGE_ALL || damage() & FL_DAMAGE_CHILD ) {
      draw_box(table->box(), tox, toy, tow, toh, table->color());
    }
  }
  // Clip all further drawing to the inner widget dimensions
  fl_push_clip(wix, wiy, wiw, wih);
  {
    // Only redraw a few cells?
    if ( ! ( damage() & FL_DAMAGE_ALL ) && _redraw_leftcol != -1 ) {
      fl_push_clip(tix, tiy, tiw, tih);
      for ( int c = _redraw_leftcol; c <= _redraw_rightcol; c++ ) {
        for ( int r = _redraw_toprow; r <= _redraw_botrow; r++ ) {
          _redraw_cell(CONTEXT_CELL, r, c);
        }
      }
      fl_pop_clip();
    }
    if ( damage() & FL_DAMAGE_ALL ) {
      int X,Y,W,H;
      // Draw row headers, if any
      if ( row_header() ) {
        get_bounds(CONTEXT_ROW_HEADER, X, Y, W, H);
        fl_push_clip(X,Y,W,H);
        for ( int r = toprow; r <= botrow; r++ ) {
          _redraw_cell(CONTEXT_ROW_HEADER, r, 0);
        }
        fl_pop_clip();
      }
      // Draw column headers, if any
      if ( col_header() ) {
        get_bounds(CONTEXT_COL_HEADER, X, Y, W, H);
        fl_push_clip(X,Y,W,H);
        for ( int c = leftcol; c <= rightcol; c++ ) {
          _redraw_cell(CONTEXT_COL_HEADER, 0, c);
        }
        fl_pop_clip();
      }
      // Draw all cells.
      //    This includes cells partially obscured off edges of table.
      //    No longer do this last; you might think it would be nice
      //    to draw over dead zones, but on redraws it flickers. Avoid
      //    drawing over deadzones; prevent deadzones by sizing columns.
      //
      fl_push_clip(tix, tiy, tiw, tih); {
        for ( int r = toprow; r <= botrow; r++ ) {
          for ( int c = leftcol; c <= rightcol; c++ ) {
            _redraw_cell(CONTEXT_CELL, r, c);
          }
        }
      }
      fl_pop_clip();
      // Draw little rectangle in corner of headers
      if ( row_header() && col_header() ) {
        fl_rectf(wix, wiy, row_header_width(), col_header_height(), color());
      }

      // Table has a boxtype? Close those few dead pixels
      if ( table->box() ) {
        if ( col_header() ) {
          fl_rectf(tox, wiy, Fl::box_dx(table->box()), col_header_height(), color());
        }
        if ( row_header() ) {
          fl_rectf(wix, toy, row_header_width(), Fl::box_dx(table->box()), color());
        }
      }

      // Table width smaller than window? Fill remainder with rectangle
      if ( table_w < tiw ) {
        fl_rectf(tix + table_w, tiy, tiw - table_w, tih, color());
        // Col header? fill that too
        if ( col_header() ) {
          fl_rectf(tix + table_w,
                   wiy,
                   // get that corner just right..
                   (tiw - table_w + Fl::box_dw(table->box()) -
                    Fl::box_dx(table->box())),
                   col_header_height(),
                   color());
        }
      }
      // Table height smaller than window? Fill remainder with rectangle
      if ( table_h < tih ) {
        fl_rectf(tix, tiy + table_h, tiw, tih - table_h, color());
        if ( row_header() ) {
          // NOTE:
          //     Careful with that lower corner; don't use tih; when eg.
          //     table->box(FL_THIN_UP_FRAME) and hscrollbar hidden,
          //     leaves a row of dead pixels.
          //
          fl_rectf(wix, tiy + table_h, row_header_width(),
                   (wiy+wih) - (tiy+table_h) -
                   ( hscrollbar->visible() ? scrollsize : 0),
                   color());
        }
      }
    }
    // Both scrollbars? Draw little box in lower right
    if ( vscrollbar->visible() && hscrollbar->visible() ) {
      fl_rectf(vscrollbar->x(), hscrollbar->y(),
               vscrollbar->w(), hscrollbar->h(), color());
    }
    draw_cell(CONTEXT_ENDPAGE, 0, 0,            // let user's drawing
              tix, tiy, tiw, tih);              // routines cleanup

    _redraw_leftcol = _redraw_rightcol = _redraw_toprow = _redraw_botrow = -1;
  }
  fl_pop_clip();
}

/**
  Returns the current height of the specified row as a value in pixels.
*/
int Fl_Table::row_height(int row) {
  return((row < 0 || row >= row_size()) ? 0 : (*_rowheights)[row]);
}

/**
  Returns the current width of the specified column in pixels.
*/
int Fl_Table::col_width(int col) {
  return((col < 0 || col >= col_size()) ? 0 : (*_colwidths)[col]);
}
