/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CListView.cpp  -  description
                             -------------------
    begin                : Fri Mar 31 2000
    copyright            : (C) 2000-2005 by Alexey S.Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library is distributed in the hope that it will be useful, but 
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

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

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

#include <sptk3/CListView.h>
#include <sptk3/CQuery.h>
#include <sptk3/CException.h>
#include <sptk3/CThemes.h>
#include <sptk3/CStrings.h>
#include <sptk3/string_ext.h>

using namespace std;

// default header size
#define HEADER_HEIGHT 20

void CListView::scrollbar_callback(Fl_Widget* s, void*) {
   ((CListView*)(s->parent()))->top(int(((CScrollBar*)s)->value()));
}

void CListView::hscrollbar_callback(Fl_Widget* s, void*) {
   ((CListView*)(s->parent()))->hposition(int(((CScrollBar*)s)->value()));
}

int CListView::m_scrollbarWidth = 16;

// Cause minimal update to redraw the given item:
void CListView::redraw_line(unsigned l) {
   if (!m_redraw1 || m_redraw1 == (int)l) {m_redraw1 = l; damage(FL_DAMAGE_EXPOSE);}
   else if (!m_redraw2 || m_redraw2 == (int)l) {m_redraw2 = l; damage(FL_DAMAGE_EXPOSE);}
   else damage(FL_DAMAGE_SCROLL);
}

void CListView::hposition(int x) {
   if (x < 0) x = 0;
   if (x == m_horizPosition) return;
   m_horizPosition = x;
   if (x != m_realHorizPosition) redraw_lines();
}

void CListView::ctor_init() 
{
   m_dataMode = LV_DATA_UNDEFINED;
   m_activeRow = 0;
   m_capitalizeColumnNames = false;
   m_fillTerminated = false;
   m_fillInProgress = false;
   m_multipleSelection = true;
   box(FL_NO_BOX);
   m_horizPosition = m_realHorizPosition = 0;
   m_top = 0;
   m_headerHeight = HEADER_HEIGHT;
   when(FL_WHEN_RELEASE_ALWAYS);
   selection_color(FL_SELECTION_COLOR);
   scrollbar.callback(scrollbar_callback);
   hscrollbar.callback(hscrollbar_callback);
   scrollbar.visible_focus(false);
   hscrollbar.visible_focus(false);
   hscrollbar.type(FL_HORIZONTAL);
   m_maxWidth = 0;
   m_redraw1 = m_redraw2 = 0;
   end();

   m_control = this;

   align(Fl_Align(FL_ALIGN_LEFT | FL_ALIGN_TOP));

   m_draggingColumn  = -1;
   m_showGrid        = false;
   m_showStripes     = false;
   m_showSelection   = true;
   m_autoRowHeight   = false;
   m_textFont        = FL_HELVETICA;
   m_textSize        = FL_NORMAL_SIZE;
   m_textColor       = FL_BLACK;
   m_currentTextFont = m_textFont;
   m_currentTextSize = m_textSize;
}

CListView::CListView(const char *label,int layoutSize,CLayoutAlign layoutAlignment)
: CControl(label,layoutSize,layoutAlignment), scrollbar(0, 0, 0, 0), hscrollbar(0, 0, 0, 0)
{
   ctor_init();
}

#ifdef __COMPATIBILITY_MODE__    
CListView::CListView(int x, int y, int w, int h, const char*l)
: CControl(x, y, w, h, l), scrollbar(0, 0, 0, 0, 0), hscrollbar(0, 0, 0, 0, 0) 
{
   ctor_init();
}
#endif

// the scrollbars are resized & placed by draw(), since each one's size
// depends on whether the other is visible or not.  This skips over
// Fl_Group::resize since it moves the scrollbars uselessly.
void CListView::resize(int X, int Y, int W, int H) {
   Fl_Widget::resize(X, Y, W, H);

   // Determine the optimal scroll bar	width
   int sbh = H;
   m_scrollbarWidth = 0;
   scrollbar.preferredSize(m_scrollbarWidth,sbh);

   bbox(X,Y,W,H);

   scrollbar.resize(scrollbar.align()&FL_ALIGN_LEFT ? X-m_scrollbarWidth : X+W, Y, m_scrollbarWidth, H);
   hscrollbar.resize(X, scrollbar.align()&FL_ALIGN_TOP ? Y-m_scrollbarWidth : Y+H, W, m_scrollbarWidth);

   if (m_menuButton)
      m_menuButton->resize(X, Y, W, H);
}

void CListView::displayRow(unsigned index) {
   if (index >= m_rows.size()) return;
   if (displayed(index)) return;

   int X, Y, W, H;
   bbox(X, Y, W, H);

   // find the bottom
   int hh = 0;
   bool lastPage = true;
   for (unsigned i = index; i < m_rows.size(); i++) {
      int rh = m_rows[i]->height;
      hh += rh; 
      if (hh > H) {
         lastPage = false;
         break;
      }
   }

   if (lastPage) { // Now find top
      for (int i = index-1; i >= 0; i--) {
         int rh = m_rows[i]->height;
         hh += rh;
         if (hh > H) {
            m_top = i;
            break;
         }
      }
   } else {
      hh = 0;
      H /= 2;
      m_top = index;
      for (int i = index; i >= 0; i--) {
         int rh = m_rows[i]->height;
         hh += rh;
         if (hh > H) {
            m_top = i;
            break;
         }
      }
   }

   redraw();
}

// redraw, has side effect of updating top and setting scrollbar:
void CListView::draw() {
   if (m_currentTextFont != m_textFont || m_currentTextSize != m_textSize) {
      m_currentTextFont = m_textFont;
      m_currentTextSize = m_textSize;
      item_compute_height_all();
   }

   Fl_Color    bgColor = color();
   if (box() != FL_FLAT_BOX) {
      if (bgColor == FL_BACKGROUND_COLOR)
         bgColor = FL_LIGHT3;
      else bgColor = fl_lighter(fl_lighter(bgColor));
   }

   int drawsquare = 0;
   if (damage() & FL_DAMAGE_ALL) { // redraw the box if full redraw
      Fl_Boxtype b = box() ? box() : FL_DOWN_BOX;
      draw_box(b, x(), y(), w(), h(), bgColor);
      drawsquare = 1;
   }

   scrollbar.clear_visible();
   hscrollbar.clear_visible();

   int full_width_ = fullWidth();
   int full_height_ = fullHeight();

   int X, Y, W, H;
   bbox(X, Y, W, H);

   if (m_top >= m_rows.size())
      m_top = 0;

   // see if scrollbar needs to be switched on/off:
   for (int iteration = 0; iteration < 2; iteration++) {
      if (full_height_ > H) {
         if (!scrollbar.visible()) {
            scrollbar.set_visible();
            W -= m_scrollbarWidth;
            drawsquare = 1;
         }
      } else {
         m_top = 0;
         if (scrollbar.visible()) {
            scrollbar.clear_visible();
            clear_damage(uchar(damage()|FL_DAMAGE_SCROLL));
         }
      }

      if (full_width_ > W) {
         if (!hscrollbar.visible()) {
            hscrollbar.set_visible();
            H -= m_scrollbarWidth;
            drawsquare = 1;
         }
      } else {
         m_realHorizPosition = 0;
         if (hscrollbar.visible()) {
            hscrollbar.clear_visible();
            clear_damage(uchar(damage()|FL_DAMAGE_SCROLL));
         }
      }
   }

   if (m_headerHeight)
      header_draw(X-m_horizPosition, Y-m_headerHeight, m_headerHeight);

   fl_push_clip(X, Y, W, H);

   int l = top();

   int yy = 0;
   m_maxWidth = item_width(l);

   draw_box(FL_FLAT_BOX,X,Y,W,H,FL_LIGHT3);

   int maxl = size();
   for (; l < maxl && yy < H; l++) {
      int hh = item_height(l);
      if (hh <= 0) continue;
      if ((damage()&(FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) || l == m_redraw1 || l == m_redraw2) {
         fl_push_clip(X, Y+yy, W, hh);
         int textAlign = FL_ALIGN_TOP;
         if (m_autoRowHeight) 
            textAlign |= FL_ALIGN_WRAP;
         item_draw(l, 0L, X-m_horizPosition, yy+Y, W+m_horizPosition, hh, Fl::focus() == this,textAlign,false);
         fl_pop_clip();
      }
      yy += hh;
   }

   fl_pop_clip();

   m_redraw1 = m_redraw2 = -1;

   // update the scrollbars and redraw them:
   int dy = m_top ? item_height(m_top) : 0; if (dy < 10) dy = 10;
   if (scrollbar.visible()) {
      int visibleRows = 0;
      int hh = 0;
      for (unsigned i = m_top; i < m_rows.size(); i++) {
         hh += m_rows[i]->height;
         if (hh < H)
            visibleRows++;
         else break;
      }
      scrollbar.damage_resize(
         scrollbar.align()&FL_ALIGN_LEFT ? X-m_scrollbarWidth : X+W,
         Y, m_scrollbarWidth, H);
      scrollbar.value(m_top, visibleRows, 0, m_rows.size());
      scrollbar.linesize(1);
      if (drawsquare) draw_child(scrollbar);
      else update_child(scrollbar);
   }
   if (hscrollbar.visible()) {
      hscrollbar.damage_resize(
         X, scrollbar.align()&FL_ALIGN_TOP ? Y-m_scrollbarWidth : Y+H,
         W, m_scrollbarWidth);
      hscrollbar.value(m_horizPosition, W, 0, full_width_);
      hscrollbar.linesize(dy);
      if (drawsquare) draw_child(hscrollbar);
      else update_child(hscrollbar);
   }

    // draw that little square between the scrollbars:
   if (drawsquare && scrollbar.visible() && hscrollbar.visible()) {
      fl_color(parent()->color());
      fl_rectf(scrollbar.x(), hscrollbar.y(), m_scrollbarWidth,m_scrollbarWidth);
   }

   m_realHorizPosition = m_horizPosition;
}

bool CListView::item_selected(unsigned index) const {
   return m_rows[index]->flags & CLV_SELECTED;
}

void CListView::item_select(unsigned index, bool v) {
   if (index >= m_rows.size()) return;
   CPackedStrings *item = m_rows[index];
   if (v) {
      m_selection.select(item);
   } else {
      m_selection.deselect(item);
   }
}

void CListView::removeRow(unsigned line) {
   if (line > m_rows.size()) return;
   m_selection.deselect(m_rows[line]);
   if (displayed(line)) redraw_lines();
   m_rows.remove(line);
}

int CListView::item_height(unsigned index) const {
   try {
      CPackedStrings *l = m_rows[index];
      if (l->flags & CLV_NOTDISPLAYED) return 0;
      int h = (unsigned char) l->height;
      if (h < 6)
         h = 6;
      return h;
   }
   catch (...) {
   }

   return 0;
}

int CListView::item_compute_height_all() {
   unsigned cnt = m_rows.size();
   unsigned totalHeight = 0;
   for (unsigned i = 0; i < cnt; i++) 
      totalHeight += item_compute_height(m_rows[i]);
   m_rows.m_fullHeight = totalHeight;
   return totalHeight;
}

int CListView::item_compute_height(CPackedStrings *l) {
   if (l->flags & CLV_NOTDISPLAYED) return 0;

   if (fl_font() != m_textFont || fl_size() != m_textSize)
      fl_font(m_textFont,m_textSize);

   CPackedStrings& cells = *l;

    // For blank lines set the height to exactly 1 line!
   unsigned hmax = m_textSize;
   unsigned borderWidth = cellBorderWidth();
   int ch;
   int cw;
   const char *str;

   if (cells.size()) {
      short colmax = cells.size();
      if (colmax > (short)m_columnList.size())
         colmax = m_columnList.size();
      for (short c = 0; c < colmax; c++) {
         CColumn&  column = m_columnList[c];
         if (column.visible()) {
            str = cells[c];
            ch = 0;
            cw = column.width() - borderWidth;

            if (column.autoWidth())
               cw = 400;   // max width for auto-width column

            if (column.autoWidth() || m_autoRowHeight)
               fl_measure(str, cw, ch, 0);
            else  ch = m_textSize + 2;

            cw += borderWidth;
            if (column.autoWidth()) {
               if ((kind() & (DCV_CHECKBUTTONS | DCV_RADIOBUTTONS)) && c == 0 )
                  cw += (m_textSize - 2) / 3 * 3 + 3;
               if (cw > column.width())
                  column.width(cw); // adjust column width for auto-width column
            }
            if (ch > (int)hmax) hmax = ch;
         }
      }
   }

   hmax += borderWidth;
   if (hmax > 254) hmax = 254;
   l->height = (unsigned char)hmax;
   return hmax;
}

int CListView::item_width(unsigned) const {
   unsigned w = 6;
   unsigned cnt = m_columnList.size();
   for (unsigned i = 0; i < cnt; i++) {
      if (m_columnList[i].visible())
         w += m_columnList[i].width();
   }

   return w;
}

void CListView::sortColumn(int column,bool sortNow) {
   if ((unsigned)column >= m_columnList.size())
      return;
   CColumn& columnInfo = m_columnList[column];
   CVariantType ctype = columnInfo.type();
   CPackedStrings *row = 0L;
   if (m_activeRow < size())
      row = m_rows[m_activeRow];
   m_rows.sortColumn(column,ctype,sortNow);
   if (row && sortNow) {
      m_activeRow = m_rows.indexOf(row);
      if (!displayed(m_activeRow))
         displayRow(m_activeRow);
      else redraw();
   }
}

int CListView::sortColumn() const {
   return m_rows.sortColumn();
}

std::string CListView::sortColumnName() const {
   int ndx = sortColumn();
   if (ndx < 0) return "";
   return m_columnList[ndx].name();
}

void CListView::sortAscending(bool ascending,bool sortNow) {
   CPackedStrings *row = 0L;
   if (m_activeRow < size())
      row = m_rows[m_activeRow];
   m_rows.sortAscending(ascending,sortNow);
   if (row && sortNow) {
      m_activeRow = m_rows.indexOf(row);
      if (!displayed(m_activeRow))
         displayRow(m_activeRow);
      else redraw();
   }
}

void CListView::header_draw(int xx, int y, int hh) const {
   fl_push_clip(x()+2,y,w()-3,hh);
   fl_font(m_textFont,m_textSize);
   unsigned cnt = m_columnList.size();
   for (unsigned i = 0; i < cnt; i++) {
      const CColumn&  column = m_columnList[i];
      bool sortColumn = (int(i) == m_rows.sortColumn());
      if (column.visible()) {
         int width = column.width();
         int markSize = hh - 8;
         if (!CThemes::drawButton(THM_BUTTON_COMBO, xx, y, width, hh, sortColumn, false, false)) {
            // Workaround for the FLTK bug..
            draw_box(FL_FLAT_BOX, xx, y, width, hh, FL_BACKGROUND_COLOR);
            draw_box(FL_UP_FRAME, xx, y, width, hh, FL_BACKGROUND_COLOR);
         }
         int tx = xx + 3;
         int twidth = width - 6;
         int clipWidth = twidth;
         if ( sortColumn )
            clipWidth -= markSize;
         fl_color(FL_BLACK);
         fl_push_clip(xx, y, clipWidth, hh);
         fl_draw(column.name().c_str(), tx, y, twidth, hh, FL_ALIGN_LEFT );
         fl_pop_clip();
         if ( sortColumn ) {
            int yc = y + hh / 2;
            int xc = tx + twidth - markSize;
            if (sortAscending()) {
               fl_color(FL_DARK3);
               fl_line(xc+markSize/2,yc+markSize/2,xc,yc-markSize/2,xc+markSize,yc-markSize/2);
               fl_color(FL_LIGHT3);
               fl_line(xc+markSize/2,yc+markSize/2,xc+markSize,yc-markSize/2);
            } else {
               fl_color(FL_LIGHT3);
               fl_line(xc+markSize/2,yc-markSize/2,xc+markSize,yc+markSize/2,xc,yc+markSize/2);
               fl_color(FL_DARK3);
               fl_line(xc,yc+markSize/2,xc+markSize/2,yc-markSize/2);
            }
         }
         xx += width;
      }
   }
   draw_box(FL_FLAT_BOX, xx, y, x() + w()- xx + 10, hh, FL_BACKGROUND_COLOR);
   fl_pop_clip();
}

Fl_Color CListView::item_color(int,const CPackedStrings&) const {
   return m_textColor;
}

unsigned CListView::cellBorderWidth() const {
   int bwidth = 2;
   if (m_showGrid) bwidth = 4;
   return bwidth;
}

void CListView::item_draw(unsigned index, const CPackedStrings *item, int xx, int yy, int ww, int hh, int focusMode, int verticalAlign, bool paintBackground) const {
   bool selected = false;
   if (!item) {
      if ((int)index < 0)
         item = NULL;
      else
         item = m_rows[index];
   }
   if (item)
      selected = item->flags & CLV_SELECTED;
   Fl_Color    lcol = FL_BLACK;
   Fl_Color    boxcol = FL_LIGHT3;
   if (m_showStripes && (index & 0x1)) 
      boxcol = FL_LIGHT2;

   if (selected && m_showSelection) {
      switch (focusMode) {
         case -1:
            lcol = FL_BLACK;
            break;
         case 0:
            lcol = fl_contrast(lcol, selection_color());
            boxcol = selection_color();
            break;
         default: // Selected item for the listview
            lcol = fl_contrast(lcol, selection_color());
            boxcol = selection_color();
            break;
      }
   }

   //fl_push_clip(this->x(),yy,this->w(),hh);

   fl_font(m_textFont,m_textSize);
   unsigned cnt = m_columnList.size();
   if (item && (unsigned)item->size() < cnt)
      cnt = item->size();
   Fl_Align align = Fl_Align(FL_ALIGN_LEFT | verticalAlign);
   int borderWidth = cellBorderWidth();
   int x = xx;
   int xf = xx + borderWidth - 1;
   int maxX = xx + ww;

   if (paintBackground || boxcol != FL_LIGHT3) 
      draw_box(FL_FLAT_BOX, xx, yy, ww, hh, boxcol);

   for (unsigned i = 0; i < cnt; i++) {
      const CColumn&  column = m_columnList[i];
      if (column.visible()) {
         int xt = x + borderWidth - 1;
         int yt = yy + borderWidth - 1;
         int w1 = column.width();

         if (x + w1 > 0 && x <= maxX) {
         //if (x + w1 > 0) {
            if (m_showGrid) 
               draw_box(FL_THIN_DOWN_FRAME, x, yy, w1, hh, FL_GRAY);

            const char *str = "";
            if (item) str = (*item)[short(i)];

            int wt = w1 - borderWidth * 2;

            if ((kind() & (DCV_CHECKBUTTONS|DCV_RADIOBUTTONS)) && i == 0 ) {
               int bw = m_textSize;
               int xb = xt + bw;

               int yb = yt + bw;

               if (kind() == DCV_CHECKBUTTONS) {
                  draw_box(FL_DOWN_BOX, xt, yt, bw + 1, bw + 1, FL_WHITE);

                  if (selected) {
                     fl_color(FL_BLACK);
                     // left top to right bottom
                     fl_line(xt+3,yt+3,xb-3,yb-3);
                     fl_line(xt+3,yt+4,xb-4,yb-3);
                     fl_line(xt+4,yt+3,xb-3,yb-4);
                     // right top to left bottom
                     fl_line(xb-3,yt+3,xt+3,yb-3);
                     fl_line(xb-4,yt+3,xt+3,yb-4);
                     fl_line(xb-3,yt+4,xt+4,yb-3);
                  }
               } else if (kind() == DCV_RADIOBUTTONS) {
                  draw_box(FL_ROUND_DOWN_BOX, xt, yt, bw + 1, bw + 1, FL_WHITE);
                  if (selected) {
                     fl_color(FL_BLACK);
                     fl_pie(xt+4,yt+4,bw-7,bw-7,0,360);
                     fl_arc(xt+4,yt+4,bw-7,bw-7,0,360);
                  }
               }
               xt += bw + 3;
               wt -= bw + 3;
               xf = xt;
            }

            if (item) {
               fl_push_clip(xt,yy,wt,hh);
               if (column.type() == VAR_IMAGE_PTR) {
                  char *endptr;
#ifdef _WIN32
                  Fl_Image *image = (Fl_Image *)strtol(str,&endptr,16);
#else
                  Fl_Image *image = (Fl_Image *)(unsigned long)strtoll(str,&endptr,16);
#endif
                  if (image)
                     image->draw(xt, yy + 2, image->w(), image->h(), 0, 0);
               } else {
                  if (selected)
                     fl_color(lcol);
                  else  fl_color(item_color(i,*item));
                  fl_draw(str, xt, yy + 1, wt, hh, align, 0, 0);
               }
               fl_pop_clip();
            }
         }
         x += w1;
      }
   }

   // Draw the focus rect if necessary
   if (index == m_activeRow && Fl::focus() == (Fl_Widget *)this)
      draw_focus(FL_FLAT_BOX, xf - 1, yy+1, x - xf + 2, hh-1);

   int w = ww - x + this->x() + 10;
   if (w > 0)
      draw_box(FL_FLAT_BOX, x, yy, w, hh, boxcol);

   //fl_pop_clip();
}

void CListView::clear() {
   m_top = 0;
   m_horizPosition = m_realHorizPosition = 0;
   m_rows.clear();
   m_selection.clear();
   m_maxWidth = 0;
   redraw_lines();
}

void CListView::addRow(int rowId,const char *s1,const char *s2,const char *s3,const char *s4,const char *s5) {
   int count = 1;
   const char *strings[5];

   strings[0] = s1;

   if (s2) {
      strings[1] = s2;
      if (s3) {
         strings[2] = s3;
         if (s4) {
            strings[3] = s4;
            if (s5) {
               strings[4] = s5;
               count = 5;
            } else {
               count = 4;
            }
         } else {
            count = 3;
         }
      } else {
         count = 2;
      }
   }
   addRow(count,strings,rowId);
}

void CListView::addRow(int count,const char *ptr[],int rowId) {
   CPackedStrings *ps = new CPackedStrings(count,ptr);
   ps->argument(rowId);
   item_compute_height(ps);
   m_rows.add(ps);
}

void CListView::addRow(CPackedStrings *ptr) {
   item_compute_height(ptr);
   m_rows.add(ptr);
}

void CListView::addRow(const CStrings& ss,int ident) {
   CPackedStrings *packedStrings = new CPackedStrings(ss);
   if (ident)
      packedStrings->argument(ident);
   addRow(packedStrings);
}

void CListView::insertRow(unsigned position,CPackedStrings *ptr) {
   item_compute_height(ptr);
   m_rows.insert(position,ptr);
}

void CListView::insertRow(unsigned position,const CStrings& ss,int ident) {
   CPackedStrings *packedStrings = new CPackedStrings(ss);
   if (ident)
      packedStrings->argument(ident);
   insertRow(position,packedStrings);
}

void CListView::updateRow(unsigned position,CPackedStrings *ptr) {
   int oldh = item_height(position);
   CPackedStrings *row = m_rows[position];
   *row = *ptr;
   int dh = item_compute_height(row) - oldh;
   m_rows.m_fullHeight += dh;
}

void CListView::updateRow(unsigned position,const CStrings& ss,int ident) {
   int oldh = item_height(position);
   CPackedStrings *row = m_rows[position];
   *row = ss;
   if (ident)
      row->argument(ident);
   int dh = item_compute_height(row) - oldh;
   m_rows.m_fullHeight += dh;
}

CPackedStrings *CListView::row(unsigned line) const {
   return m_rows[line];
}

int CListView::selected(int line) const {
   const CPackedStrings *r = m_rows[line];
   if (r)
      return r->flags & CLV_SELECTED;
   else  return 0;
}

void CListView::show(int line) {
   CPackedStrings *t = m_rows[line];
   if (!t) return;
   if (t->flags & CLV_NOTDISPLAYED) {
      t->flags &= ~CLV_NOTDISPLAYED;
      m_rows.m_fullHeight += item_compute_height(t);
      if (displayed(line)) 
         redraw_lines();
   }
}

void CListView::hide(int line) {
   CPackedStrings *t = m_rows[line];
   if (!t) return;
   if (!(t->flags & CLV_NOTDISPLAYED)) {
      m_rows.m_fullHeight -= item_height(line);
      t->flags |= CLV_NOTDISPLAYED;
      if (displayed(line)) redraw_lines();
   }
}

int CListView::visible(int line) const {
   CPackedStrings *t = m_rows[line];
   if (!t) return false;
   return !(t->flags & CLV_NOTDISPLAYED);
}

std::string CListView::textValue() const {
   CPackedStrings& t = selectedRow();
   if (!&t) return "";
   return t[0];
}

void CListView::textValue(std::string tv) {
   unsigned cnt = m_rows.size();
   bool     dataWasChanged = false;
   void    *dataValue = 0;
   int      column = m_rows.sortColumn();
   if (column >= -1)
      for (unsigned line = 0; line < cnt; line++) {
         CPackedStrings *t = m_rows[line];
         bool wasSelected = t->flags & CLV_SELECTED;
         if ( (*t)[column] == tv ) {
            activate_row(line);
            dataWasChanged = true;
            dataValue = t->user_data();
         } else if (wasSelected) {
            t->flags &= ~CLV_SELECTED;
            dataWasChanged = true;
         }
      }
   if (dataWasChanged)
      fireEvent(CE_DATA_CHANGED,(long)dataValue);
   redraw();
}

CVariant CListView::data() const {
   CPackedStrings& t = selectedRow();
   if (!&t) {
      switch (m_dataMode) {
         case LV_DATA_KEY:
         case LV_DATA_INDEX:
            return 0;
         default: // LV_DATA_CAPTION, LV_DATA_UNDEFINED
            return "";
      }
   }
   switch (m_dataMode) {
      case LV_DATA_KEY:
         return t.argument();
      case LV_DATA_INDEX:
         return selectedIndex();
      default: // LV_DATA_CAPTION, LV_DATA_UNDEFINED
         return t[0];
   }
}

void CListView::data(const CVariant vv) {
   unsigned cnt = m_rows.size();
   void    *dataValue = 0;
   int      intValue = vv;
   unsigned line = 0; 
   unsigned oldSelectedCount = m_selection.size();
   CPackedStrings *oldSelectedRow = 0L;
   CPackedStrings *newSelectedRow = 0L;

   if (oldSelectedCount > 0)
      oldSelectedRow = &m_selection[0];

   switch (m_dataMode) {
      case LV_DATA_KEY:
         newSelectedRow = findKey(vv);
         break;
      case LV_DATA_INDEX: {
            line = intValue;
            if (line < cnt) {
               newSelectedRow = m_rows[line];
            }
         }
         break;
      default: // LV_DATA_CAPTION, LV_DATA_UNDEFINED
         newSelectedRow = findCaption(vv.asString()); 
         break;
   }

   m_selection.deselectAll();
   m_selection.select(newSelectedRow);
   bool dataWasChanged = false;
   if ((newSelectedRow && newSelectedRow != oldSelectedRow) || oldSelectedCount > 1) {
      dataWasChanged = true;
      displayRow(selectedIndex());
      dataValue = newSelectedRow->user_data();
   }
   if (dataWasChanged)
      fireEvent(CE_DATA_CHANGED,(long)dataValue);
   redraw();
}

void CListView::showGrid(bool flag) {
   if (m_showGrid != flag) {
      m_showGrid = flag;
      if (m_showGrid)
         m_showStripes = false;
      redraw();
   }
}

void CListView::showStripes(bool flag) {
   if (m_showStripes != flag) {
      m_showStripes = flag;
      if (m_showStripes)
         m_showGrid = false;
      redraw();
   }
}

void CListView::columns(const CColumnList& _columns) {
   m_columnList = _columns;
}

int CListView::columnAt(int xm) {
   unsigned xx = 0;
   m_draggingColumn = -1;
   for (unsigned col = 0; col < m_columnList.size(); col++) {
      int leftDistance = xm - xx;
      xx += m_columnList[col].width();
      int rightDistance = xx - xm;
      if (rightDistance < 0) continue;
      if (leftDistance < rightDistance) {
         if (leftDistance < 4) {
            m_draggingColumn = col-1;
            fl_cursor(FL_CURSOR_WE);
            return -1;
         }
         return col;
      }

      if (leftDistance > rightDistance) {
         if (rightDistance < 4) {
            m_draggingColumn = col;
            fl_cursor(FL_CURSOR_WE);
            return -1;
         }
         return col;
      }
   }

   m_draggingStarted = xm;

   if (m_draggingColumn >= 0)
      fl_cursor(FL_CURSOR_WE);

   return -1;
}

void CListView::header_clicked(int x) {
   int column = columnAt(x);
   if (column >= 0) {
      if (sortColumn() == column) {
         // Same sort column clicked, reversing the sort order
         sortAscending(!sortAscending(),true);
      } else {
         // Sort the column ascending
         sortColumn(column,false);
         sortAscending(true,true);
      }
   }
}

bool CListView::header_drag_position(int x) {
   int xx = 0;
   for (unsigned c = 0; c < m_columnList.size(); c++) {
      xx += m_columnList[c].width();
      if (abs(x - xx) < 3)
         return true;
      if (x < xx) break;
   }
   return false;
}

void CListView::header_dragged(int xm) {
   if (m_draggingColumn < 0) return;
   int dx = xm - m_mousePressedX;
   CColumn&    column = m_columnList[m_draggingColumn];
   int newWidth = column.width() + dx;
   if (newWidth < 1) newWidth = 1;
   column.width(short(newWidth));
   m_mousePressedX = xm;
   damage(FL_DAMAGE_SCROLL);
}

void CListView::header_released(int) {
   m_draggingColumn = -1;
}

void CListView::key_changed(unsigned index) {
   CPackedStrings *t = m_rows[index];
   int keyValue = 0;
   if (t) keyValue = t->argument();
   fireEvent(CE_DATA_CHANGED,keyValue);
}

// loading the key value in data entry dialog
void CListView::load(CQuery *loadQuery) {
   CQuery& query = *loadQuery;
   if (!m_fieldName.length()) return;
   CField& fld = query[m_fieldName.c_str()];
   data( fld );
}

// storing the key value in data entry dialog
void CListView::save(CQuery *updateQuery) {
   if (!m_fieldName.length()) return;
   CParam& param = updateQuery->param(m_fieldName);
   param = data();
}

void CListView::item_clicked(int) {

}

int CListView::find_id(int id) const {
   unsigned cnt = m_rows.size();
   for (unsigned i = 0; i < cnt; i++) {
      CPackedStrings *r = m_rows[i];
      if (id == r->argument())
         return i;
   }
   return -1;
}

void CListView::getSelections(CIntList& sel) const {
   sel.clear();
   if (!m_multipleSelection) {
      sel.push_back( data() );
   } else {
      unsigned cnt = m_rows.size();
      for (unsigned i = 0; i < cnt; i++) {
         CPackedStrings *r = m_rows[i];
         if (r->flags & CLV_SELECTED)
            sel.push_back( r->argument() );
      }
   }
}

void CListView::setSelections(const CIntList& sel) {
   unsigned scnt = sel.size();
   if (scnt)
      data( sel[0] );
   else  data( -1 );

   if (!m_multipleSelection) return;

   for (unsigned si = 1; si < scnt; si++) {
      int selectedKey = sel[si];
      unsigned cnt = m_rows.size();
      for (unsigned i = 0; i < cnt; i++) {
         CPackedStrings *r = m_rows[i];
         if ( r->argument() == selectedKey) {
            r->flags |= CLV_SELECTED;
            continue;
         }
      }
   }
}

void CListView::autoRowHeight(bool arh) {
   if (arh != m_autoRowHeight) {
      m_autoRowHeight = arh;
      item_compute_height_all();
      redraw_lines();
   }
}

int CListView::findString(std::string str,bool findAndSelect,unsigned startRow,unsigned endRow) {
   unsigned cnt = m_rows.size();
   if (startRow >= cnt) startRow = 0;
   if (endRow <= startRow || endRow >= cnt) endRow = cnt - 1;

   int col = m_rows.sortColumn();
   if (col < 0) col = 0;
   std::string searchStr = lowerCase(str);
   for (int i = startRow; i <= (int)endRow; i++) {
      CPackedStrings *row = m_rows[i];
      std::string currentStr( (*row)[col] );
      int pos = lowerCase(currentStr).find(searchStr.c_str());
      if (pos >= 0) {
         if (findAndSelect) {
            select_only(i);
            m_activeRow = i;
         }
         return i;
      }
   }
   return -1;
}

void CListView::top(int t) {
   m_top = t;
   redraw();
}

void CListView::preferredSize(int& w,int& h) {
   if (w < 100) w = 100;
   if (h < 100) h = 100;
}

void CListView::fill(CDataSource &ds,std::string keyFieldName,unsigned recordsLimit,unsigned recordsEstimated,CRefreshKind refreshKind) {

   m_fillTerminated = false;
   if (m_fillInProgress) {
      m_fillTerminated = true;
      return;
   }

   do {
      m_fillTerminated = false;
      m_fillInProgress = true;

      fl_cursor(FL_CURSOR_WAIT);
      Fl::check();

      try {
         ds.open();

         unsigned fieldCount = ds.fieldCount();
         unsigned keyField = 9999;

         for (unsigned i = 0; i < fieldCount; i++) {
            if (ds[i].fieldName() == keyFieldName) {
               keyField = i;
               break;
            }
         }

         if (keyField == 9999) {
            // if keyFieldName is not defined (find the first integer field and use it)
            for (unsigned i = 0; i < fieldCount; i++) {
               if (ds[i].dataType() == VAR_INT) {
                  keyField = i;
                  break;
               }
            }
         }

         CColumnList    newColumns;
         CColumnList&   oldColumns = columns();
         int colNumber = 0;
         for (unsigned f = 0; f < ds.fieldCount(); f++) {
            if (f == keyField) continue;
            CField&  field = ds[f];
            std::string  columnName = field.fieldName();
            bool cvisible = columnName[0] != '_';
            columnName = replaceAll(columnName,"_"," ");
            if (m_capitalizeColumnNames)
               columnName = capitalizeWords(columnName);
            short cwidth = short(field.width + 1);
            CVariantType ctype = field.dataType();
            switch (ctype) {
               case VAR_INT:
                  cwidth = 12;
                  break;

               case VAR_FLOAT:
                  cwidth = 16;
                  break;

               case VAR_DATE_TIME:
                  cwidth = 24;
                  break;

               case VAR_DATE:
                  cwidth = 14;
                  break;

               default:
                  break;
            }
            short maxcw = short(columnName.length() + 1);
            if (cwidth < maxcw)
               cwidth = maxcw;
            if (cwidth > 50) cwidth = 50;
            cwidth = short(cwidth * 7);

            int colIndex = oldColumns.indexOf(columnName.c_str());
            if (colIndex < 0)
               newColumns.push_back(CColumn(columnName,ctype,cwidth,cvisible));
            else {
               CColumn& existingColumn = oldColumns[colIndex];
               newColumns.push_back(existingColumn);
            }

            colNumber++;
         }

         columns(newColumns);

         // Memorizing selected records IDs, if the keyField is defined
         unsigned selectedCount = 0;
         int *selectedIDs = 0L;
         if (keyField < 9999) {
            selectedCount = m_selection.size();

            selectedIDs = new int[selectedCount];
            for (unsigned i = 0; i < selectedCount; i++)
               selectedIDs[i] = m_selection[i].argument();
         }
         m_selection.clear();

         std::string  errorText;
         unsigned recordCount = 0;
         unsigned lastProgression = 0;
         if (fieldCount > 0) {
            fireEvent(CE_PROGRESS,0);

            CStrings rowStrings;
            rowStrings.resize(fieldCount);

            while (!ds.eof()) {

               unsigned listSize = m_rows.size();

               int keyValue = 0;
               int j = 0;
               for (unsigned i = 0; i < fieldCount; i++) {
                  CField& field = ds[i];
                  if (i == keyField) {
                     keyValue = field.asInteger();
                     rowStrings.argument(keyValue);
                  } else {
                     rowStrings[j] = field.asString();
                     j++;
                  }
               }

               int rowNumber;
               if (refreshKind == LV_REFRESH_FULL) {
                  rowNumber = recordCount;
                  if (recordCount >= listSize) {
                     addRow(new CPackedStrings(rowStrings));
                  } else {
                     CPackedStrings *row = m_rows[rowNumber];
                     int oldh = row->height;
                     *row = rowStrings;
                     int newh = item_compute_height(row);
                     m_rows.m_fullHeight += newh - oldh;
                  }
               } else {
                  rowNumber = find_id(keyValue);
                  if (rowNumber < 0) {
                     addRow(new CPackedStrings(rowStrings));
                  } else {
                     updateRow(rowNumber,rowStrings,keyValue);
                  }
               }

               recordCount++;
               if (recordsLimit && recordCount >= recordsLimit) {
                  break;
               }

               if (recordsEstimated) {
                  unsigned newProgression = (recordCount*20/recordsEstimated) * 5;
                  if (newProgression != lastProgression) {
                     fireEvent(CE_PROGRESS,newProgression);
                     lastProgression = newProgression;
                  }
               }

               if (m_fillTerminated) {
                  puts("Terminating the fill()");
                  break;
               }

               try {
                  ds.next();
               }
               catch (exception& e) {
                  ds.close();
                  throw;
               }
            }
         }
         ds.close();

         if (refreshKind != LV_REFRESH_FULL)
            recordCount = m_rows.size();

         fireEvent(CE_PROGRESS,100);

         // truncate and sort rows
         m_rows.truncate(recordCount);

         // Select previously selected rows
         for (unsigned selectionIndex = 0; selectionIndex < selectedCount; selectionIndex++) {
            int keyValue = selectedIDs[selectionIndex];
            int rowNumber = find_id(keyValue);
            if (rowNumber >= 0)
               m_selection.select(m_rows[rowNumber]);
         }

         if (selectedIDs) delete selectedIDs;

         // check if we have any selection in SELECT_BROWSER
         if (!m_multipleSelection && !m_selection.size()) {
            select(0,1);
         }

         if (unsigned(m_rows.sortColumn()) < columns().size()) 
            m_rows.sort();
      }
      catch (...) {
         m_fillInProgress = false;
         ds.close();
         fl_cursor(FL_CURSOR_DEFAULT);
         Fl::check();
         redraw();
         throw;
      }
   }
   while (m_fillTerminated);

   fl_cursor(FL_CURSOR_DEFAULT);
   Fl::check();

   m_fillInProgress = false;

   redraw();
}

int CListView::selectedIndex() const {
   CPackedStrings *row = &selectedRow();
   return m_rows.indexOf(row);
}

bool CListView::select_only(unsigned index, bool docallbacks) {
   CPackedStrings *row = m_rows[index];
   bool change = (row->flags & CLV_SELECTED == 0);
   m_selection.deselectAll();
   if (select(index, 1, docallbacks))
      change = true;
   m_activeRow = index;
   displayRow(index);
   if (change)
      key_changed(index);

   return change;
}

// translate the current keystroke into up/down/left/right for navigation:
#define ctrl(x) (x^0x40)
static int navkey() {
   switch (Fl::event_key()) {
      case 0: // not an FL_KEYBOARD/FL_SHORTCUT event
         break;
      case FL_Tab:
         if (!Fl::event_state(FL_SHIFT)) return FL_Right;
      case 0xfe20: // XK_ISO_Left_Tab
         return FL_Left;
      case FL_Right:
      case FL_Left:
      case FL_Up:
      case FL_Down:
      case FL_Page_Up:
      case FL_Page_Down:
      case FL_Home:         
      case FL_End:         
         return Fl::event_key();
      default:
         switch (Fl::event_text()[0]) {
            case ctrl('N') : return FL_Down;

            case ctrl('P') : return FL_Up;
            case ctrl('F') : return FL_Right;
            case ctrl('B') : return FL_Left;
         }
   }
   return 0;
}

static int charkey(unsigned ch) {
   if (ch >= 32 && ch <= 254)
      return ch;
   else  return 0;
}

int CListView::get_focus() {
   if (contains(Fl::focus())) return 1;
   m_searchPhrase = "";
   Fl::focus(this);
   return 1;
}

int CListView::handle(int event) {
   switch (event) {
      case FL_FOCUS:
         if (navkey()) {
            int rc = get_focus();
            if (!m_hasFocus) {
               m_hasFocus = true;
               fireEvent(CE_FOCUS,0);
            }
            return rc;
         }
         redraw();
         break;

      case FL_UNFOCUS:
         fireEvent(CE_UNFOCUS,0);
         m_hasFocus = false;
         redraw();
         break;

      case FL_SHORTCUT:
      case FL_KEYBOARD:
         if (navkey()) {
            //int l = m_activeRow;
            switch (Fl::event_key()) {
               case FL_Down:
                  m_searchPhrase = "";
                  return select_next();
               case FL_Page_Down:
                  m_searchPhrase = "";
                  return select_next_page();
               case FL_Up:
                  m_searchPhrase = "";
                  return select_prior();
               case FL_Page_Up:
                  m_searchPhrase = "";
                  return select_prior_page();
               case FL_Home:
                  m_searchPhrase = "";
                  return select_first();
               case FL_End:
                  m_searchPhrase = "";
                  return select_last();
            }
         } else {
            unsigned ch = Fl::event_key();
            if (m_multipleSelection) {
               if ( ch == ' ')
                  select(m_activeRow, !item_selected(m_activeRow), 1);
               return 1;
            }

            // Type-in search
            unsigned ln = m_searchPhrase.length();
            switch (ch) {
               case FL_BackSpace:
                  if (ln)
                     m_searchPhrase[ln - 1] = 0;
                  break;
               case FL_Escape:
                  m_searchPhrase = "";
                  return 1;
            }

            char chr = charkey(ch);
            if (chr) {
               std::string newSearchString = m_searchPhrase + chr;
               int found = findString(newSearchString);
               if (found != -1) {
                  m_searchPhrase = newSearchString;
                  m_activeRow = found;
               }
            }
            return 1;
         }
   }

   //int rc = Fl_Group::handle(event);
   //if (event != FL_MOVE) return rc;
   int X, Y, W, H; bbox(X, Y, W, H);
   int my;
   unsigned l;
   static char change;
   static char whichway;
   static int py;

   switch (event) {

      case FL_MOVE:
         if (Fl::event_inside(X, Y-m_headerHeight, W, m_headerHeight)) {
            if (header_drag_position(Fl::event_x()+m_horizPosition-x())) {
               fl_cursor(FL_CURSOR_WE,FL_BLACK,FL_WHITE);
               break;
            }
         } 
         fl_cursor(FL_CURSOR_DEFAULT,FL_BLACK,FL_WHITE);
         return 1;

      case FL_PUSH:
         get_focus();

         if (!m_hasFocus) {
            m_hasFocus = true;
            fireEvent(CE_FOCUS,0);
         }

         m_mousePressedX = Fl::event_x()+m_horizPosition-x();
         if (!Fl::event_inside(X, Y, W, H)) {
            if (Fl::event_inside(X, Y-m_headerHeight, W, m_headerHeight)) {
               header_clicked(Fl::event_x()+m_horizPosition-x());
               return 1;
            }
            break;
         }
         if (!m_multipleSelection)
            m_selection.deselectAll();
         my = py = Fl::event_y();
         change = 0;
         l = find_item(my);
         if (m_multipleSelection && (Fl::event_state()&(FL_CTRL|FL_SHIFT))!=0) {
            if (Fl::event_state()&FL_CTRL) { // Ctrl pressed
               if (l < unsigned(-1)) {
                  whichway = !item_selected(l);
                  change = select(l, whichway != 0, when() & FL_WHEN_CHANGED);
                  m_activeRow = l;
               }
            } else { // Shift pressed
               if (m_selection.size()) {
                  CPackedStrings *ps = &m_selection[m_selection.size()-1];
                  int index = m_rows.indexOf(ps);
                  m_selection.deselectAll();
                  int itemClicked = l;
                  if (index < itemClicked) {
                     int t = index;
                     index = itemClicked;
                     itemClicked = t;
                  }
                  if (itemClicked < 0) itemClicked = 0;
                  if (index >= (int)m_rows.size()) index = size() - 1;
                  for (int i = itemClicked; i <= index; i++) 
                     m_selection.select(m_rows[i]);
                  change = true;
                  redraw();
               } else {
                  change = select_only(l, when() & FL_WHEN_CHANGED);
               }
            }
         } else {
            change = select_only(l, when() & FL_WHEN_CHANGED);
         }
         if (l < size())
            item_clicked(Fl::event_key()-FL_Button);

         if (Fl::event_clicks())
            fireEvent(CE_MOUSE_DOUBLE_CLICK,0);
         else fireEvent(CE_MOUSE_CLICK,0);

         break;

      case FL_DRAG:
         if (Fl::event_inside(X, Y-m_headerHeight, W, m_headerHeight)) {
            header_dragged(Fl::event_x()+m_horizPosition-x());
            return 1;
         }
         // do the scrolling first:
         my = Fl::event_y();
         if (my < Y && my < py) {
            /* broken - ASP
            int p = m_realPosition+my-Y;
            if (p<0) p = 0;
            position(p);
            */
         } else if (my > (Y+H) && my > py) {
            /* broken - ASP
            int p = m_realPosition+my-(Y+H);
            int h = full_height()-H; if (p > h) p = h;
            if (p<0) p = 0;
            position(p);
            */
         }
         break;

      case FL_RELEASE:
         if (Fl::event_inside(X, Y-m_headerHeight, W, m_headerHeight)) {
            header_released(Fl::event_x()+m_horizPosition-x());
            fl_cursor(FL_CURSOR_DEFAULT);
            return 1;
         }
         if (change) {
            if (when() & FL_WHEN_RELEASE)
               fireEvent(CE_MOUSE_RELEASE,0);
            else
               if (!(when()&FL_WHEN_CHANGED))
                  set_changed();
         }
         //return 1;
         break;
   }

   return Fl_Group::handle(event);
}

bool CListView::select_first() {
   if ( size() == 0 ) return false;
   m_top = 0;
   return activate_row(0);
}

bool CListView::select_last() {
   if ( size() == 0 ) return false;
   return activate_row(size()-1);
}

bool CListView::select_next() {
   int l = m_activeRow;

   // find next row to activate
   int newActiveRow = -1;
   while ( (++l) < (int)size() )
      if (item_height(l)>0) {
         newActiveRow = l;
         break;
      }

   if (newActiveRow < 0) return false;

   unsigned cnt = size();
   for (; m_top < cnt; m_top++) {
      if (!item_height(m_top)) continue;
      if (displayed(newActiveRow))
         break;
   }

   return activate_row(newActiveRow);
}

bool CListView::select_prior() {
   int l = m_activeRow;

   // find next row to activate
   int newActiveRow = -1;
   while ( (--l) >= 0)
      if (item_height(l)>0) {
         newActiveRow = l;
         break;
      }

   if (newActiveRow < 0) return false;

   for (int newTop = m_top; newTop >= 0; newTop--) {
      m_top = newTop;
      if (!item_height(m_top)) continue;
      if (displayed(newActiveRow))
         break;
   }

   return activate_row(newActiveRow);
}

bool CListView::select_next_page() {
   int row;
   unsigned cnt = size();

   int X,Y,W,H;
   int hh = 0;
   bbox(X,Y,W,H);

   // find the new active row
   unsigned newActiveRow = m_activeRow;
   for (row = m_activeRow; row < (int)cnt; row++) {
      hh += item_height(row);
      if (hh >= H)
         break;
      newActiveRow = row;
   }

   if (newActiveRow == m_activeRow) return false;

   // find the new top
   hh = 0;
   int newTop = 0;
   for (row = newActiveRow; row >= 0; row--) {
      hh += item_height(row);
      if (hh >= H)
         break;
      newTop = row;
   }
   m_top = newTop;

   return activate_row(newActiveRow);
}

bool CListView::select_prior_page() {
   int row;

   int X,Y,W,H;
   int hh = 0;
   bbox(X,Y,W,H);

   // find the new active row
   unsigned newActiveRow = m_activeRow;
   for (row = m_activeRow; row >= 0; row--) {
      hh += item_height(row);
      if (hh >= H)
         break;
      newActiveRow = row;
   }

   if (newActiveRow == m_activeRow) return false;

   m_top = newActiveRow;

   return activate_row(newActiveRow);
}

int CListView::find_item(int my) {
   int X, Y, W, H;
   bbox(X, Y, W, H);
   int yy = Y;
   int maxl = size();
   for (int l = m_top; l < maxl; l++) {
      int hh = item_height(l);
      if (hh <= 0)
         continue;
      yy += hh;
      if (my <= yy || yy>=(Y+H))
         return l;
   }
   return 0;
}

// Tell whether item is currently displayed:
bool CListView::displayed(unsigned index) const {
   int X, Y, W, H;
   bbox(X, Y, W, H);
   int yy = H;
   unsigned  maxl = size();
   for (unsigned l = m_top; l < maxl && yy > 0; l++) {
      yy -= item_height(l);
      if (l == index)
         return yy >= 0;
   }
   return 0;
}

unsigned CListView::fullHeight() {
   return m_rows.fullHeight() + m_headerHeight + cellBorderWidth() * 2;
}

unsigned CListView::fullWidth() const {
   unsigned cnt = m_columnList.size();
   unsigned ww = 0;
   for (unsigned i = 0; i < cnt; i++)
      if (m_columnList[i].visible())
         ww += m_columnList[i].width();
   return ww;
}

bool CListView::select(unsigned index, bool i, int docallbacks) {
   if (index >= size()) return 0;
   bool sel = item_selected(index);
   if (m_multipleSelection) {
      item_select(index, i);
      redraw_line(index);
   } else {
      if (i == sel) return false;

      if (i)
         m_selection.deselectAll();
      item_select(index, i);
      if (i)
         displayRow(index);
      redraw();
   }

   if (docallbacks)
      fireEvent(CE_DATA_CHANGED,0);

   return true;
}

// return where to draw the actual box:
void CListView::bbox(int& X, int& Y, int& W, int& H) const {
   Fl_Boxtype b = box() ? box() : FL_DOWN_BOX;
   X = x()+Fl::box_dx(b);
   Y = y()+Fl::box_dy(b)+m_headerHeight;
   W = w()-Fl::box_dw(b);
   H = h()-Fl::box_dh(b)-m_headerHeight;
   if (scrollbar.visible())  W -= m_scrollbarWidth;
   if (hscrollbar.visible()) H -= m_scrollbarWidth;
   if (H < 0) H = 0;
   if (W < 0) W = 0;
}

bool CListView::activate_row(unsigned newActiveRow) {
   if (newActiveRow < size()){
      if (m_activeRow != newActiveRow) {
         select_only(newActiveRow, 1);
         return true;
      }
   }
   return false;
}

CPackedStrings& CListView::selectedRow() const {
   if (m_selection.size()) {
      CPackedStrings& ps = m_selection[0];
      return ps;
   }
   return *(CPackedStrings *)0L;
}

CColumn& CListView::column(const char *colname)   {
   int index = m_columnList.indexOf(colname);
   if (index >= 0)
      return m_columnList[index]; 
   throw CException("Column '" + std::string(colname) + "' is not found.");
}

CPackedStrings *CListView::findCaption(std::string caption) {
   CPackedStrings *row = m_selection.findCaption(caption);
   if (row) 
      return row;

   unsigned cnt = m_rows.size();
   for (unsigned i =0; i < cnt; i++) {
      CPackedStrings *row = m_rows[i];
      if (strcmp((*row)[0],caption.c_str()) == 0)
         return row;
   }
   return 0L;
}

CPackedStrings *CListView::findKey(int keyValue) {
   CPackedStrings *row = m_selection.findKey(keyValue);
   if (row) 
      return row;

   unsigned cnt = m_rows.size();
   for (unsigned i = 0; i < cnt; i++) {
      CPackedStrings *row = m_rows[i];
      if (row->argument() == keyValue)
         return row;
   }
   return 0L;
}
