/* fieldlistview.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2002-2025 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "fieldlistview.h"
#include <stdlib.h>
#include "awindow.h"
#include "guielement.h"
#include "drawablecont.hh"

const char *FieldListView::type="FieldListView";

unsigned int FieldListView::_select_button = Button1, FieldListView::_select_buttonmask = Button1Mask;
unsigned int FieldListView::_activate_button = Button2, FieldListView::_activate_buttonmask = Button2Mask;
unsigned int FieldListView::_activate_mod = 0;
unsigned int FieldListView::_scroll_button = Button3, FieldListView::_scroll_buttonmask = Button3Mask;
unsigned int FieldListView::_scroll_mod = 0;
unsigned int FieldListView::_entry_pressed_msg_button = Button3,
             FieldListView::_entry_pressed_msg_buttonmask = Button3Mask,
             FieldListView::_entry_pressed_msg_mod = ShiftMask;
FieldListView::mouse_select_t FieldListView::_select_mode = FieldListView::MOUSE_SELECT_NORMAL;

unsigned int FieldListView::s_hscroll_left_button = 6;
unsigned int FieldListView::s_hscroll_right_button = 7;

FieldListView::ColorDef FieldListView::s_default_element_colors;

bool FieldListView::s_enable_delayed_entry_pressed_event = false;

const static int s_pan_prepare_delta = 10;
const static double s_pan_prepare_deadzone_percent = 0.05;

FieldListView::FieldListView( AGUIX *caguix, int cx, int cy, int cwidth, int cheight, int cdata ):GUIElement( caguix )
{
  int i;

  fields = 1;
  fieldwidth = new int[fields];
  for ( i = 0; i < fields; i++ )
    fieldwidth[i] = -1;
  used_width = new int[fields];
  for ( i = 0; i < fields; i++ )
    used_width[i] = 0;
  maxrow = new int[fields];
  for ( i = 0; i < fields; i++ )
    maxrow[i] = -1;

  elements = 0;
  arraysize = 100;
  elementarray = new Content*[arraysize];
  yoffset = 0;
  xoffset = 0;

  fieldalign = new align_t[fields];
  for ( i = 0; i < fields; i++ )
    fieldalign[i] = ALIGN_LEFT;

  fieldtext = new std::string[fields];
  for ( i = 0; i < fields; i++ )
    fieldtext[i] = "";

  fieldtextmerged = new bool[fields];
  for ( i = 0; i < fields; i++ )
    fieldtextmerged[i] = false;

  fieldtextResizeable = new bool[fields];
  for ( i = 0; i < fields; i++ )
    fieldtextResizeable[i] = false;

  fieldtextclicked = -1;

  activerow = -1;

  this->data = cdata;
  _x = cx;
  _y = cy;
  if ( cwidth > 6 )
    _w = cwidth;
  else
    _w = 6;
  if ( cheight > 6 )
    _h = cheight;
  else
    _h = 6;

  hbar = 1;
  vbar = 1;
  vbar_width = 12;
  hbar_height = 12;
  hbar_dir = 0;
  vbar_dir = 0;
  vbar_pressed = false;
  hbar_pressed = false;
  hbarwin = 0;
  vbarwin = 0;
  selecthandler=NULL;
  font = NULL;
  gettimeofday( &lastwheel, NULL );

  mbg = _aguix->getFaces().getColor( "default-bg" );
  if ( mbg < 0 ) {
      mbg = 0;
  }

  elementHeight = 0;
  ox = 0;
  oy = 0;

  buffer = 0;

  _header_pixmap = None;
  _header_pixmap_w = _header_pixmap_h = -1;

  displayFocus = false;
  setCanHandleFocus();

  cond_changed = false;
  showHeader = false;

  vbarMode = VBAR_IDLE;
  hbarMode = HBAR_IDLE;
  grabs = 0;
  scrollDelta = 0;
  thirdMode = THIRD_IDLE;
  scrollXDir = 0;
  scrollYDir = 0;
  selectMode = SELECT_IDLE;
  clickLastSelectedRow = clickFirstSelectedRow = -1;
  clickState = false;
  clickSelected = 0;
  middleMode = MIDDLE_IDLE;
  headerMode = HEADER_IDLE;

  headerCols[0] = _aguix->getFaces().getColor( "default-fg" );
  if ( headerCols[0] < 0 ) {
      headerCols[0] = 1;
  }

  headerCols[1] = mbg;

  drag_base_x = drag_base_w = 0;
  last_drag_clicked = -1;
  last_drag_time = 0;
  drag_field = -1;

  header_cursor_set = false;
  
  _last_single_select_row = -1;
  _last_single_select_time = 0;
  _last_single_middle_row = -1;
  _last_single_middle_time = 0;

  m_complete_update = false;

  gettimeofday( &m_last_update_time, NULL );

  m_global_field_spacing = 0;

  m_consider_header_for_dynamic_width = false;

  m_header_text_width.resize( fields );
  m_field_spacing.resize( fields, -1 );
  m_hide_empty_field.resize( fields );

  m_default_color_mode = PRECOLOR_NONE;

  initStaticDefaultColors();
}

FieldListView::~FieldListView()
{
  int i;

  destroy();
  
  delete [] fieldwidth;
  delete [] used_width;
  delete [] fieldalign;
  delete [] fieldtext;
  delete [] fieldtextmerged;
  delete [] fieldtextResizeable;
  for ( i = 0; i < elements; i++ ) {
    delete elementarray[i];
  }
  delete [] elementarray;
  delete [] maxrow;
}

FieldListView::Content::Content()
{
  int i;

  cur_fields = 1;
  texts = new std::string*[cur_fields];
  m_highlight_segments = NULL;
  for ( i = 0; i < cur_fields; i++)
    texts[i] = new std::string("");

  // default colors
  fg[ 0 ] = s_default_element_colors.getFG( CC_NORMAL );
  fg[ 1 ] = s_default_element_colors.getFG( CC_SELECT );
  fg[ 2 ] = s_default_element_colors.getFG( CC_ACTIVE );
  fg[ 3 ] = s_default_element_colors.getFG( CC_SELACT );
  bg[ 0 ] = s_default_element_colors.getBG( CC_NORMAL );
  bg[ 1 ] = s_default_element_colors.getBG( CC_SELECT );
  bg[ 2 ] = s_default_element_colors.getBG( CC_ACTIVE );
  bg[ 3 ] = s_default_element_colors.getBG( CC_SELACT );

  select = false;
  data = 0;
  dataExt = NULL;
  mark = false;

  m_ondemand_data_available = false;
}

FieldListView::Content::~Content()
{
  int i;

  for ( i = 0; i < cur_fields; i++)
    delete texts[i];
  delete [] texts;

  delete m_highlight_segments;
  
  delete dataExt;
}

void FieldListView::Content::setText( int field, std::string text )
{
  if ( field < 0 ) return;
  if ( field >= cur_fields ) trim( field + 1 );
  *texts[ field ] = text;
}

void FieldListView::Content::setStrikeOut( int field, bool nv )
{
    if ( field < 0 ) return;
    if ( field >= cur_fields ) trim( field + 1 );

    if ( field >= (int)m_strike_out.size() ) {
        m_strike_out.resize( field + 1 );
    }

    m_strike_out.at( field ) = nv;
}

bool FieldListView::Content::getStrikeOut( int field ) const
{
    if ( field >= (int)m_strike_out.size() ) return false;
    return m_strike_out.at( field );
}

void FieldListView::Content::setStrikeOutStart( int field, size_t nv )
{
    if ( field < 0 ) return;
    if ( field >= cur_fields ) trim( field + 1 );

    if ( field >= (int)m_strike_out_start.size() ) {
        m_strike_out_start.resize( field + 1 );
    }

    m_strike_out_start.at( field ) = nv;
}

size_t FieldListView::Content::getStrikeOutStart( int field ) const
{
    if ( field >= (int)m_strike_out_start.size() ) return 0;
    return m_strike_out_start.at( field );
}

void FieldListView::Content::trim( int newfields )
{
  std::string **nt;
  int i;

  if ( newfields < 1 ) return;
  if ( newfields == cur_fields ) return;

  nt = new std::string*[ newfields ];
  if ( newfields < cur_fields ) {
    for ( i = 0; i < newfields; i++ ) {
      nt[i] = texts[i];
    }
    for ( i = newfields; i < cur_fields; i++ ) {
      delete texts[i];
    }
  } else {
    for ( i = 0; i < cur_fields; i++ ) {
      nt[i] = texts[i];
    }
    for ( i = cur_fields; i < newfields; i++ ) {
      nt[i] = new std::string( "" );
    }
  }
  cur_fields = newfields;
  delete [] texts;
  texts = nt;

  if ( m_highlight_segments ) {
      m_highlight_segments->resize( cur_fields );
  }
}

std::string FieldListView::Content::getText( int field ) const
{
  if ( ( field < 0 ) || ( field >= cur_fields ) ) return "";
  return *texts[field];

}

void FieldListView::Content::setHighlightSegments( int field, const std::vector< size_t > &segments )
{
    if ( field < 0 ) return;
    if ( field >= cur_fields ) trim( field + 1 );

    if ( ! m_highlight_segments ) {
        m_highlight_segments = new std::vector< std::vector< size_t > >();
        m_highlight_segments->resize( cur_fields );
    }

    m_highlight_segments->at( field ) = segments;
}

const std::vector< size_t > *FieldListView::Content::getHighlightSegments( int field ) const
{
    if ( ! m_highlight_segments ) return NULL;
    if ( field < 0 || field >= cur_fields ) return NULL;
    return &m_highlight_segments->at( field );
}

void FieldListView::Content::clearHighlightSegments()
{
    if ( m_highlight_segments ) {
        delete m_highlight_segments;
        m_highlight_segments = NULL;
    }
}

void FieldListView::setFieldWidthQ( int field, int width )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;

  if ( width < 0 ) {
    width = -1;
    used_width[ field ] = -1;
    maxrow[ field ] = -1;
  }
  fieldwidth[ field ] = width;
}

void FieldListView::setFieldWidth( int field, int width )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  setFieldWidthQ( field, width );

  redraw();
}

/*
 * setText
 *
 * sets the text for the specified row and field
 *
 * this will not redraw the lv because of the performance
 */
void FieldListView::setText( int row, int field, std::string text, bool update_simple_set )
{
  int tl;

  if ( ( row < 0 ) || ( row >= elements ) ) return;
  if ( field < 0 ) return;

  // make changes to te
  
  // test if field already exists
  if ( field >= fields ) {
    // increase fieldwidth
    setNrOfFields( field + 1 );
  }

  // test if new text will change dynamic width
  if ( fieldwidth[ field ] < 0 ) {
    // dynamic field width
    if ( used_width[ field ] >= 0 ) {
      // only change if not unknown

      tl = _aguix->getTextWidth( text.c_str(), font );

      if ( tl > used_width[ field ] ) {
        // text is longer so only change used_width
        used_width[ field ] = tl;
	maxrow[ field ] = row;
	cond_changed = true;
        m_complete_update = true;
      } else if ( tl < used_width[ field ] ) {
        // text is shorter
	if ( ( maxrow[ field ] < 0 ) || ( maxrow[ field ] == row ) ) {
	  // we don't know the max row or we are the max row
	  // we don't know new width so mark it as unknown
	  used_width[ field ] = -1;
	  maxrow[ field ] = -1;
	  cond_changed = true;
          m_complete_update = true;
	}
      }
    }
  }
  elementarray[ row ]->setText( field, text );
  if ( isRowVisible( row ) == true ) {
      cond_changed = true;

      if ( update_simple_set == true ) {
          m_updated_rows.insert( row );
      }
  }
}

void FieldListView::increaseArray( int newsize )
{
  Content **newarray;
  int i;

  if ( newsize < arraysize ) return;

  newarray = new Content*[newsize];
  for ( i = 0; i < elements; i++ ) {
    newarray[i] = elementarray[i];
  }
  delete [] elementarray;
  elementarray = newarray;
  arraysize = newsize;
}

void FieldListView::checkShrink()
{
  double loadfactor;
  int newsize, i;
  Content **newarray;

  if ( arraysize <= 100 ) return;  // never less then 100 (possible) entires
  
  loadfactor = (double)elements;
  loadfactor /= arraysize;
  if ( loadfactor < .1 ) {
    // only if less the 10 percent used
    newsize = arraysize / 5;  // decrease to 20%
    if ( newsize < 100 ) newsize = 100;
    newarray = new Content*[newsize];
    for ( i = 0; i < elements; i++ ) {
      newarray[i] = elementarray[i];
    }
    delete [] elementarray;
    elementarray = newarray;
    arraysize = newsize;
  }
}

int FieldListView::getNrOfFields() const
{
    return fields;
}

void FieldListView::setNrOfFields( int nv )
{
  int *nw, *nu, i, *nmr;
  align_t *na;
  std::string *nt;
  bool *ntm;
  bool *ntra;

  if ( nv < 1 ) return;

  nw = new int[nv];
  nu = new int[nv];
  na = new align_t[nv];
  nt = new std::string[nv];
  ntm = new bool[nv];
  ntra = new bool[nv];
  nmr = new int[nv];
  if ( nv < fields ) {
    for ( i = 0; i < nv; i++ ) {
      nw[i] = fieldwidth[i];
      nu[i] = used_width[i];
      na[i] = fieldalign[i];
      nt[i] = fieldtext[i];
      ntm[i] = fieldtextmerged[i];
      ntra[i] = fieldtextResizeable[i];
      nmr[i] = maxrow[i];
    }
  } else {
    for ( i = 0; i < fields; i++ ) {
      nw[i] = fieldwidth[i];
      nu[i] = used_width[i];
      na[i] = fieldalign[i];
      nt[i] = fieldtext[i];
      ntm[i] = fieldtextmerged[i];
      ntra[i] = fieldtextResizeable[i];
      nmr[i] = maxrow[i];
    }
    for ( i = fields; i < nv; i++ ) {
      nw[i] = -1;
      nu[i] = -1;
      na[i] = ALIGN_LEFT;
      nt[i] = "";
      ntm[i] = false;
      ntra[i] = false;
      nmr[i] = -1;
    }
  }
  delete [] fieldwidth;
  delete [] used_width;
  delete [] fieldalign;
  delete [] fieldtext;
  delete [] fieldtextmerged;
  delete [] fieldtextResizeable;
  delete [] maxrow;
  fieldwidth = nw;
  used_width = nu;
  fieldalign = na;
  fieldtext = nt;
  fieldtextmerged = ntm;
  fieldtextResizeable = ntra;
  maxrow = nmr;
  fields = nv;

  fieldtextclicked = -1;  // when changing fields deactivate pressed

  m_header_text_width.resize( fields );
  m_field_spacing.resize( fields, -1 );
  m_hide_empty_field.resize( fields );

  redraw();
}

int FieldListView::getUsedWidth( int field )
{
  int i, nw, tw, r;

  if ( ( field < 0 ) || ( field >= fields ) ) return -1;
  
  if ( used_width[ field ] < 0 ) {
    nw = 0;
    if ( elements > 0 ) {
      r = (int)( (double)elements * rand() / ( RAND_MAX + 1.0 ) );
      if ( r >= elements ) r = 0;  // will not happen but just to be sure
      maxrow[ field ] = r;
      nw = _aguix->getTextWidth( elementarray[ r ]->getText( field ).c_str(), font );
      for ( i = 0; i < elements; i++ ) {
	tw = _aguix->getTextWidth( elementarray[ i ]->getText( field ).c_str(), font );
	if ( tw > nw ) {
	  nw = tw;
	  maxrow[ field ] = i;
	} else if ( tw == nw ) {
	  // the same length as current max
	  // but because I don't want that always the first is the max
	  // I will get a random value and decide if I use this as the new max
	  r = (int)( (double)elements* rand() / ( RAND_MAX + 1.0 ) );
	  if ( ( r >= ( ( elements / 2 ) - 2 ) ) && ( r <= ( ( elements / 2 ) - 2 ) ) ) {
	    maxrow[ field ] = i;
	  }
	}
      }
    }
    used_width[ field ] = nw;
  }
  return used_width[ field ];
}

int FieldListView::addRow()
{
  int newrow;

  if ( elements == arraysize ) increaseArray( arraysize * 2 );
  newrow = elements++;
  elementarray[ newrow ] = new Content();

  // this code is actually not needed since adding rows cannot invalidate
  // selection states (the selected rows still exist)
#if 0
  // deactivate select/middle mode
  selectModeIgnore();
  middleModeIgnore();
#endif

  if ( m_default_color_mode != PRECOLOR_NONE ) {
      setPreColors( newrow, m_default_color_mode );
  }

  m_vbar_rebuild_required = true;

  return newrow;
}

int FieldListView::deleteRow( int row )
{
  int i;

  if ( ( row < 0 ) || ( row >= elements ) ) return -1;
  delete elementarray[ row ];
  for ( i = row + 1; i < elements; i++ ) {
    elementarray[ i - 1 ] = elementarray[ i ];
  }
  elements--;
  if ( activerow == row ) activerow = -1;
  else if ( activerow > row ) {
    // decrease activerow so the same entry is active
    activerow--;
  }
  checkShrink();

  for ( i = 0; i < fields; i++ ) {
    // invalid dynamic fields
    if ( fieldwidth[i] == -1 ) {
      if ( ( maxrow[ i ] < 0 ) || ( maxrow[i] == row ) ) {
	used_width[i] = -1;
	maxrow[i] = -1;
      }
    }
  }

  // check yoffset
  if ( ( yoffset + maxdisplayv ) >= elements ) yoffset = elements - maxdisplayv;
  if ( yoffset < 0 ) yoffset = 0;

  if ( elements < maxdisplayv ) {
    // not all lines used so reset the unused elements
    clearNonElements();
  }

#if 0
  // deactivate select/middle mode
  selectModeIgnore();
  middleModeIgnore();
#endif

  m_vbar_rebuild_required = true;

  return 0;
}

void FieldListView::setXOffset( int nv, bool no_redraw )
{
  int t;

  t = getMaxTextWidth();
  if ( ( nv + getInnerWidth() ) >= t ) nv = t - getInnerWidth();
  if ( nv < 0 ) nv = 0;
  if ( nv != xoffset ) {
    xoffset = nv;
    
    if ( no_redraw == false ) {
      redrawContent();
      redrawHeader();
      drawBuffer();
      hbarredraw();
    }

    sendScrollEvent( AG_FIELDLV_HSCROLL );
  }
}

int FieldListView::getMaxTextWidth()
{
  //TODO: Vielleicht zwischenspeichern und nur bei deleteRow und setText aktualisieren
  int i, wi;

  wi = 0;
  for ( i = 0; i < fields; i++ ) {
      wi += getEffectiveFieldWidth( i );
  }
  return wi;
}

void FieldListView::updateOnDemandData()
{
    int i;
    struct on_demand_data data;

    if ( m_ondemand_callback ) {
        for ( i = 0; i < maxdisplayv; i++ ) {
            const int row = yoffset + i;
            if ( row >= elements ) break;

            if ( ! elementarray[ row ]->getOnDemandDataAvailable() ) {

                for ( int field = 0;
                      field < fields;
                      field++ ) {
                    m_ondemand_callback( this, row, field, data );

                    if ( data.text_set ) {
                        setText( row, field, data.text );
                        data.text_set = false;
                    }

                    if ( data.segments_set ) {
                        setHighlightSegments( row, field, data.segments );
                        data.segments_set = false;
                    }

                    if ( data.strike_out_set ) {
                        setStrikeOut( row, field, data.strike_out );
                        data.strike_out_set = false;
                    }
                  
                    if ( data.strike_out_start_set ) {
                        setStrikeOutStart( row, field, data.strike_out_start );
                        data.strike_out_start_set = false;
                    }
                  
                    if ( data.done ) {
                        data.done = false;
                        break;
                    }
                }

                elementarray[ row ]->setOnDemandDataAvailable( true );
            }
        }
    }
}

void FieldListView::redrawContent()
{
  int i;

  updateOnDemandData();
  
  for ( i = 0; i < maxdisplayv; i++ ) {
    if ( ( i + yoffset ) >= elements ) break;
    redrawLine( yoffset + i );
  }
}

void FieldListView::setYOffset( int nv, bool no_redraw )
{
  if ( ( nv + maxdisplayv ) >= elements ) nv = elements - maxdisplayv;
  if ( nv < 0 ) nv = 0;
  if ( nv != yoffset ) {
    yoffset = nv;

    clearNonElements();
    if ( no_redraw == false ) {
      redrawContent();
      drawBuffer();
      vbarredraw();
    }

    sendScrollEvent( AG_FIELDLV_VSCROLL );
  }
}

void FieldListView::setFieldAlignQ( int field, align_t nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  fieldalign[ field ] = nv;
}

void FieldListView::setFieldAlign( int field, align_t nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  setFieldAlignQ( field, nv );

  redraw();
}

void FieldListView::setFieldTextQ( int field, const std::string nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  fieldtext[ field ] = nv;
  m_header_text_width.at( field ) = 0;
}

void FieldListView::setFieldText( int field, const std::string nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  setFieldTextQ( field, nv );

  redraw();
}

std::string FieldListView::getFieldText( int field ) const
{
  if ( field < 0 || field >= fields ) return "";
  return fieldtext[ field ];
}

void FieldListView::setFieldTextMergedQ( int field, bool nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  fieldtextmerged[ field ] = nv;
}

void FieldListView::setFieldTextMerged( int field, bool nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  setFieldTextMergedQ( field, nv );

  redraw();
}

void FieldListView::setFieldTextResizeable( int field, bool nv )
{
  if ( ( field < 0 ) || ( field >= fields ) ) return;
  fieldtextResizeable[ field ] = nv;
}

void FieldListView::setSize( int rows )
{
  int i;
  if ( rows < elements ) {
    for ( i = rows; i < elements; i++ )
      delete elementarray[ i ];
    elements = rows;
    checkShrink();
  } else if ( rows > elements ) {
    if ( rows > arraysize ) increaseArray( rows );
    for ( i = elements; i < rows; i++ ) {
      elementarray[ i ] = new Content();
    }
    elements = rows;
  }
  if ( activerow >= elements ) activerow = -1;

  for ( i = 0; i < fields; i++ ) {
    // invalid dynamic fields
    if ( fieldwidth[i] == -1 ) {
      used_width[i] = -1;
      maxrow[i] = -1;
    }
  }

  // check yoffset
  if ( ( yoffset + maxdisplayv ) >= elements ) yoffset = elements - maxdisplayv;
  if ( yoffset < 0 ) yoffset = 0;

  if ( elements < maxdisplayv ) {
    // not all lines used so reset the unused elements
    clearNonElements();
  }

  m_vbar_rebuild_required = true;

#if 0
  // deactivate select/middle mode
  selectModeIgnore();
  middleModeIgnore();
#endif
}

void FieldListView::redraw( int element )
{
  if ( ( element < 0 ) || ( element >= elements ) ) return;
  if ( ( element >= yoffset ) && ( element < ( yoffset + maxdisplayv ) ) ) {
    redrawLine( element );
    drawBuffer( element );
  }
}

/*
 * redrawLine( element )
 *
 * draws element
 * No buffer->window copying
 */
void FieldListView::redrawLine( int element )
{
  std::string s;
  int sx, ex, f;
  int vis;
  int tfg, tbg;
  XRectangle clip_rect;
  int tx;

  if ( isCreated() == false ) return;
  if ( ( element < 0 ) || ( element >= elements ) ) return;
  if ( ( element < yoffset ) || ( element >= ( yoffset + maxdisplayv ) ) ) return;

  sx = 0;
  
  vis = element - yoffset;
  
  if ( element == activerow ) {
    if ( elementarray[ element ]->getSelect() == true ) {
      tfg = elementarray[ element ]->getFG( CC_SELACT );
      tbg = elementarray[ element ]->getBG( CC_SELACT );
    } else {
      tfg = elementarray[ element ]->getFG( CC_ACTIVE );
      tbg = elementarray[ element ]->getBG( CC_ACTIVE );
    }
  } else {
    if ( elementarray[ element ]->getSelect() == true ) {
      tfg = elementarray[ element ]->getFG( CC_SELECT );
      tbg = elementarray[ element ]->getBG( CC_SELECT );
    } else {
      tfg = elementarray[ element ]->getFG( CC_NORMAL );
      tbg = elementarray[ element ]->getBG( CC_NORMAL );
    }
  }
  _aguix->setFG( tbg );
  _aguix->FillRectangle( buffer,
			 ox,
			 vis * elementHeight + oy,
			 getInnerWidth(),
			 elementHeight );

  //TODO this could be member variable for efficiency since this method
  // is usually could for every visible line
  DrawableCont dc( _aguix, buffer );
  for ( f = 0; f < fields; f++ ) {
    ex = sx + getEffectiveFieldWidth( f );
    
    if ( ( sx < ( xoffset + getInnerWidth() ) ) && ( ex > xoffset ) ) {
      // field visible
      
      clip_rect.x = a_max( sx - xoffset, 0 ) + ox;
      clip_rect.y = 0;
      
      tx = ex - xoffset;
      if ( tx > getInnerWidth() ) tx = getInnerWidth();
      tx = tx + ox - clip_rect.x;
      tx -= getFieldSpace( f ) / 2;
      if ( tx < 0 ) tx = 0;
      clip_rect.width = tx;
      
      clip_rect.height = _h;
      _aguix->setClip( font, &dc, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height );
      s = elementarray[ element ]->getText( f );

      int field_content_x = 0;
      int field_content_width = 0;

      if ( fieldalign[ f ] == ALIGN_RIGHT ) {
          int tw = _aguix->getTextWidth( s.c_str(), font );
          int spacing = getFieldSpace( f ) / 2;
          _aguix->DrawText( dc, font, s.c_str(), ox + ex - spacing - xoffset - tw, vis * elementHeight + oy, tfg );

          if ( elementarray[ element ]->getStrikeOut( f ) ) {
              field_content_x = ox + ex - spacing - xoffset - tw;
              field_content_width = tw;
          }
      } else {
          int spacing = getFieldSpace( f ) / 2;

          const std::vector< size_t > *toggle_length = elementarray[ element ]->getHighlightSegments( f );

          if ( elementarray[ element ]->getStrikeOut( f ) ) {
              field_content_x = ox + sx + spacing - xoffset;
              field_content_width = _aguix->getTextWidth( s.c_str(), font );
          }

          if ( toggle_length == NULL ) {
              _aguix->DrawText( dc, font, s.c_str(), ox + sx + spacing - xoffset, vis * elementHeight + oy, tfg );
          } else {
              // draw segment of alternating draw state

              bool toggle_state = false;
              int current_x_pos = ox + sx + spacing - xoffset;
              size_t total_length = s.length();
              size_t processed_length = 0;

              for ( auto length : *toggle_length ) {
                  if ( processed_length + length >= total_length ) {
                      break;
                  }
                  if ( length > 0 ) {
                      std::string segment( s, processed_length, length );
                      int segment_width = _aguix->getTextWidth( segment.c_str(), font );

                      if ( toggle_state ) {
                          _aguix->setFG( tfg );
                          _aguix->FillRectangle( buffer,
                                                 current_x_pos,
                                                 vis * elementHeight + oy,
                                                 segment_width,
                                                 elementHeight );
                      }

                      _aguix->DrawText( dc, font, segment.c_str(), current_x_pos, vis * elementHeight + oy, toggle_state ? tbg : tfg );
                      current_x_pos += segment_width;
                      processed_length += length;
                  }
                  toggle_state = ! toggle_state;
              }

              if ( processed_length < total_length ) {
                  std::string segment( s, processed_length, total_length - processed_length );
                  int segment_width = _aguix->getTextWidth( segment.c_str(), font );

                  if ( toggle_state ) {
                      _aguix->setFG( tfg );
                      _aguix->FillRectangle( buffer,
                                             current_x_pos,
                                             vis * elementHeight + oy,
                                             segment_width,
                                             elementHeight );
                  }

                  _aguix->DrawText( dc, font, segment.c_str(), current_x_pos, vis * elementHeight + oy, toggle_state ? tbg : tfg );
              }
          }
      }

      if ( elementarray[ element ]->getStrikeOut( f ) ) {
          int strike_out_y = vis * elementHeight + oy + elementHeight / 2;

          size_t start = elementarray[ element ]->getStrikeOutStart( f );
          int skip_start = 0;

          if ( start > 0 ) {
              std::string segment( s, 0, start );
              skip_start = _aguix->getTextWidth( segment.c_str(), font );
          }

          if ( start < s.size() ) {
              _aguix->setFG( tfg );
              _aguix->DrawLine( buffer,
                                field_content_x + skip_start,
                                strike_out_y,
                                field_content_x + field_content_width - 1,
                                strike_out_y);
          }
      }
    }
    
    sx = ex;
  }
  _aguix->unclip( font, &dc );
  if ( getVisMark( element ) == true ) {
    _aguix->setDashDFG( 1 );
    _aguix->setDashDBG( 0 );
    _aguix->DrawDashDRectangle( buffer, 
				ox,
				vis * elementHeight + oy,
				getInnerWidth(),
				elementHeight );
  }
}

void FieldListView::Content::setFG( colorclass_t cc, int col )
{
  fg[ cc ] = col;
}

int FieldListView::Content::getFG( colorclass_t cc ) const
{
  return fg[ cc ];
}

void FieldListView::Content::setBG( colorclass_t cc, int col )
{
  bg[ cc ] = col;
}

int FieldListView::Content::getBG( colorclass_t cc ) const
{
  return bg[ cc ];
}

void FieldListView::setFG( int row, colorclass_t cc, int col )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  elementarray[ row ]->setFG( cc, col );
}

int FieldListView::getFG( int row, colorclass_t cc ) const
{
  if ( ( row < 0 ) || ( row >= elements ) ) return 0;
  return elementarray[ row ]->getFG( cc );
}

void FieldListView::setBG( int row, colorclass_t cc, int col )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  elementarray[ row ]->setBG( cc, col );
}

int FieldListView::getBG( int row, colorclass_t cc ) const
{
  if ( ( row < 0 ) || ( row >= elements ) ) return 0;
  return elementarray[ row ]->getBG( cc );
}

void FieldListView::Content::setSelect( bool nv )
{
  select = nv;
}

bool FieldListView::Content::getSelect() const
{
  return select;
}

void FieldListView::Content::setMark( bool nv )
{
  mark = nv;
}

bool FieldListView::Content::getMark() const
{
  return mark;
}

/*
 * setSelectQ: set select status
 * only disadvantage to setSelect is that the row isn't redrawed
 */
void FieldListView::setSelectQ( int row, bool nv )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;

  if ( elementarray[ row ]->getSelect() == nv ) return;

  elementarray[ row ]->setSelect( nv );

  updateSelectState( row, false );
}

void FieldListView::setSelect( int row, bool nv )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  setSelectQ( row, nv );
  redraw( row );

  if ( m_show_selection_in_vbar &&
       m_vbar_select_some_dirty ) {
      vbarredraw();
  }
}

bool FieldListView::getSelect( int row ) const
{
  if ( ( row < 0 ) || ( row >= elements ) ) return false;
  return elementarray[ row ]->getSelect();
}

/*
 * setActiveRowQ
 * sets the active row without redrawing of affected rows
 */
void FieldListView::setActiveRowQ( int nv )
{
  if ( ( nv < -1 ) || ( nv >= elements ) ) return;
  activerow = nv;
}

void FieldListView::setActiveRow( int nv )
{
  int oldact;

  if ( ( nv < -1 ) || ( nv >= elements ) ) return;

  // remember old activerow for redraw
  oldact = activerow;

  setActiveRowQ( nv );

  redraw( activerow );
  if ( oldact >= 0 ) redraw( oldact );  // redraw old active element
}

int FieldListView::getActiveRow() const
{
  return activerow;
}

/*
 * setVisMaskQ: set visual mark status
 * only disadvantage to setVisMask is that the row isn't redrawed
 */
void FieldListView::setVisMarkQ( int row, bool nv )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  elementarray[ row ]->setMark( nv );
}

void FieldListView::setVisMark( int row, bool nv )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  setVisMarkQ( row, nv );
  redraw( row );
}

bool FieldListView::getVisMark( int row ) const
{
  if ( ( row < 0 ) || ( row >= elements ) ) return false;
  return elementarray[ row ]->getMark();
}

void FieldListView::resize( int tw, int th )
{
  if ( tw < 6 || th < 6 ) return;

  _w = tw;
  _h = th;
  if ( isCreated() == true ) {
    _parent->resizeSubWin( win, tw, th );
    _aguix->freePixmap( buffer );
    buffer = _aguix->createPixmap( win, tw, th );
    recalcMaxValues();
    setupBars();
    if ( ( yoffset + maxdisplayv ) >= elements ) {
      setYOffset( yoffset );
    }
    if ( ( xoffset + getInnerWidth() ) >= getMaxTextWidth() ) {
      setXOffset( xoffset );
    }
    resetWin( true );
  }
}

int FieldListView::getData() const
{
  return data;
}

void FieldListView::setData( int tdata )
{
  this->data = tdata;
}

void FieldListView::redraw()
{
  mredraw();
  redrawContent();
  hbarredraw();
  vbarredraw();
  redrawHeader();
  drawBuffer();
}

void FieldListView::flush()
{
  drawBuffer();
}

void FieldListView::handleExpose( Window msgwin, int ex, int ey, int ew, int eh )
{
  if ( isCreated() == false ) return;
  if ( msgwin == win ) {
    drawBuffer( ex, ey, ew, eh );
  } else if ( msgwin == hbarwin ) hbarredraw();
  else if ( msgwin == vbarwin ) vbarredraw();
}

bool FieldListView::handleMessage( XEvent *E, Message *msg )
{
  bool returnvalue;
  struct timeval t2;
  int dt, s, us, scrollspeed;

  if ( isCreated() == false ) return false;

  returnvalue = false;
  if ( msg->type == Expose ) {
    handleExpose( msg->window,
		  msg->x,
		  msg->y,
		  msg->width,
		  msg->height );
  }
  if ( vbarMode != VBAR_IDLE ) handleVBar( msg );
  else if ( hbarMode != HBAR_IDLE ) handleHBar( msg );
  else if ( thirdMode != THIRD_IDLE ) handleThird( msg );
  else if ( selectMode != SELECT_IDLE ) handleSelect( msg );
  else if ( middleMode != MIDDLE_IDLE ) handleMiddle( msg );
  else if ( headerMode != HEADER_IDLE ) handleHeader( msg );
  else {
    if ( msg->type == ButtonPress ) {
      // jetzt muessen wir was machen
      bool isc = false;
      if ( msg->window == hbarwin ) {
	takeFocus();
	handleHBar( msg );
	isc = true;
      } else if ( msg->window == vbarwin ) {
	takeFocus();
	handleVBar( msg );
	isc = true;
      } else if ( msg->window == win ) {
	// handleSelect, handleThird
	takeFocus();
	
	if ( ( msg->mousey >= ( oy - getHeaderHeight() ) ) && ( msg->mousey < oy ) ) {
	  // field clicked
	  handleHeader( msg );
	} else {
	  if ( msg->button == _scroll_button &&
               checkForValidButtonMod( msg->button, msg->keystate, _scroll_mod ) == true ) {
              handleThird( msg );
          } else if ( msg->button == Button4 ||
                      msg->button == Button5 ||
                      msg->button == s_hscroll_left_button ||
                      msg->button == s_hscroll_right_button ) {
	    gettimeofday( &t2, NULL );
	    s = abs( (int)( t2.tv_sec - lastwheel.tv_sec ) );
	    us = t2.tv_usec - lastwheel.tv_usec;
	    if ( us < 0 ) {
	      us += 1000000;
	      s--;
	    }
	    dt = us + s * 1000000;
	    dt /= 100000;
	    if ( dt < 2 ) scrollspeed = 5;
	    else if ( dt < 4 ) scrollspeed = 2;
	    else scrollspeed = 1;
	    if ( msg->button == Button4 ) scrollV1( -scrollspeed );
	    else if ( msg->button == Button5 ) scrollV1( scrollspeed );
	    else if ( msg->button != 0 && msg->button == s_hscroll_left_button ) {
                setXOffset( getXOffset() - scrollspeed * getHScrollStep() / 2 );
            } else if ( msg->button != 0 && msg->button == s_hscroll_right_button ) {
                setXOffset( getXOffset() + scrollspeed * getHScrollStep() / 2 );
            }
            
	    lastwheel = t2;
	  } else if ( msg->button == _activate_button &&
                      checkForValidButtonMod( msg->button, msg->keystate, _activate_mod ) == true ) {
	    handleMiddle( msg );
	  } else if ( msg->button == _entry_pressed_msg_button &&
                      checkForValidButtonMod( msg->button, msg->keystate, _entry_pressed_msg_mod ) == true ) {
	    handleEntryPressed( msg );
	  } else if ( msg->button == _select_button ) {
	    handleSelect( msg );
	  }
	}
	isc = true;
      }
      if ( isc == true ) {
	AGMessage *agmsg = AGUIX_allocAGMessage();
	agmsg->fieldlv.lv = this;
	agmsg->fieldlv.row = -1;
	agmsg->fieldlv.time = msg->time;
	agmsg->fieldlv.mouse = true;
	agmsg->type = AG_FIELDLV_PRESSED;
        msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
      }
    } else if ( msg->type == ButtonRelease ) {
      // handle button release only for header
      if ( msg->window == win ) {
	handleHeader( msg );
      }
    } else if ( msg->type == KeyPress ) {
      if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
	if ( isVisible() == true ) {
	  if ( _parent->isTopParent( msg->window ) == true ) {
	    // we have the focus so let's react to some keys
	    // lets call an extra handler so it can be overwritten
	    returnvalue = handleKeys( msg );
	  }
	}
      }
    } else if ( msg->type == MotionNotify ) {
      _aguix->queryPointer( win, &( msg->mousex ), &( msg->mousey ) );
      handleHeader( msg );
    }
  }
  if ( msg->lockElement == this ) msg->ack = true;
  return returnvalue;
}

void FieldListView::hbarredraw()
{
  if ( hbar == 0 ) return;
  if ( isCreated() == false ) return;
  int tx, ty, tw, th, bw;
  XPoint arrow_left[3], arrow_right[3];

  int maxtextwidth = getMaxTextWidth();

  tw = getInnerWidth();
  th = hbar_height;
  ty = 0;
  tx = 0;

  bw = 2;

  int dh = ( th - 1 ) & ~1;
  dh = ( dh - 2 * bw ) / 2;
  
  arrow_left[0].x = tx + th - bw - 1;
  arrow_left[0].y = ty + ( th - 1 ) / 2 - dh;
  arrow_left[1].x = tx + bw;
  arrow_left[1].y = ty + ( th - 1 )  / 2;
  arrow_left[2].x = tx + th - bw - 1;
  arrow_left[2].y = ty + ( th - 1 ) / 2 + dh;

  arrow_right[0].x = tx + tw - th + bw;
  arrow_right[0].y = ty + ( th - 1 ) / 2 - dh;
  arrow_right[1].x = tx + tw - bw - 1;
  arrow_right[1].y = ty + ( th - 1 )  / 2;
  arrow_right[2].x = tx + tw - th + bw;
  arrow_right[2].y = ty + ( th - 1 ) / 2 + dh;

  _aguix->SetWindowBG( hbarwin, _aguix->getFaces().getAGUIXColor( "slider-bg" ) );
  _aguix->ClearWin( hbarwin );
  _aguix->drawBorder( hbarwin, ( hbar_dir == 1 ) ? true : false, tx, ty, th, th, 0 );

  // dark grey
  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg2" ) );
  _aguix->FillRectangle( hbarwin, tx + th, ty,
                         tw -  2 * th, th );

  _aguix->setFG( _aguix->getFaceCol_3d_dark() );
  _aguix->DrawLine( hbarwin, tx + th, ty + th - 1, tx + tw - th - 1, ty + th - 1 );
  _aguix->setFG( _aguix->getFaceCol_3d_bright() );
  _aguix->DrawLine( hbarwin, tx + th, ty, tx + tw - th - 1, ty );
  
  _aguix->drawBorder( hbarwin, ( hbar_dir == 2 ) ? true : false, tx + tw - th, ty, th, th, 0 );

  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-arrow" ) );
  _aguix->DrawTriangleFilled( hbarwin,
			      arrow_left[0].x, arrow_left[0].y,
			      arrow_left[1].x, arrow_left[1].y,
			      arrow_left[2].x, arrow_left[2].y );
  _aguix->DrawTriangleFilled( hbarwin,
			      arrow_right[0].x, arrow_right[0].y,
			      arrow_right[1].x, arrow_right[1].y,
			      arrow_right[2].x, arrow_right[2].y );

  // consider empty area after last field 
  int emptyArea = a_max( 0, getInnerWidth() - ( maxtextwidth - xoffset ) );
  maxtextwidth += emptyArea;

  // jetzt noch die eigentliche Leiste
  tw -= 2 * th;  // Breite wenn alles anzeigbar ist
  th -= 2;
  ty++;
  if ( maxtextwidth > getInnerWidth() ) {
    int dw = tw * getInnerWidth();
    dw /= maxtextwidth;

    if ( dw < 10 ) dw = 10;

    int a = maxtextwidth - getInnerWidth();
    double b = tw - dw;  // das was uebrig bleibt
    b /= a;            // verteilt sich auch die nichtsichtbaren Zeichen
    tx = tx + hbar_height + (int)( xoffset * b );
    tw = dw;
  } else {
    tx = tx + hbar_height;
  }

  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg" ) );
  _aguix->FillRectangle( hbarwin, tx, ty,
			 tw, th );

  _aguix->drawBorder( hbarwin, hbar_pressed, tx, ty, tw, th, 0 );
}

void FieldListView::vbarredraw()
{
  if(vbar==0) return;
  if ( isCreated() == false ) return;
  int tx, ty, tw, th, bw;
  XPoint arrow_up[3], arrow_down[3];

  th = getInnerHeight();
  tw=vbar_width;
  tx=0;
  ty=0;

  bw = 2;

  int dw = ( tw - 1 ) & ~1;
  dw = ( dw - 2 * bw ) / 2;
  
  arrow_down[0].x = tx + ( tw - 1 ) / 2 - dw;
  arrow_down[0].y = ty + th - tw + bw;
  arrow_down[1].x = tx + ( tw - 1 ) / 2;
  arrow_down[1].y = ty + th - bw - 1;
  arrow_down[2].x = tx + ( tw - 1 )  / 2 + dw;
  arrow_down[2].y = ty + th - tw + bw;

  arrow_up[0].x = tx + ( tw - 1 ) / 2 - dw;
  arrow_up[0].y = ty + tw - bw - 1;
  arrow_up[1].x = tx + ( tw - 1 ) / 2;
  arrow_up[1].y = ty + bw;
  arrow_up[2].x = tx + ( tw - 1 )  / 2 + dw;
  arrow_up[2].y = ty + tw - bw - 1;

  _aguix->SetWindowBG( vbarwin, _aguix->getFaces().getAGUIXColor( "slider-bg" ) );
  _aguix->ClearWin(vbarwin);

  _aguix->drawBorder( vbarwin, ( vbar_dir == 1 ) ? true : false, tx, ty, tw, tw, 0 );
  
  // dark grey
  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg2" ) );
  _aguix->FillRectangle( vbarwin, tx, ty + tw,
                         tw, th -  2 * tw );

  _aguix->setFG( _aguix->getFaceCol_3d_dark() );
  _aguix->DrawLine(vbarwin,tx+tw-1,ty+tw,tx+tw-1,ty+th-tw-1);
  _aguix->setFG( _aguix->getFaceCol_3d_bright() );
  _aguix->DrawLine(vbarwin,tx,ty+tw,tx,ty+th-tw-1);

  _aguix->drawBorder( vbarwin, ( vbar_dir == 2 ) ? true : false, tx, ty + th - tw, tw, tw, 0 );
  
  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-arrow" ) );
  _aguix->DrawTriangleFilled( vbarwin,
			      arrow_down[0].x, arrow_down[0].y,
			      arrow_down[1].x, arrow_down[1].y,
			      arrow_down[2].x, arrow_down[2].y );
  _aguix->DrawTriangleFilled( vbarwin,
			      arrow_up[0].x, arrow_up[0].y,
			      arrow_up[1].x, arrow_up[1].y,
			      arrow_up[2].x, arrow_up[2].y );

  drawSelectStateVBar();
  
  // jetzt noch die eigentliche Leiste
  th-=2*tw;  // Hoehe wenn alles anzeigbar ist
  tw-=2;
  tx++;
  int curelements=getElements();
  if ( curelements > maxdisplayv ) {
    int dh=th*maxdisplayv;
    dh /= curelements;

    if ( dh < 10 ) dh = 10;

    int a = curelements - maxdisplayv;
    double b=th-dh;  // das was uebrig bleibt
    b/=a;            // verteilt sich auch die nichtsichtbaren Zeichen
    ty=ty+vbar_width+(int)(yoffset*b);
    th=dh;
  } else {
    ty=ty+vbar_width;
  }

  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg" ) );
  _aguix->FillRectangle( vbarwin, tx, ty,
                         tw, th );

  _aguix->drawBorder( vbarwin, vbar_pressed, tx, ty, tw, th, 0 );
}

void FieldListView::mredraw()
{
  if ( isCreated() == false ) return;

  _aguix->setFG( mbg );
  _aguix->FillRectangle( buffer, 0, 0, _w, _h );
  _aguix->setFG( _aguix->getFaceCol_3d_bright() );
  _aguix->DrawLine( buffer, 0, _h - 1, 0, 0 );
  _aguix->DrawLine( buffer, 0, 0, _w - 1, 0 );
  _aguix->setFG( _aguix->getFaceCol_3d_dark() );
  _aguix->DrawLine( buffer, 1, _h - 1, _w - 1, _h - 1 );
  _aguix->DrawLine( buffer, _w - 1, _h - 1, _w - 1, 1 );
  if ( ( hbar != 0 ) && ( vbar != 0 ) ) {
    int tx, ty;
    tx = ( vbar == 1 ) ? ( ( displayFocus == true ) ? 2 : 1 ) : ( _w - ( ( displayFocus == true ) ? 2 : 1 ) - vbar_width );
    ty = ( hbar == 1 ) ? ( ( displayFocus == true ) ? 2 : 1 ) : ( _h - ( ( displayFocus == true ) ? 2 : 1 ) - hbar_height );
    _aguix->setFG( _aguix->getFaceCol_default_bg() );
    _aguix->FillRectangle( buffer, tx, ty, vbar_width, hbar_height );
  }
  if ( displayFocus == true ) {
    if ( getHasFocus() == true ) {
      _aguix->setFG( _aguix->getFaceCol_3d_dark() );
      _aguix->DrawLine( buffer, 1, _h - 2, 1, 1 );
      _aguix->DrawLine( buffer, 1, 1, _w - 2, 1 );
      _aguix->setFG( _aguix->getFaceCol_3d_bright() );
      _aguix->DrawLine( buffer, 2, _h - 2, _w - 2, _h - 2 );
      _aguix->DrawLine( buffer, _w - 2, _h - 2, _w - 2, 2 );
    } else {
      _aguix->setFG( _aguix->getFaceCol_3d_bright() );
      _aguix->DrawLine( buffer, 1, _h - 2, 1, 1 );
      _aguix->DrawLine( buffer, 1, 1, _w - 2, 1 );
      _aguix->setFG( _aguix->getFaceCol_3d_dark() );
      _aguix->DrawLine( buffer, 2, _h - 2, _w - 2, _h - 2 );
      _aguix->DrawLine( buffer, _w - 2, _h - 2, _w - 2, 2 );
    }
  }
}

void FieldListView::recalcMaxValues()
{
  int th;

  if ( font == NULL ) elementHeight = _aguix->getCharHeight();
  else elementHeight = font->getCharHeight();

  th = getInnerHeight();
  /* this is for maxdisplayv/h ... */

  if ( showHeader == true )
    th -= getHeaderHeight();

  if ( th < 0 ) th = 0;
  maxdisplayv = th / elementHeight;

  /* ... and this for the drawing of the elements */

  oy = 1;

  if ( hbar == 1 ) oy += hbar_height;

  ox = 1;
  if ( vbar == 1 ) ox += vbar_width;

  if ( displayFocus == true ) {
    oy++;
    ox++;
  }
  if ( showHeader == true ) {
    oy += getHeaderHeight();
  }
}

int FieldListView::getElements() const
{
  return elements;
}

int FieldListView::getXOffset() const
{
  return xoffset;
}

int FieldListView::getYOffset() const
{
  return yoffset;
}

int FieldListView::getMaxDisplayV() const
{
  return maxdisplayv;
}

void FieldListView::setHBarState( int state )
{
  if ( hbar == state ) return;
  if ( ( state == 0 ) && ( isCreated() == true ) ) _parent->removeSubWin( hbarwin );
  if ( ( hbar == 0 ) && ( state > 0 ) && ( isCreated() == true ) ) hbarwin = _parent->getSubWindow( win, 0, 0, 5, 5 );
  hbar = state;
  recalcMaxValues();
  setupBars();
  resetWin( true );
}

void FieldListView::setVBarState(int state)
{
  if ( state == vbar ) return;
  if ( ( state == 0 ) && ( isCreated() == true ) ) _parent->removeSubWin( vbarwin );
  if ( ( vbar == 0 ) && ( state > 0 ) && ( isCreated() == true ) ) vbarwin = _parent->getSubWindow( win, 0, 0, 5, 5 );
  vbar = state;
  recalcMaxValues();
  setupBars();
  resetWin( true );
}

int FieldListView::getHBarState() const
{
  return hbar;
}

int FieldListView::getVBarState() const
{
  return vbar;
}

void FieldListView::setVBarWidth( int new_width )
{
  if ( ( new_width < 0 ) || ( ( new_width + 4 ) >= _w ) ) return;
  vbar_width = new_width;
  recalcMaxValues();
  setupBars();
  vbarredraw();
  resetWin( true );
}

int FieldListView::getVBarWidth() const
{
  return vbar_width;
}

void FieldListView::setHBarHeight( int new_height )
{
  if ( ( new_height < 0 ) || ( ( new_height + 4 ) >= _h ) ) return;
  hbar_height = new_height;
  recalcMaxValues();
  setupBars();
  hbarredraw();
  resetWin( true );
}

int FieldListView::getHBarHeight() const
{
  return hbar_height;
}

/*
 * clearNonElements
 *
 * will reset background for unused entries
 *
 */
void FieldListView::clearNonElements()
{
  int sy,e;

  if ( isCreated() == false ) return;
  if ( ( yoffset + maxdisplayv ) > elements ) {
    sy = elements - yoffset;
    sy = sy * elementHeight + oy;
    e = maxdisplayv - ( elements - yoffset );
    _aguix->setFG( mbg );
    _aguix->FillRectangle( buffer, ox, sy, getInnerWidth(), e * elementHeight );
  }
}

int FieldListView::selectRowsRange( int startRow, int endRow, bool state )
{
  int dire, selected = 0;

  int curRow = -1;

  if ( startRow >= elements ) startRow = elements - 1;
  if ( endRow >= elements ) endRow = elements - 1;
  if ( ( startRow < 0 ) || ( endRow < 0 ) ) return 0;

  // skip first entry if it's selected already
  if ( startRow < endRow ) {
    if ( getSelect( startRow ) == state ) startRow++;
    dire = 1;
  } else if ( startRow > endRow ) {
    if ( getSelect( startRow ) == state ) startRow--;
    dire = -1;
  } else dire = 0;
  
  for ( curRow = startRow; ; curRow += dire ) {
    if ( curRow >= elements ) break;
    if ( curRow < 0 ) break;

    setSelect( curRow, state );
    if ( curRow == endRow ) setActiveRow( curRow );
    runSelectHandler( curRow );
    selected++;

    if ( curRow == endRow ) break;
  }
  return selected;
}

void FieldListView::handleSelect( Message *msg )
{
  int j;
  int win_x, win_y;
  unsigned int keys_buttons;
  int newelement;
  AGMessage *agmsg;
  
  if ( isCreated() == false ) return;

  if ( msg->type == ButtonPress ) {
    if ( msg->window != win ) return;
    if ( selectMode != SELECT_IDLE ) return;

    j = getRowForMouseY( msg->mousey );
    if ( j < 0 ) return;
    if ( ( j + yoffset ) >= elements ) return;
    if ( j >= maxdisplayv ) return;

    keys_buttons = msg->keystate & ( ShiftMask | ControlMask );

    enum { RANGE_SELECT, SINGLE_SELECT, TOGGLE_SELECT } mode;

    if ( _select_mode == MOUSE_SELECT_ALT ) {
      mode = SINGLE_SELECT;
    } else {
      mode = TOGGLE_SELECT;
    }
    if ( keys_buttons == ShiftMask ) {
      mode = RANGE_SELECT;
    } else if ( keys_buttons == ControlMask ) {
      mode = TOGGLE_SELECT;
    }

    newelement = j + yoffset;

    if ( mode == RANGE_SELECT && isValidRow( clickLastSelectedRow ) == true ) {
      //TODO lieber active element nutzen als start?
      clickState = true;
      clickSelected = selectRowsRange( clickLastSelectedRow, newelement, clickState );
      clickFirstSelectedRow = clickLastSelectedRow;
      clickLastSelectedRow = newelement;
    } else if ( mode == SINGLE_SELECT ) {
      clickState = true;
      singleSelectRow( newelement, true );
      // Although there are propably more than one changes of the select state
      // I initialize this with 1 because this is the number of elements
      // actually selected by the user
      clickSelected = 1;
      clickLastSelectedRow = newelement;
      clickFirstSelectedRow = clickLastSelectedRow;

      //TODO fuer spaeter: Man koennte einen zaehler mitfuehren, der bei setSelect
      // jeweils aktualisiert wird. Wenn nun dieser Zaehler 1, dann duerfte das momentan
      // ausgewaehlte Element clickLastSelected sein, d.h. ich muss dann nicht ueber
      // die gesamte Liste fahren
    } else {
      clickLastSelectedRow = newelement;
      clickState = ( getSelect( clickLastSelectedRow ) == false ) ? true : false;
      clickFirstSelectedRow = clickLastSelectedRow;
      clickSelected = 1;
      
      setSelect( clickLastSelectedRow, clickState );
      setActiveRow( clickLastSelectedRow );
      runSelectHandler( clickLastSelectedRow );
    }
    
    selectMode = SELECT_HOLD;

    _aguix->Flush();
    _aguix->msgLock( this );
  } else if ( msg->type == MotionNotify ) {
    if ( ( msg->window == win ) &&
	 ( ( selectMode == SELECT_HOLD ) ||
	   ( selectMode == SELECT_SCROLL_DOWN ) ||
	   ( selectMode == SELECT_SCROLL_UP ) ) ) {

      keys_buttons = msg->keystate & ( ShiftMask | ControlMask );
      if ( _select_mode == MOUSE_SELECT_NORMAL ||
	   keys_buttons == ControlMask ) {
          _aguix->queryPointer( win, &win_x, &win_y, &keys_buttons );
	if ( ( keys_buttons & _select_buttonmask ) != 0 ) {
	  j = getRowForMouseY( win_y );
	  if ( ( j < 0 ) && ( yoffset > 0 ) ) {
	    newelement = yoffset - 1;
	    if ( newelement >= 0 ) {
	      if ( selectMode != SELECT_SCROLL_UP ) {
		scrollV1( -1 );
		clickSelected += selectRowsRange( clickLastSelectedRow, newelement, clickState );
		clickLastSelectedRow = newelement;
		selectMode = SELECT_SCROLL_UP;
		_aguix->enableTimer();
	      }
	    }
	  } else if ( ( j >= maxdisplayv ) && ( ( yoffset + maxdisplayv ) < elements ) ) {
	    newelement = yoffset + maxdisplayv;
	    if ( newelement < elements ) {
	      if ( selectMode != SELECT_SCROLL_DOWN ) {
		scrollV1( 1 );
		clickSelected += selectRowsRange( clickLastSelectedRow, newelement, clickState );
		clickLastSelectedRow = newelement;
		selectMode = SELECT_SCROLL_DOWN;
		_aguix->enableTimer();
	      }
	    }
	  } else {
	    if ( j < 0 ) j = 0;
	    if ( ( j + yoffset ) >= elements ) {
	      j = elements - 1 - yoffset;
	    }
	    if ( ( j + yoffset ) != clickLastSelectedRow ) {
	      newelement = j + yoffset;
	      clickSelected += selectRowsRange( clickLastSelectedRow, newelement, clickState );
	      clickLastSelectedRow = newelement;
	    }
	    if ( selectMode != SELECT_HOLD ) {
	      selectMode = SELECT_HOLD;
	      _aguix->disableTimer();
	    }
	  }
	}
      }
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == win ) &&
	 ( msg->button == _select_button ) ) {
      switch ( selectMode ) {
	case SELECT_IGNORE:
	  _aguix->msgUnlock( this );
	  selectMode = SELECT_IDLE;
	  break;
	case SELECT_SCROLL_UP:
	case SELECT_SCROLL_DOWN:
	  _aguix->disableTimer();
	case SELECT_HOLD:
	  agmsg = AGUIX_allocAGMessage();
	  agmsg->fieldlv.lv = this;
	  agmsg->fieldlv.row = clickFirstSelectedRow;
	  agmsg->fieldlv.time = msg->time;
	  agmsg->fieldlv.mouse = true;
	  if ( clickSelected == 1 ) {
	    agmsg->type = AG_FIELDLV_ONESELECT;
	  } else {
	    agmsg->type = AG_FIELDLV_MULTISELECT;
	  }
          msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );

          if ( clickSelected == 1 ) {
            int dc_row = _last_single_select_row;
            Time dc_time = _last_single_select_time;
            
            _last_single_select_row = clickFirstSelectedRow;
            _last_single_select_time = msg->time;
            
            if ( isValidRow( dc_row ) == true && clickFirstSelectedRow == dc_row ) {
              if ( _aguix->isDoubleClick( msg->time, dc_time ) == true ) {
                agmsg = AGUIX_allocAGMessage();
                agmsg->fieldlv.lv = this;
                agmsg->fieldlv.row = clickFirstSelectedRow;
                agmsg->fieldlv.time = msg->time;
                agmsg->fieldlv.mouse = true;
                agmsg->type = AG_FIELDLV_DOUBLECLICK;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                _last_single_select_row = -1;
                _last_single_select_time = 0;
              }
            }
          }
 
	  _aguix->msgUnlock( this );
	  selectMode = SELECT_IDLE;
	  break;
	default:
	  break;
      }
    }
  } else if ( msg->type == ClientMessage ) {
    if ( msg->specialType == Message::TIMEREVENT ) {
      if ( ( msg->time % 1 ) == 0 ) { //HARDCODED
        if ( getCurrentMouseButtonState( win ) & _select_buttonmask ) {
          if ( selectMode == SELECT_SCROLL_UP ) {
            newelement = clickLastSelectedRow - 1;
            if ( newelement >= 0 ) {
              scrollV1( -1 );
              clickSelected += selectRowsRange( clickLastSelectedRow, newelement, clickState );
              clickLastSelectedRow = newelement;
            }
          } else if ( selectMode == SELECT_SCROLL_DOWN ) {
            newelement = clickLastSelectedRow + 1;
            if ( newelement < elements ) {
              scrollV1( 1 );
              clickSelected += selectRowsRange( clickLastSelectedRow, newelement, clickState );
              clickLastSelectedRow = newelement;
            }
          }
          _aguix->Flush();
        }
      }
    }
  }
}

void FieldListView::updateScrollValues( int mx, int my )
{
  int midx, midy, dsx, dsy;

  midx = getInnerWidth();
  dsx = midx;
  midx /= 2;
  dsx /= 6;

  midy = getInnerHeight();
  if ( showHeader == true ) midy -= getHeaderHeight();
  dsy = midy;
  midy /= 2;
  dsy /= 6;

  if ( mx < ( midx - dsx ) ) scrollXDir = -1;
  else if ( mx > ( midx + dsx ) ) scrollXDir = 1;
  else scrollXDir = 0;
  
  if ( my < ( midy - dsy ) ) scrollYDir = -1;
  else if ( my > ( midy + dsy ) ) scrollYDir = 1;
  else scrollYDir = 0;
  
  //HARDCODED
  if ( ( my < dsy ) || ( my > ( midy + 2 * dsy ) ) ) scrollDelta = 1;
  else if ( ( mx < dsx ) || ( mx > ( midx + 2 * dsx ) ) ) scrollDelta = 1;
  else scrollDelta = 3;
}

void FieldListView::handleThird(Message *msg)
{
  int mx, my;
  int j;
  
  if ( isCreated() == false ) return;

  if ( msg->type == ButtonPress ) {
    if ( msg->window != win ) return;
    if ( thirdMode != THIRD_IDLE ) return;

    j = getRowForMouseY( msg->mousey );
    if ( j < 0 ) return;
    if ( j >= maxdisplayv ) return;

    mx = msg->mousex;
    my = msg->mousey;
    my -= oy;
    mx -= ox;

    updateScrollValues( mx, my );

    thirdMode = THIRD_SCROLL;

    if ( scrollXDir == 0 && scrollYDir == 0 ) {
        m_pan_info.m_pan_mode = m_pan_info.PAN_PREPARE;
        m_pan_info.m_pan_base_mx = mx;
        m_pan_info.m_pan_base_my = my;
        _aguix->setCursor( win, AGUIX::SCROLLFOUR_CURSOR );
    }

    if ( scrollXDir < 0 ) setXOffset( xoffset - getHScrollStep() );
    else if ( scrollXDir > 0 ) setXOffset( xoffset + getHScrollStep() );
    if ( scrollYDir < 0 ) scrollV1( -1 );
    else if ( scrollYDir > 0 ) scrollV1( 1 );
    _aguix->Flush();
    _aguix->msgLock( this );
    _aguix->enableTimer();
  } else if ( msg->type == MotionNotify ) {
    if ( thirdMode == THIRD_SCROLL ) {
      _aguix->queryPointer( win, &mx, &my );
      my -= oy;
      mx -= ox;

      if ( m_pan_info.m_pan_mode != m_pan_info.PAN_INACTIVE ) {
          if ( m_pan_info.m_pan_mode == m_pan_info.PAN_PREPARE ) {
              if ( abs( m_pan_info.m_pan_base_mx - mx ) > s_pan_prepare_delta ||
                   abs( m_pan_info.m_pan_base_my - my ) > s_pan_prepare_delta ) {
                  const int curelements = getElements() - maxdisplayv + 1;
                  int ih = getInnerHeight();
                  const int iw = getInnerWidth();
                  int hh = ( showHeader == true ) ? getHeaderHeight() : 0;
                  // the header is part of the inner height
                  ih -= hh;

                  if ( abs( m_pan_info.m_pan_base_mx - mx ) > abs( m_pan_info.m_pan_base_my - my ) ) {
                      m_pan_info.m_pan_mode = m_pan_info.PAN_HORIZONTAL;

                      _aguix->setCursor( win, AGUIX::SCROLLH_CURSOR );

                      // mouse position areas
                      m_pan_info.init_mouse_positions( mx, iw );

                      // scroll positions
                      m_pan_info.init_scroll_positions( getXOffset(),
                                                        getMaxTextWidth() - iw,
                                                        iw );
                  } else {
                      m_pan_info.m_pan_mode = m_pan_info.PAN_VERTICAL;

                      _aguix->setCursor( win, AGUIX::SCROLLV_CURSOR );

                      // mouse position areas
                      m_pan_info.init_mouse_positions( my, ih );

                      // scroll positions
                      m_pan_info.init_scroll_positions( getYOffset(),
                                                        curelements,
                                                        maxdisplayv );
                  }
              }
          }

          if ( m_pan_info.m_pan_mode == m_pan_info.PAN_VERTICAL ) {
              setYOffset( m_pan_info.get_scroll_offset( m_pan_info.m_pan_mode, mx, my ) );
          } else if ( m_pan_info.m_pan_mode == m_pan_info.PAN_HORIZONTAL ) {
              setXOffset( m_pan_info.get_scroll_offset( m_pan_info.m_pan_mode, mx, my ) );
          }
      } else {
          updateScrollValues( mx, my );
      }
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == win ) &&
	 ( msg->button == _scroll_button ) ) {
      switch ( thirdMode ) {
	case THIRD_SCROLL:
	  _aguix->disableTimer();
	  _aguix->msgUnlock( this );
	  thirdMode = THIRD_IDLE;
          m_pan_info.m_pan_mode = m_pan_info.PAN_INACTIVE;
	  _aguix->unsetCursor( win );
	  break;
	default:
	  break;
      }
    }
  } else if ( msg->type == ClientMessage ) {
    if ( msg->specialType == Message::TIMEREVENT &&
         thirdMode == THIRD_SCROLL &&
         m_pan_info.m_pan_mode == m_pan_info.PAN_INACTIVE ) {
      if ( ( msg->time % scrollDelta ) == 0 ) {
        if ( getCurrentMouseButtonState( win ) & _scroll_buttonmask ) {
          const int oldx = getXOffset(), oldy = getYOffset();

          if ( scrollXDir < 0 ) setXOffset( xoffset - getHScrollStep(), true );
          else if ( scrollXDir > 0 ) setXOffset( xoffset + getHScrollStep(), true );
          if ( scrollYDir < 0 ) scrollV1( -1, true );
          else if ( scrollYDir > 0 ) scrollV1( 1, true );

          if ( getXOffset() != oldx || getYOffset() != oldy ) {
            redraw();
            _aguix->Flush();
          }
        }
      }
    }
  }
}

void FieldListView::handleHBar( Message *msg )
{
  if ( hbar == 0 ) return;
  if ( isCreated() == false ) return;
  int mx, my;
  int dir;
  int hx, hw, hh;
  int maxtextwidth = getMaxTextWidth();
  int tx, ty, tw, th;
  double b = 0;

  mx = msg->mousex;
  my = msg->mousey;

  hw = getInnerWidth();
  hh = hbar_height;
  hx = 0;

  // consider empty area after last field 
  int emptyArea = a_max( 0, getInnerWidth() - ( maxtextwidth - xoffset ) );
  maxtextwidth += emptyArea;

  tw = hw;
  th = hbar_height;
  ty = 0;
  tx = 0;
  tw -= 2 * th;  // Breite wenn alles anzeigbar ist
  th -= 2;
  ty++;
  if ( maxtextwidth > getInnerWidth() ) {
    int dw = tw * getInnerWidth();
    dw /= maxtextwidth;
    
    if ( dw < 10 ) dw = 10;
    
    int a = maxtextwidth - getInnerWidth();
    b = tw - dw;  // das was uebrig bleibt
    b /= a;            // verteilt sich auch die nichtsichtbaren Zeichen
    tx += hbar_height + (int)( xoffset * b );
    tw = dw;
  } else {
    tx += hbar_height;
  }
  
  if ( msg->type == ButtonPress ) {
    if ( hbarMode != HBAR_IDLE ) return;
    if ( msg->window == hbarwin ) {
      dir = 0;
      if ( msg->button == Button1 ) {
	if ( ( mx > hx ) && ( mx <= ( hx + hh ) ) ) {
	  // left
	  dir = -1;
	  hbar_dir = 1;
	  hbarMode = HBAR_SCROLL_LEFT;
	}
	if ( ( mx > ( hx + hw - hh ) ) && ( mx <= ( hx + hw ) ) ) {
	  // right
	  dir = 1;
	  hbar_dir = 2;
	  hbarMode = HBAR_SCROLL_RIGHT;
	}
      }

      if ( dir != 0 ) {
	vbarredraw();
	setXOffset( xoffset + dir * getHScrollStep() );
	_aguix->Flush();
	_aguix->msgLock( this );
	_aguix->enableTimer();
      } else if ( msg->button == Button4 ) {
	setXOffset( xoffset + ( -getInnerWidth() + 1 ) / 4 );
	_aguix->Flush();
      } else if ( msg->button == Button5 ) {
	setXOffset( xoffset + ( getInnerWidth() - 1 ) / 4 );
	_aguix->Flush();
      } else if ( ( mx >= tx ) &&
		  ( mx <= ( tx + tw ) ) &&
		  ( my >= ty ) &&
		  ( my <= ( ty + th ) ) &&
		  ( msg->button == Button1 ) ) {
	// the hbar-scroller is pressed
	if ( maxtextwidth > getInnerWidth() ) {
	  grabs = XGrabPointer( _aguix->getDisplay(),
				hbarwin,
				False,
				Button1MotionMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
				GrabModeAsync,
				GrabModeAsync,
				None,
				None,
				CurrentTime );
	  _aguix->setCursor( hbarwin, AGUIX::SCROLLH_CURSOR );
	  scrollDelta = mx - tx;
	  hbar_pressed = true;
	  hbarredraw();
	  _aguix->Flush();
	  hbarMode = HBAR_SCROLL;
	  _aguix->msgLock( this );
	}
      } else {
	if ( msg->button == Button1 ) {
	  if ( mx < tx ) {
	    setXOffset( xoffset - getInnerWidth() + 1 );
	  } else {
	    setXOffset( xoffset + getInnerWidth() - 1 );
	  }
	  _aguix->Flush();
	}
      }
    }
  } else if ( msg->type == MotionNotify ) {
    if ( ( msg->window == hbarwin ) &&
	 ( hbarMode == HBAR_SCROLL ) ) {
      int tmx;
      double f1;

      _aguix->queryPointer( hbarwin, &mx, &my );
      tmx = mx;
      tmx -= hbar_height;
      tmx -= scrollDelta;
      f1 = tmx / b;
      setXOffset( (int)f1 );
      //_aguix->Flush();
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == hbarwin ) &&
	 ( msg->button == Button1 ) ) {
      switch ( hbarMode ) {
	case HBAR_SCROLL_LEFT:
	case HBAR_SCROLL_RIGHT:
	  hbar_dir = 0;
	  hbarredraw();
	  _aguix->Flush();
	  _aguix->disableTimer();
	  _aguix->msgUnlock( this );
	  break;
	case HBAR_SCROLL:
	  _aguix->unsetCursor( hbarwin );
	  if ( grabs == GrabSuccess ) XUngrabPointer( _aguix->getDisplay(), CurrentTime );
	  hbar_pressed=false;
	  hbarredraw();
	  _aguix->Flush();
	  _aguix->msgUnlock( this );
	  break;
	default:
	  break;
      }
      hbarMode = HBAR_IDLE;
    }
  } else if ( msg->type == ClientMessage ) {
    if ( ( msg->specialType == Message::TIMEREVENT ) &&
	 ( hbarMode != HBAR_IDLE ) ) {
      if ( ( msg->time % 2 ) == 0 ) { //HARDCODED
        if ( getCurrentMouseButtonState( win ) & Button1Mask ) {
          switch ( hbarMode ) {
            case HBAR_SCROLL_LEFT:
              setXOffset( xoffset - getHScrollStep() );
              break;
            case HBAR_SCROLL_RIGHT:
              setXOffset( xoffset + getHScrollStep() );
              break;
            default:
              break;
          }
          _aguix->Flush();
        }
      }
    }
  }
  return;
}

void FieldListView::handleVBar(Message *msg)
{
  if ( vbar == 0 ) return;
  if ( isCreated() == false ) return;
  int mx, my;
  int dir;
  int vy, vw, vh;
  int tx, ty, tw, th;
  double b = 1;
  int curelements = getElements();

  mx = msg->mousex;
  my = msg->mousey;

  vh = getInnerHeight();
  vw = vbar_width;
  vy = 0;

  th = vh;
  tw = vbar_width;
  tx = 0;
  ty = 0;
  th -= 2 * tw;  // Hoehe wenn alles anzeigbar ist
  tw -= 2;
  tx++;
  ty++;
  if ( curelements > maxdisplayv ) {
    int dh = th * maxdisplayv;
    dh /= curelements;
    
    if ( dh < 10 ) dh = 10;
    
    int a = curelements - maxdisplayv;
    b = th - dh;     // das was uebrig bleibt
    b /= a;          // verteilt sich auch die nichtsichtbaren Zeichen
    ty += vbar_width + (int)( yoffset * b );
    th = dh;
  } else {
    ty += vbar_width;
  }

  if ( msg->type == ButtonPress ) {
    if ( vbarMode != VBAR_IDLE ) return;
    if ( msg->window == vbarwin ) {
      dir = 0;
      if ( msg->button == Button1 ) {
	if ( ( my > vy ) && ( my <= ( vy + vw ) ) ) {
	  // up
	  dir = -1;
	  vbar_dir = 1;
	  vbarMode = VBAR_SCROLL_UP;
	}
	if ( ( my > ( vy + vh - vw ) ) && ( my <= ( vy + vh ) ) ) {
	  // down
	  dir = 1;
	  vbar_dir = 2;
	  vbarMode = VBAR_SCROLL_DOWN;
	}
      }

      if ( dir != 0 ) {
	vbarredraw();
	scrollV1( dir );
	_aguix->Flush();
	_aguix->msgLock( this );
	_aguix->enableTimer();
      } else if ( msg->button == Button4 ) {
	scrollV( ( -maxdisplayv + 1 ) / 4 );
	_aguix->Flush();
      } else if ( msg->button == Button5 ) {
	scrollV(  ( maxdisplayv - 1 ) / 4 );
	_aguix->Flush();
      } else if ( ( mx >= tx ) &&
		  ( mx <= ( tx + tw ) ) &&
		  ( my >= ty ) &&
		  ( my <= ( ty + th ) ) &&
		  ( msg->button == Button1 ) ) {
	// vbar-scroller pressed
	if ( curelements > maxdisplayv ) {
	  grabs = XGrabPointer( _aguix->getDisplay(),
				vbarwin,
				False,
				Button1MotionMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
				GrabModeAsync,
				GrabModeAsync,
				None,
				None,
				CurrentTime );
	  _aguix->setCursor( vbarwin, AGUIX::SCROLLV_CURSOR );
	  scrollDelta = my - ty;
	  vbar_pressed = true;
	  vbarredraw();
	  _aguix->Flush();
	  vbarMode = VBAR_SCROLL;
	  _aguix->msgLock( this );
	}
      } else {
	if (msg->button == Button1 ) {
	  if ( my < ty ) {
	    scrollV( - maxdisplayv + 1 );
	  } else {
	    scrollV( maxdisplayv - 1 );
	  }
	  _aguix->Flush();
	}
      }
    }
  } else if ( msg->type == MotionNotify ) {
    if ( ( msg->window == vbarwin ) &&
	 ( vbarMode == VBAR_SCROLL ) ) {
      int tmy;
      double f1;

      _aguix->queryPointer( vbarwin, &mx, &my );
      tmy = my;
      tmy -= vbar_width;
      tmy -= scrollDelta;
      f1 = tmy / b;
      setYOffset( (int)f1 );
      //        _aguix->Flush();
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == vbarwin ) &&
	 ( msg->button == Button1 ) ) {
      switch ( vbarMode ) {
	case VBAR_SCROLL_UP:
	case VBAR_SCROLL_DOWN:
	  vbar_dir = 0;
	  vbarredraw();
	  _aguix->Flush();
	  _aguix->disableTimer();
	  _aguix->msgUnlock( this );
	  break;
	case VBAR_SCROLL:
	  _aguix->unsetCursor(vbarwin);
	  if ( grabs == GrabSuccess ) XUngrabPointer( _aguix->getDisplay(), CurrentTime );
	  vbar_pressed=false;
	  vbarredraw();
	  _aguix->Flush();
	  _aguix->msgUnlock( this );
	  break;
	default:
	  break;
      }
      vbarMode = VBAR_IDLE;
    }
  } else if ( msg->type == ClientMessage ) {
    if ( ( msg->specialType == Message::TIMEREVENT ) &&
	 ( vbarMode != VBAR_IDLE ) ) {
      if ( ( msg->time % 2 ) == 0 ) { //HARDCODED
        if ( getCurrentMouseButtonState( win ) & Button1Mask ) {
          switch ( vbarMode ) {
            case VBAR_SCROLL_UP:
              scrollV1( -1 );
              break;
            case VBAR_SCROLL_DOWN:
              scrollV1( 1 );
              break;
            default:
              break;
          }
          _aguix->Flush();
        }
      }
    }
  }
  return;
}

void FieldListView::scrollV( int delta, bool no_redraw )
{
  setYOffset( yoffset + delta, no_redraw );
}

void FieldListView::scrollV1( int dir, bool no_redraw )
{
  setYOffset( yoffset + dir, no_redraw );
}

void FieldListView::setSelectHandler( void (*nsh)( FieldListView*, int p ) )
{
  selecthandler = nsh;
}

void FieldListView::setSelectHandler( std::function< void( FieldListView*, int element ) > cb )
{
    m_select_handler = cb;
}

/*
 * showActive zeigt den aktiven Eintrag
 * sie tut nichts, wenn Eintrag sichtbar
 * ist der oberhalb, wird er oben als erstes Element sichtbar
 * ist er unterhalb, wird er als letztes sichtbar
 */
void FieldListView::showActive()
{
  if ( activerow >= 0 ) {
    if ( activerow < yoffset ) setYOffset( activerow );
    else if ( ( activerow - yoffset ) >= maxdisplayv ) setYOffset( activerow - maxdisplayv + 1 );
  }
}

/*
 * centerActive zentriert Ansicht auf aktiven Eintrag
 * egal, ob sichtbar oder nicht
 */
void FieldListView::centerActive()
{
  if ( activerow >= 0 ) {
    setYOffset( activerow - maxdisplayv / 2 );
  }
}

/*
 * setupBars
 *
 * will move/resize hbar/vbar
 */
void FieldListView::setupBars()
{
  int bw;

  if ( isCreated() == false ) return;

  bw = 1 + ( ( displayFocus == true ) ? 1 : 0 );

  if ( hbar != 0 ) {
    int tx, ty, tw, th;

    tw = getInnerWidth();
    th = hbar_height;

    if ( hbar == 1 ) ty = bw;
    else ty = _h - th - bw;

    if ( vbar == 1 ) tx = vbar_width + bw;
    else tx = bw;
    
    // atleast 10 pixel width
    if ( tw < 10 ) tw = 10;
    _parent->resizeSubWin( hbarwin, tw, th );
    _parent->moveSubWin( hbarwin, tx, ty );
  }
  if ( vbar != 0 ) {
    int tx, ty, tw, th;

    th = getInnerHeight();
    tw = vbar_width;

    if ( vbar == 1 ) tx = bw;
    else tx = _w - tw - bw;

    if ( hbar == 1 ) ty = hbar_height + bw;
    else ty = bw;
    
    // atleast 10 pixel height
    if ( th < 10 ) th = 10;
    _parent->resizeSubWin( vbarwin, tw, th );
    _parent->moveSubWin( vbarwin, tx, ty );
  }
}

int FieldListView::setFont( const char *fontname )
{
  font=_aguix->getFont(fontname);
  if(font==NULL) return -1;
  recalcMaxValues();
  setupBars();
  resetWin( true );
  return 0;
}

const char *FieldListView::getType() const
{
  return type;
}

bool FieldListView::isType(const char *qtype) const
{
  if(strcmp(type,qtype)==0) return true;
  return false;
}

bool FieldListView::isParent(Window child) const
{
  if ( isCreated() == true ) {
    if(child==win) return true;
    if(child==hbarwin) return true;
    if(child==vbarwin) return true;
  }
  return false;
}

void FieldListView::runSelectHandler( int p )
{
  if ( selecthandler != NULL ) ( selecthandler )( this, p );
  else if ( m_select_handler ) m_select_handler( this, p );
}

void FieldListView::showRow( int row, bool redraw )
{
  if ( ( row >=0 ) && ( row < elements ) ){
    if ( row < yoffset ) {
      if ( abs( row - yoffset ) == 1 ) scrollV1( -1, ( redraw == true ) ? false : true );
      else scrollV( row - yoffset, ( redraw == true ) ? false : true );
    } else if ( ( row - yoffset ) >= maxdisplayv ) {
      if ( abs( row - yoffset - maxdisplayv + 1 ) == 1 ) scrollV1( 1, ( redraw == true ) ? false : true );
      else scrollV( row - yoffset - maxdisplayv + 1, ( redraw == true ) ? false : true );
    }
  }
}

int FieldListView::getRowHeight() const
{
  if ( font == NULL ) return _aguix->getCharHeight();
  else return font->getCharHeight();
}

int FieldListView::getMBG() const
{
  return mbg;
}

void FieldListView::setMBG(int color)
{
  mbg = color;
  if ( isCreated() == true ) {
    /* TODO: eigentlich brauche ich hier nicht mehr SetWindowBG */
    //_aguix->SetWindowBG( win, mbg );
    clearNonElements();
  }
}

int FieldListView::getMaximumWidth()
{
  int s;
  int bw;

  bw = getBorderWidth();
  
  s = getMaxTextWidth() + ( ( vbar != 0 ) ? vbar_width : 0 ) + 2 * bw;
  return s;
}

int FieldListView::maximizeX()
{
  int s;

  s = getMaximumWidth();
  resize( s, getHeight() );
  return s;
}

int FieldListView::getMaximumHeight()
{
  int elem_height, s;
  int bw;

  bw = getBorderWidth();

  if ( font == NULL ) elem_height = _aguix->getCharHeight();
  else elem_height = font->getCharHeight();
  s = elem_height * getElements() +
    ( ( hbar != 0 ) ? hbar_height : 0 ) + 2 * bw +
    ( ( showHeader == true ) ? getHeaderHeight() : 0 );
  return s;
}

int FieldListView::maximizeY()
{
  int s;

  s = getMaximumHeight();
  resize( getWidth(), s );
  return s;
}

void FieldListView::handleMiddle(Message *msg)
{
  int j;
  int win_x, win_y;
  int newelement;
  AGMessage *agmsg;
  
  if ( isCreated() == false ) return;

  if ( msg->type == ButtonPress ) {
    if ( msg->window != win ) return;
    if ( middleMode != MIDDLE_IDLE ) return;

    j = getRowForMouseY( msg->mousey );
    if ( j < 0 ) return;
    if ( ( j + yoffset ) >= elements ) return;
    if ( j >= maxdisplayv ) return;

    clickLastSelectedRow = j + yoffset;
    clickFirstSelectedRow = clickLastSelectedRow;

    setActiveRow( clickLastSelectedRow );
    runSelectHandler( clickLastSelectedRow );
    
    middleMode = MIDDLE_HOLD;
    m_middle_first_timer_event = 0;
    m_middle_delayed_entry_pressed_disabled = false;

    _aguix->Flush();
    _aguix->msgLock( this );
    if ( s_enable_delayed_entry_pressed_event ) {
        _aguix->enableTimer();
    }
  } else if ( msg->type == MotionNotify ) {
    if ( ( msg->window == win ) &&
	 ( ( middleMode == MIDDLE_HOLD ) ||
	   ( middleMode == MIDDLE_SCROLL_DOWN ) ||
	   ( middleMode == MIDDLE_SCROLL_UP ) ) ) {
      _aguix->queryPointer( win, &win_x, &win_y );
      j = getRowForMouseY( win_y );
      if ( ( j < 0 ) && ( yoffset > 0 ) ) {
	newelement = yoffset - 1;
	if ( newelement >= 0 ) {
	  if ( middleMode != MIDDLE_SCROLL_UP ) {
	    scrollV1( -1 );
	    setActiveRow( newelement );
	    runSelectHandler( newelement );
	    clickLastSelectedRow = newelement;
	    middleMode = MIDDLE_SCROLL_UP;
	    _aguix->enableTimer();
            m_middle_delayed_entry_pressed_disabled = true;
	  }
	}
      } else if ( ( j >= maxdisplayv ) && ( ( yoffset + maxdisplayv ) < elements ) ) {
	newelement = yoffset + maxdisplayv;
	if ( newelement < elements ) {
	  if ( middleMode != MIDDLE_SCROLL_DOWN ) {
	    scrollV1( 1 );
	    setActiveRow( newelement );
	    runSelectHandler( newelement );
	    clickLastSelectedRow = newelement;
	    middleMode = MIDDLE_SCROLL_DOWN;
	    _aguix->enableTimer();
            m_middle_delayed_entry_pressed_disabled = true;
	  }
	}
      } else {
	if ( j < 0 ) j = 0;
	if ( ( j + yoffset ) >= elements ) {
	  j = elements - 1 - yoffset;
	}
	if ( ( j + yoffset ) != clickLastSelectedRow ) {
	  newelement = j + yoffset;
	  setActiveRow( newelement );
	  runSelectHandler( newelement );
	  clickLastSelectedRow = newelement;
          m_middle_delayed_entry_pressed_disabled = true;
	}
	if ( middleMode != MIDDLE_HOLD ) {
	  middleMode = MIDDLE_HOLD;
	}
        if ( m_middle_delayed_entry_pressed_disabled ) {
            _aguix->disableTimer();
        }
      }
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == win ) &&
	 ( msg->button == _activate_button ) ) {
      switch ( middleMode ) {
	case MIDDLE_IGNORE:
	  _aguix->msgUnlock( this );
	  middleMode = MIDDLE_IDLE;
	  break;
	case MIDDLE_SCROLL_UP:
	case MIDDLE_SCROLL_DOWN:
	case MIDDLE_HOLD:
	  _aguix->disableTimer();
	  agmsg = AGUIX_allocAGMessage();
	  agmsg->fieldlv.lv = this;
	  agmsg->fieldlv.row = clickFirstSelectedRow;
	  agmsg->fieldlv.time = msg->time;
	  agmsg->fieldlv.mouse = true;
	  agmsg->type = AG_FIELDLV_ONESELECT;
          msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );

          {
            int dc_row = _last_single_middle_row;
            Time dc_time = _last_single_middle_time;

            _last_single_middle_row = clickFirstSelectedRow;
            _last_single_middle_time = msg->time;

            if ( isValidRow( dc_row ) == true && clickFirstSelectedRow == dc_row ) {
              if ( _aguix->isDoubleClick( msg->time, dc_time ) == true ) {
                agmsg = AGUIX_allocAGMessage();
                agmsg->fieldlv.lv = this;
                agmsg->fieldlv.row = clickFirstSelectedRow;
                agmsg->fieldlv.time = msg->time;
                agmsg->fieldlv.mouse = true;
                agmsg->type = AG_FIELDLV_DOUBLECLICK;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
                _last_single_middle_row = -1;
                _last_single_middle_time = 0;
              }
            }
          }
 
	  _aguix->msgUnlock( this );
	  middleMode = MIDDLE_IDLE;
	  break;
	default:
	  break;
      }
    }
  } else if ( msg->type == ClientMessage ) {
    if ( msg->specialType == Message::TIMEREVENT ) {
      if ( ( msg->time % 1 ) == 0 ) { //HARDCODED
        if ( getCurrentMouseButtonState( win ) & _activate_buttonmask ) {
          if ( middleMode == MIDDLE_SCROLL_UP ) {
            newelement = clickLastSelectedRow - 1;
            if ( newelement >= 0 ) {
              scrollV1( -1 );
              setActiveRow( newelement );
              runSelectHandler( newelement );
              clickLastSelectedRow = newelement;
            }
          } else if ( middleMode == MIDDLE_SCROLL_DOWN ) {
            newelement = clickLastSelectedRow + 1;
            if ( newelement < elements ) {
              scrollV1( 1 );
              setActiveRow( newelement );
              runSelectHandler( newelement );
              clickLastSelectedRow = newelement;
            }
          } else if ( middleMode == MIDDLE_HOLD &&
                      s_enable_delayed_entry_pressed_event ) {
              if ( m_middle_first_timer_event == 0 ) {
                  m_middle_first_timer_event = msg->time;
              }

              if ( ! m_middle_delayed_entry_pressed_disabled &&
                   msg->time - m_middle_first_timer_event > 1 * AGUIX::TIMER_TICKS / 2 ) {
                  _aguix->msgUnlock( this );
                  middleMode = MIDDLE_IDLE;

                  agmsg = AGUIX_allocAGMessage();
                  agmsg->fieldlv.lv = this;
                  agmsg->fieldlv.row = clickLastSelectedRow;
                  agmsg->fieldlv.time = msg->time;
                  agmsg->fieldlv.mouse = true;
                  agmsg->type = AG_FIELDLV_ENTRY_PRESSED;
                  msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
              }
          }
          _aguix->Flush();
        }
      }
    }
  }
}

bool FieldListView::handleKeys(Message *msg)
{
  bool returnvalue=false;
  bool sendmsg = false;

  if ( isCreated() == false ) return false;
  if ( msg != NULL ) {
    //TODO2:
    // most important features:
    // 1.up/down
    //   should react like handleMiddle
    // 2.return for simulate doubleclick
    //   problem: there is no doubleclick-feature at all at the moment
    // 3.space for selecting/unselecting
    //   just like handleSelect
    if ( msg->type == KeyPress ) {
      if ( ( msg->key == XK_Up ) ||
	   ( msg->key == XK_Down ) ||
	   ( msg->key == XK_Home ) ||
	   ( msg->key == XK_End ) ||
	   ( msg->key == XK_Prior ) ||
	   ( msg->key == XK_Next ) ) {
	int pos;

	switch ( msg->key ) {
	case XK_Up:
	  if ( activerow > 0 )
	    pos = activerow - 1;
	  else
	    pos = 0;
	  break;
	case XK_Down:
	  if ( activerow < 0 )
	    pos = 0;
	  else if ( activerow >= ( elements - 1 ) )
	    pos = elements - 1;
	  else
	    pos = activerow + 1;
	  break;
	case XK_Home:
	  pos = 0;
	  break;
	case XK_End:
	  pos = elements - 1;
	  break;
	case XK_Prior:
	  if ( activerow < 0 )
	    pos = 0;
	  else {
	    pos = activerow - maxdisplayv + 1;
	    if ( pos < 0 )
	      pos = 0;
	  }
	  break;
	case XK_Next:
	  if ( activerow < 0 )
	    pos = 0;
	  else {
	    pos = activerow + maxdisplayv - 1;
	    if ( pos >= ( elements - 1 ) )
	      pos = elements - 1;
	  }
	  break;
	default:
	  pos = -1;
	  break;
	}
	if ( ( pos >= 0 ) && ( pos < elements ) ){
	  // now activate entry pos
	  setActiveRow( pos );
	  if ( ( ( pos - yoffset ) < 0 ) || ( ( pos - yoffset ) >= maxdisplayv ) )
	    showActive();
	  runSelectHandler( pos );
	  _aguix->Flush();
          sendmsg = true;

          returnvalue = true;
	}
      } else if ( msg->key == XK_space ) {
	if ( activerow >= 0 ) {
	  bool state = getSelect( activerow );
	  state = ( state == false ) ? true : false;
	  setSelect( activerow, state );
	  runSelectHandler( activerow );
	  _aguix->Flush();
          sendmsg = true;

          returnvalue = true;
	}
      } else if ( msg->key == XK_Return ) {
      }
    }
  }
  
  if ( sendmsg == true ) {
    AGMessage *agmsg = AGUIX_allocAGMessage();
    agmsg->fieldlv.lv = this;
    agmsg->fieldlv.row = activerow;  // since we only change things for
                                     // activerow with keys send this
    agmsg->fieldlv.time = time( NULL );
    agmsg->fieldlv.mouse = false;
    agmsg->type = AG_FIELDLV_ONESELECT;
    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
  }
  return returnvalue;
}

void FieldListView::setData( int row, int ndata )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  elementarray[ row ]->setData( ndata );
}

int FieldListView::getData( int row, int fallback_value ) const
{
  if ( row < 0 || row >= elements ) return fallback_value;
  return elementarray[ row ]->getData();
}

void FieldListView::setDataExt( int row, FieldLVRowData *ndata )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  elementarray[ row ]->setDataExt( ndata );
}

FieldLVRowData *FieldListView::getDataExt( int row ) const
{
  if ( ( row < 0 ) || ( row >= elements ) ) return NULL;
  return elementarray[ row ]->getDataExt();
}

void FieldListView::Content::setData( int ndata )
{
  data = ndata;
}

int FieldListView::Content::getData() const
{
  return data;
}

void FieldListView::Content::setDataExt(FieldLVRowData *ndata )
{
  if ( dataExt != NULL ) delete dataExt;
  dataExt = ndata;
}

FieldLVRowData *FieldListView::Content::getDataExt() const
{
  return dataExt;
}

bool FieldListView::isValidRow( int row ) const
{
  if ( ( row >= 0 ) && ( row < elements ) ) return true;
  return false;
}

int FieldListView::insertRow( int row )
{
  int i;

  if ( row < 0 ) return -1;
  if ( row >= elements ) return addRow();

  if ( elements == arraysize ) increaseArray( arraysize * 2 );

  for ( i = elements - 1; i >= row; i-- ) {
    elementarray[ i + 1 ] = elementarray[ i ];
  }

  elements++;
  elementarray[ row ] = new Content();

  if ( activerow >= row ) {
    // since we insert at row, activerow must be raised
    activerow++;
  }

  for ( i = 0; i < fields; i++ ) {
    if ( maxrow[i] >= row ) {
      // correct maximum row
      maxrow[i]++;
    }
  }

#if 0
  // deactivate select/middle mode
  selectModeIgnore();
  middleModeIgnore();
#endif

  m_vbar_rebuild_required = true;

  return row;
}

void FieldListView::swapRows( int row1, int row2 )
{
  Content *te;
  int i;

  if ( ( isValidRow( row1 ) != true ) || ( isValidRow( row2 ) != true ) ) return;
  
  te = elementarray[ row1 ];
  elementarray[ row1 ] = elementarray[ row2 ];
  elementarray[ row2 ] = te;

  if ( row1 == activerow ) {
    // row1 was active => make row2 active
    activerow = row2;
  } else if ( row2 == activerow ) {
    activerow = row1;
  }
  
  for ( i = 0; i < fields; i++ ) {
    if ( maxrow[i] == row1 ) {
      // row1 is the row with maximum length
      maxrow[i] = row2;
    } else if ( maxrow[i] == row2 ) {
      maxrow[i] = row1;
    }
  }

#if 0
  // deactivate select/middle mode
  selectModeIgnore();
  middleModeIgnore();
#endif

  m_vbar_rebuild_required = true;

  return;
}

void FieldListView::setPreColors( int row, precolor_t mode )
{
  if ( isValidRow( row ) == false ) return;

  switch( mode ) {
    case PRECOLOR_ONLYSELECT:
      setFG( row, CC_NORMAL, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELECT, s_default_element_colors.getFG( CC_SELECT ) );
      setFG( row, CC_ACTIVE, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELACT, s_default_element_colors.getFG( CC_SELECT ) );
      setBG( row, CC_NORMAL, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELECT, s_default_element_colors.getBG( CC_SELECT ) );
      setBG( row, CC_ACTIVE, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELACT, s_default_element_colors.getBG( CC_SELECT ) );
      break;
    case PRECOLOR_ONLYACTIVE:
      setFG( row, CC_NORMAL, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELECT, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_ACTIVE, s_default_element_colors.getFG( CC_SELECT ) );
      setFG( row, CC_SELACT, s_default_element_colors.getFG( CC_SELECT ) );
      setBG( row, CC_NORMAL, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELECT, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_ACTIVE, s_default_element_colors.getBG( CC_SELECT ) );
      setBG( row, CC_SELACT, s_default_element_colors.getBG( CC_SELECT ) );
      break;
    case PRECOLOR_NOTSELORACT:
      setFG( row, CC_NORMAL, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELECT, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_ACTIVE, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELACT, s_default_element_colors.getFG( CC_NORMAL ) );
      setBG( row, CC_NORMAL, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELECT, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_ACTIVE, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELACT, s_default_element_colors.getBG( CC_NORMAL ) );
      break;
    case PRECOLOR_NORMAL:
      setFG( row, CC_NORMAL, s_default_element_colors.getFG( CC_NORMAL ) );
      setFG( row, CC_SELECT, s_default_element_colors.getFG( CC_SELECT ) );
      setFG( row, CC_ACTIVE, s_default_element_colors.getFG( CC_ACTIVE ) );
      setFG( row, CC_SELACT, s_default_element_colors.getFG( CC_SELACT ) );
      setBG( row, CC_NORMAL, s_default_element_colors.getBG( CC_NORMAL ) );
      setBG( row, CC_SELECT, s_default_element_colors.getBG( CC_SELECT ) );
      setBG( row, CC_ACTIVE, s_default_element_colors.getBG( CC_ACTIVE ) );
      setBG( row, CC_SELACT, s_default_element_colors.getBG( CC_SELACT ) );
    default:
      break;
  }
}

/*
 * resetWin
 *
 * this will clear the window (only win, not the bars)
 * and if wanted a redraw is done
 */
void FieldListView::resetWin( bool doRedraw )
{
  if ( isCreated() == false ) return;
  if ( doRedraw == true ) {
    mredraw();
    redrawContent();
    redrawHeader();
    drawBuffer();
  } else {
    _aguix->ClearWin( win );
  }
}

int FieldListView::getRowForMouseY( int my ) const
{
  int r;
  
  r = ( my - oy ) / elementHeight;
  if ( ( my - oy ) < 0 ) {
    r--;
  }
  return r;
}

void FieldListView::drawBuffer()
{
  drawBuffer( 0, 0, _w, _h );
}

void FieldListView::drawBuffer( int dx, int dy, int dw, int dh )
{
  if ( isCreated() == true ) {
    if ( ( dx < 0 ) ||
	 ( dy < 0 ) ||
	 ( dx >= _w ) ||
	 ( dy >= _h ) ||
	 ( dw < 1 ) ||
	 ( dh < 1 ) ||
	 ( dw > ( _w - dx ) ) ||
	 ( dh > ( _h - dy ) ) ) return;

    _aguix->copyArea( buffer, win, dx, dy, dw, dh, dx, dy );
  }
}

void FieldListView::drawBuffer( int element )
{
  if ( ( element < 0 ) || ( element >= elements ) ) return;
  if ( ( element >= yoffset ) && ( element < ( yoffset + maxdisplayv ) ) ) {
    drawBuffer( ox, oy + ( element - yoffset ) * elementHeight, getInnerWidth(), elementHeight );
  }
}

void FieldListView::setDisplayFocus( bool nv )
{
  displayFocus = nv;
  recalcMaxValues();
  setupBars();
  resetWin( true );
}

bool FieldListView::getDisplayFocus() const
{
  return displayFocus;
}

int FieldListView::getInnerWidth() const
{
  int tw, bw;

  bw = getBorderWidth();

  tw = _w - 2 * bw;
  if ( vbar != 0 ) {
    tw -= vbar_width;
  }

  return ( tw < 0 ) ? 0 : tw;
}

int FieldListView::getInnerHeight() const
{
  //TODO: Muss ich showHeader beachten?
  //  Momentan lege ich Wert darauf, dass dies gerade nicht drin ist
  //  vbar Hoehe haengt davon ab
  int th, bw;

  bw = getBorderWidth();

  th = _h - 2 * bw;
  if ( hbar != 0 ) {
    th -= hbar_height;
  }

  return ( th < 0 ) ? 0 : th;
}

void FieldListView::doCreateStuff()
{
  GUIElement::doCreateStuff();
  if ( buffer == 0 ) {
    buffer = _aguix->createPixmap( win, _w, _h );
    mredraw();
  }
  recalcMaxValues();
  if ( hbarwin == 0 ) {
    if ( hbar != 0 ) hbarwin = _parent->getSubWindow( win, 0, 0, 5, 5 );
  }
  if ( vbarwin == 0 ) {
    if ( vbar != 0 ) vbarwin = _parent->getSubWindow( win, 0, 0, 5, 5 );  
  }
  setupBars();

  _parent->addWindowEvent( win, PointerMotionMask );
}

void FieldListView::doDestroyStuff()
{
  freeHeaderPixmap();
  freeVBarSelectBuffer();
  if ( buffer != 0 ) {
    _aguix->freePixmap( buffer );
    buffer = 0;
  }
  if ( hbarwin != 0 ) {
    if ( hbar != 0 ) {
      _parent->removeSubWin( hbarwin );
    }
    hbarwin = 0;
  }
  if ( vbarwin != 0 ) {
    if ( vbar != 0 ) {
      _parent->removeSubWin( vbarwin );
    }
    vbarwin = 0;
  }
  GUIElement::doDestroyStuff();
}

/*
 * cond_redraw()
 *
 * only redraw when column width or visible entry changed
 * at the moment this is only set be setText !
 */
void FieldListView::cond_redraw()
{
  if ( cond_changed == true ) {
    redraw();
    cond_changed = false;
  }
}

bool FieldListView::isRowVisible( int row ) const
{
  if ( ( row >=0 ) && ( row < elements ) ){
    if ( ( row >= yoffset ) && ( ( row - yoffset ) < maxdisplayv ) ) {
      return true;
    }
  }
  return false;
}

std::string FieldListView::getText( int row, int field ) const
{
  if ( isValidRow( row ) == false ) return "";
  if ( ( field < 0 ) || ( field >= fields ) ) return "";
  return elementarray[ row ]->getText( field );
}

void FieldListView::setShowHeader( bool nv )
{
  showHeader = nv;
  recalcMaxValues();

  // check yoffset
  if ( ( yoffset + maxdisplayv ) >= elements ) yoffset = elements - maxdisplayv;
  if ( yoffset < 0 ) yoffset = 0;

  if ( elements < maxdisplayv ) {
    // not all lines used so reset the unused elements
    clearNonElements();
  }

  resetWin( true );
  vbarredraw();
}

bool FieldListView::getShowHeader() const
{
  return showHeader;
}

int FieldListView::getHeaderHeight() const
{
  int hbw;

  hbw = 2;
  
  return elementHeight + 2 * hbw + 1;
}

void FieldListView::redrawHeader()
{
  //TODO: ausloesen innerhalb von Content?
  std::string s;
  int sx, ex, f, fakefield;
  int hh;
#if 0
  int vsx, vex;
#endif
  align_t usedAlign;
  int tw, tx;
  XRectangle clip_rect;
  int use_ox, use_oy;
  Pixmap use_pixmap;
  int text_x, text_y;
  
  if ( isCreated() == false ) return;
  if ( showHeader != true ) return;

  hh = getHeaderHeight();

  use_ox = ox;
  use_oy = oy;
  use_pixmap = buffer;
#if 1
  // enable this to use own pixmap
  if ( checkHeaderPixmap( getInnerWidth(), hh ) == 0 ) {
    use_ox = 0;
    use_oy = hh;
    use_pixmap = _header_pixmap;
  }
#endif

  _aguix->setFG( headerCols[1] );
  _aguix->FillRectangle( use_pixmap,
			use_ox,
			use_oy - hh,
			getInnerWidth(),
			hh - 1 );
  
  DrawableCont dc( _aguix, use_pixmap );
  sx = ex = 0;
  for ( f = 0; f < fields; f++ ) {
    // merge all fields with fieldtextmerged == true
    fakefield = f;
    ex = sx;
    for (;;) {
      ex += getEffectiveFieldWidth( fakefield );
      if ( fieldtextmerged[ fakefield ] == false ) break;
      if ( ( fakefield + 1 ) >= fields ) break;
      fakefield++;
    }
    
    if ( ex > sx ) {
      // only draw header for non-empty fields
      if ( ( sx < ( xoffset + getInnerWidth() ) ) && ( ex > xoffset ) ) {
	// field visible
#if 0
	vsx = a_max( sx, xoffset );
	vex = a_min( xoffset + getInnerWidth(), ex );

	_aguix->setFG( ( fieldtextclicked == f ) ? _aguix->getFaceCol_3d_dark() : _aguix->getFaceCol_3d_bright() );
	_aguix->DrawLine( use_pixmap,
			  use_ox + vsx - xoffset,
			  use_oy - hh,
			  use_ox + vex - xoffset - 1,
			  use_oy - hh );
	if ( ( sx >= xoffset ) && ( ( ex - sx ) >= 2 ) ) {
	  // left start visible
	  _aguix->DrawLine( use_pixmap,
			    use_ox + vsx - xoffset,
			    use_oy - hh + 1,
			    use_ox + vsx - xoffset,
			    use_oy - 2 );
	}
	_aguix->setFG( ( fieldtextclicked == f ) ? _aguix->getFaceCol_3d_bright() : _aguix->getFaceCol_3d_dark() );
	_aguix->DrawLine( use_pixmap,
			  use_ox + vsx - xoffset,
			  use_oy - 2,
			  use_ox + vex - xoffset - 1,
			  use_oy - 2 );
	if ( ( ex <= ( xoffset + getInnerWidth() ) ) && ( ( ex - sx ) >= 2 ) ) {
	  // right end visible
	  _aguix->DrawLine( use_pixmap,
			    use_ox + vex - xoffset - 1,
			    use_oy - hh + 1,
			    use_ox + vex - xoffset - 1,
			    use_oy - 2 );
	}
#else
	_aguix->drawBorder( use_pixmap, ( fieldtextclicked == f ) ? true : false, use_ox + sx - xoffset, use_oy - hh,
                        ex - sx, hh - 1, 0 );

    int text_ex = ex;
    
    if ( sort_hint.field == f && sort_hint.hint != HEADER_SORT_HINT_NONE ) {
        XPoint arrow[3];
        int ah = hh * 18 / 30;
        int dw = ( ah - 1 ) & ~1;
        dw = dw / 2;
        int ax = use_ox + ex - xoffset - 2 * dw - 5;
        int ay = use_oy - hh / 2 + dw - 1;

        if ( ax > use_ox + sx - xoffset ) {
            // only if it fits into header
            if ( sort_hint.hint == HEADER_SORT_HINT_UP ) {
                // up
                arrow[0].x = ax;
                arrow[0].y = ay;
                arrow[1].x = ax + 2 * dw;
                arrow[1].y = ay;
                arrow[2].x = ax + dw;
                arrow[2].y = ay - 2 * dw;
            } else {
                // down
                arrow[0].x = ax;
                arrow[0].y = ay - 2 * dw;
                arrow[1].x = ax + 2 * dw;
                arrow[1].y = ay - 2 * dw;
                arrow[2].x = ax + dw;
                arrow[2].y = ay;
            }

            //_aguix->setFG( headerCols[0] );
            // use gray instead of black
            _aguix->setFG( _aguix->getFaces().getAGUIXColor( "listview-sort-indicator" ) );

            _aguix->DrawTriangleFilled( use_pixmap,
                                        arrow[0].x, arrow[0].y,
                                        arrow[1].x, arrow[1].y,
                                        arrow[2].x, arrow[2].y );

            text_ex = ex - 2 * dw - 5;
        }
    }
#endif
	// this many characters are visible in the header
	s = fieldtext[f];
	tw = _aguix->getTextWidth( s.c_str(), font );
	usedAlign = fieldalign[ f ];

	if ( ( text_ex - sx - 2 * m_header_border_leftright ) < tw )  {
	  // column width smaller than text width
	  // so always use left alignment for better reading
	  usedAlign = ALIGN_LEFT;
	}
	
	clip_rect.x = a_max( sx + m_header_border_leftright - xoffset, 0 ) + use_ox;
	clip_rect.y = 0;

	tx = text_ex - m_header_border_leftright - xoffset;
	if ( tx > getInnerWidth() ) tx = getInnerWidth();
	tx = tx + use_ox - clip_rect.x;
	if ( tx < 0 ) tx = 0;
	clip_rect.width = tx;

	clip_rect.height = _h;
	_aguix->setClip( font, &dc, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height );

	if ( usedAlign == ALIGN_RIGHT ) {
	  text_x = use_ox + text_ex - m_header_border_leftright - xoffset - tw - 1;
	  text_y = use_oy - hh + m_header_border_topbottom;
	} else {
      text_x = use_ox + sx + m_header_border_leftright - xoffset;
	  text_y = use_oy - hh + m_header_border_topbottom;
	}

	_aguix->DrawText( dc, font, s.c_str(), text_x, text_y, headerCols[0] );
	_aguix->unclip( font, &dc );
      }
    }
    
    sx = ex;
    f = fakefield;
  }

  // now draw a dummy field if there is space left
  // there is no real need for this but it looks better
  if ( ex < ( getInnerWidth() + xoffset ) ) {
    // sx is now start pixel!
    sx = ex - xoffset;
    if ( sx < 0 ) sx = 0;
    ex = getInnerWidth() - 1;
    if ( ex < 0 ) ex = 0;
    if ( sx < ex ) {
      _aguix->setFG( headerCols[1] );
      _aguix->FillRectangle( use_pixmap,
			     use_ox + sx,
			     use_oy - hh,
			     ex - sx,
			     hh - 1 );
      _aguix->setFG( _aguix->getFaceCol_3d_bright() );
      _aguix->DrawLine( use_pixmap,
			use_ox + sx,
			use_oy - hh,
			use_ox + ex,
			use_oy - hh );
      _aguix->DrawLine( use_pixmap,
			use_ox + sx,
			use_oy - hh + 1,
			use_ox + sx,
			use_oy - 2 );
      _aguix->setFG( _aguix->getFaceCol_3d_dark() );
      _aguix->DrawLine( use_pixmap,
			use_ox + sx,
			use_oy - 2,
			use_ox + ex,
			use_oy - 2 );
      _aguix->DrawLine( use_pixmap,
			use_ox + ex,
			use_oy - hh + 1,
			use_ox + ex,
			use_oy - 2 );
    }
  }
  if ( use_pixmap != buffer ) {
    _aguix->copyArea( use_pixmap, buffer, 0, 0, getInnerWidth(), hh - 1, ox, oy - hh );
  }
}

/*
 * returns number of field clicked
 * returnvalue:
 *  -1 when outside field area
 *  fieldnumber for clicked field
 *    for merged fields it's the first one
 */
int FieldListView::getFieldForPoint( int mx, int my, int *start_x, int *end_x )
{
  int sx, ex, f, fakefield;
  int hh, vsx, vex;
  int found = -1;

  if ( isCreated() == false ) return -1;
  if ( showHeader != true ) return -1;

  hh = getHeaderHeight();

  if ( ( my < ( oy - hh ) ) || ( my >= oy ) ) return -1;
  if ( ( mx < ox ) || ( mx >= ( ox + getInnerWidth() ) ) ) return -1;

  sx = 0;
  for ( f = 0; f < fields; f++ ) {
    // merge all fields with fieldtextmerged == true
    fakefield = f;
    ex = sx;
    for (;;) {
      ex += getEffectiveFieldWidth( fakefield );
      if ( fieldtextmerged[ fakefield ] == false ) break;
      if ( ( fakefield + 1 ) >= fields ) break;
      fakefield++;
    }
    
    if ( ( sx < ( xoffset + getInnerWidth() ) ) && ( ex >= xoffset ) ) {
      // field visible
      vsx = a_max( sx, xoffset );
      vex = a_min( xoffset + getInnerWidth(), ex );
      if ( ( mx >= ( ox + vsx - xoffset ) ) &&
	   ( mx < ( ox + vex - xoffset ) ) ) {
	found = f;
	if ( start_x != NULL )
	  *start_x = sx;
	if ( end_x != NULL )
	  *end_x = ex;
	break;
      }
    }
  
    sx = ex;
    f = fakefield;
  }
  return found;
}

void FieldListView::handleHeader( Message *msg )
{
  int hh;
  int sx = -1, ex = -1, mx;
  int f, last_f = -1;
  int resizeable_f;
  bool do_redraw = true;

  if ( msg == NULL ) return;

  if ( ( msg->type != ButtonPress ) &&
       ( msg->type != ButtonRelease ) &&
       ( msg->type != MotionNotify ) ) return;
 
  if ( headerMode == HEADER_IDLE &&
       msg->button != Button1 &&
       msg->button != Button2 &&
       msg->button != Button3 &&
       msg->type != MotionNotify ) return;

  if ( showHeader != true ) return;

  // find field
  f = getFieldForPoint( msg->mousex, msg->mousey, &sx, &ex );
  if ( f < 0 ) {
    // if not found get last field to test for field drag
    last_f = getLastField( &sx, &ex );
  }

  mx = msg->mousex - ox + xoffset;
  
  if ( msg->type == ButtonPress ) {
    // first check whether the user clicked nearby the end of a field
    if ( mx >= sx && mx >= ( ex - 5 ) && mx <= ( ex + 5 ) ) {
      headerMode = HEADER_DRAG;
    } else if ( mx >= sx && mx < ex && mx < ( sx + 5 ) && f > 0 ) {
      // user clicked near the start of a field so consider dragging of previous field
      headerMode = HEADER_DRAG;
      f = findPreviousRealField( f );
    }

    if ( headerMode == HEADER_DRAG ) {
      if ( f < 0 ) {
	f = last_f;
      }
      if ( f >= 0 ) {
	// valid field
	drag_field = f;

	if ( _aguix->isDoubleClick( msg->time, last_drag_time ) == true
	     && drag_field == last_drag_clicked ) {
	  // double clicked
	  resizeable_f = findResizeableField( drag_field );

	  // set width to dynamic width
	  setFieldWidth( resizeable_f, -1 );

	  // reset headerMode
	  headerMode = HEADER_IDLE;
	  last_drag_time = 0;
	} else {
	  // no double click so prepare dragging
	  last_drag_time = msg->time;
	  drag_base_x = mx;
	  
	  resizeable_f = findResizeableField( drag_field );
          drag_base_w = getEffectiveFieldWidth( resizeable_f );
	  
	  // grab pointer to receive motion events
	  grabs = XGrabPointer( _aguix->getDisplay(),
				win,
				False,
				Button1MotionMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
				GrabModeAsync,
				GrabModeAsync,
				None,
				None,
				CurrentTime );
	  //_aguix->setCursor( win, AGUIX::SCROLLH_CURSOR );
	  _aguix->Flush();
	  _aguix->msgLock( this );
	}
      }
    } else {
      // field clicked
      fieldtextclicked = f;
      if ( f >= 0 ) {
	headerMode = HEADER_CLICK;
      }
    }
  } else if ( msg->type == MotionNotify && headerMode == HEADER_DRAG ) {
    // motion event in drag mode
    // calculate new width

    int my;
    _aguix->queryPointer( win, &mx, &my );
    mx = mx - ox + xoffset;
    
    int nw = drag_base_w + ( mx - drag_base_x );
    nw -= getFieldSpace( drag_field );
    if ( nw < 0 ) nw = 0;

    resizeable_f = findResizeableField( drag_field );
    if ( getFieldWidth( resizeable_f ) != nw ) {
      setFieldWidth( resizeable_f, nw );
    }

    // reset drag time to prevent double click
    last_drag_time = 0;
  } else if ( msg->type == MotionNotify ) {
    bool over_handle = false;

    if ( showHeader == true ) {
      if ( ( msg->mousey >= ( oy - getHeaderHeight() ) ) &&
	   ( msg->mousey < oy ) ) {
	if ( mx >= sx && mx >= ( ex - 5 ) && mx <= ( ex + 5 ) ) {
	  over_handle = true ;
	} else if ( mx >= sx && mx < ex && mx < ( sx + 5 ) && f > 0 ) {
	  over_handle = true ;
	}
      }
      if ( over_handle == true && header_cursor_set == false ) {
	_aguix->setCursor( win, AGUIX::SCROLLH_CURSOR );
	_aguix->Flush();
	header_cursor_set = true;
      } else if ( over_handle == false && header_cursor_set == true ) {
	_aguix->unsetCursor( win );
	_aguix->Flush();
	header_cursor_set = false;
      }
    }

    do_redraw = false;
  } else if ( msg->type == ButtonRelease && headerMode != HEADER_IDLE ) {
    if ( headerMode == HEADER_DRAG ) {
      // in drag mode ungrab pointer
      //_aguix->unsetCursor( win );
      if ( grabs == GrabSuccess ) XUngrabPointer( _aguix->getDisplay(), CurrentTime );
      _aguix->Flush();
      _aguix->msgUnlock( this );

      // store field clicked for double click test
      last_drag_clicked = drag_field;
    } else {
      // just a normal field click so send msg
      if ( fieldtextclicked == f ) {
	if ( ( f >= 0 ) && ( f < fields ) ) {
	  AGMessage *agmsg = AGUIX_allocAGMessage();
	  agmsg->fieldlv.lv = this;
	  agmsg->fieldlv.row = f;
	  agmsg->fieldlv.time = msg->time;
	  agmsg->fieldlv.mouse = true;
	  agmsg->fieldlv.button = msg->button;
	  agmsg->type = AG_FIELDLV_HEADERCLICKED;
          msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
	}
      }
    }
    fieldtextclicked = -1;
    headerMode = HEADER_IDLE;
  }

  if ( do_redraw ) {
      redrawHeader();

      hh = getHeaderHeight();
      drawBuffer( ox, oy - hh, getInnerWidth(), hh );
  }
}

void FieldListView::setHeaderFG( int col )
{
  headerCols[0] = col;
  redrawHeader();
}

void FieldListView::setHeaderBG( int col )
{
  headerCols[1] = col;
  redrawHeader();
}

int FieldListView::getHeaderFG() const
{
  return headerCols[0];
}

int FieldListView::getHeaderBG() const
{
  return headerCols[1];
}

void FieldListView::selectModeIgnore()
{
  if ( selectMode == SELECT_IDLE ) return;

  switch ( selectMode ) {
    case SELECT_SCROLL_DOWN:
    case SELECT_SCROLL_UP:
      _aguix->disableTimer();
      break;
    default:
      break;
  }
  selectMode = SELECT_IGNORE;
}

void FieldListView::middleModeIgnore()
{
  if ( middleMode == MIDDLE_IDLE ) return;

  switch ( middleMode ) {
    case MIDDLE_SCROLL_DOWN:
    case MIDDLE_SCROLL_UP:
      _aguix->disableTimer();
      break;
    default:
      break;
  }
  middleMode = MIDDLE_IGNORE;
}

int FieldListView::getHScrollStep() const
{
  //TODO some better value
  return 8;
}

void FieldListView::freeHeaderPixmap()
{
  if ( _header_pixmap != None ) {
    _aguix->freePixmap( _header_pixmap );
    _header_pixmap = None;
    _header_pixmap_w = _header_pixmap_h = -1;
  }
}

int FieldListView::checkHeaderPixmap( int pw, int ph )
{
  // checker whether existing pixmap matches width and height
  if ( _header_pixmap != None &&
       _header_pixmap_w == pw &&
       _header_pixmap_h == ph ) return 0;

  freeHeaderPixmap();
  if ( isCreated() == true ) {
    _header_pixmap = _aguix->createPixmap( win, pw, ph );
    if ( _header_pixmap != None ) {
      _header_pixmap_w = pw;
      _header_pixmap_h = ph;
      return 0;
    }
  }
  return 1;
}

int FieldListView::getBorderWidth() const
{
  int bw;

  bw = 1 + ( ( displayFocus == true ) ? 1 : 0 );

  return bw;
}

int FieldListView::findPreviousRealField( int field )
{
  int last_field = 0;

  for ( int f = 0; f < fields; f++ ) {
    if ( f >= field ) break;
    last_field = f;

    // skip all merge fields
    for (;; f++ ) {
      if ( fieldtextmerged[ f ] == false ) break;
      if ( ( f + 1 ) >= fields ) break;
    }
  }
  return ( last_field < 0 ) ? 0 : last_field;
}

/*
 * returns last number of field clicked
 * returnvalue:
 *  -1 when not available
 *  fieldnumber
 *    for merged fields it's the first one
 */
int FieldListView::getLastField( int *start_x, int *end_x )
{
  int sx, ex, f, fakefield;
  int last_field = -1, last_sx = 0, last_ex = 0;

  if ( isCreated() == false ) return -1;
  if ( showHeader != true ) return -1;

  sx = 0;
  for ( f = 0; f < fields; f++ ) {
    last_field = f;
    // merge all fields with fieldtextmerged == true
    fakefield = f;
    ex = sx;
    for (;;) {
      ex += getEffectiveFieldWidth( fakefield );
      if ( fieldtextmerged[ fakefield ] == false ) break;
      if ( ( fakefield + 1 ) >= fields ) break;
      fakefield++;
    }
    last_sx = sx;
    last_ex = ex;
    
    sx = ex;
    f = fakefield;
  }

  if ( start_x != NULL ) {
    *start_x = last_sx;
  }
  if ( end_x != NULL ) {
    *end_x = last_ex;
  }
  return last_field;
}

int FieldListView::findResizeableField( int f )
{
  int found_f;

  if ( f < 0 ) f = 0;

  // find first field of several merged fields
  while ( f > 0 && fieldtextmerged[f-1] == true ) f--;

  found_f = f;
  
  // now test for resizeable field
  for ( ; f < fields; f++ ) {
    if ( fieldtextResizeable[f] == true ) {
      found_f = f;
      break;
    }
    if ( fieldtextmerged[ f ] == false ) {
      // end of merge fields
      // so stop here
      break;
    }
  }
  
  return found_f;
}

int FieldListView::getFieldWidth( int field ) const
{
  if ( field < 0 || field >= fields ) return 0;
  return fieldwidth[ field ];
}

int FieldListView::getEffectiveFieldWidth( int field )
{
    if ( field < 0 || field >= fields ) return 0;

    int effective_width;

    if ( fieldwidth[ field ] < 0 ) {
        const int uw = getUsedWidth( field );

        if ( uw > 0 || ! m_hide_empty_field.at( field ) ) { 
            effective_width = uw + getFieldSpace( field );
        } else {
            effective_width = 0;
        }

        if ( getShowHeader() && getConsiderHeaderWidthForDynamicWidth() ) {
            int tw = getHeaderTextWidth( field );

            if ( tw > effective_width ) {
                effective_width = tw;
            }
        }
    } else if ( fieldwidth[ field ] == 0 && m_hide_empty_field.at( field ) ) {
        effective_width = 0;
    } else {
        effective_width = fieldwidth[ field ] + getFieldSpace( field );
    }

    return effective_width;
}

int FieldListView::changeButtonFunction( buttonfunction_t bf, int button, int mod )
{
  unsigned int *buttonvar = NULL, *maskvar = NULL, *modvar = NULL;

  if ( bf == SELECT_BUTTON ) {
    buttonvar = &_select_button;
    maskvar = &_select_buttonmask;
  } else if ( bf == ACTIVATE_BUTTON ) {
    buttonvar = &_activate_button;
    maskvar = &_activate_buttonmask;
    modvar = &_activate_mod;
  } else if ( bf == SCROLL_BUTTON ) {
    buttonvar = &_scroll_button;
    maskvar = &_scroll_buttonmask;
    modvar = &_scroll_mod;
  } else if ( bf == ENTRY_PRESSED_MSG_BUTTON ) {
    buttonvar = &_entry_pressed_msg_button;
    maskvar = &_entry_pressed_msg_buttonmask;
    modvar = &_entry_pressed_msg_mod;
  } else return 1;

  switch ( button ) {
    case Button1:
      *buttonvar = Button1;
      *maskvar = Button1Mask;
      break;
    case Button2:
      *buttonvar = Button2;
      *maskvar = Button2Mask;
      break;
    case Button3:
      *buttonvar = Button3;
      *maskvar = Button3Mask;
      break;
  }
  
  if ( modvar != NULL ) {
      *modvar = KEYSTATEMASK( mod );
  }
  
  return 0;
}

int FieldListView::changeSelectMode( mouse_select_t nm )
{
  _select_mode = nm;
  return 0;
}

int FieldListView::changeHScrollMode( bool nv )
{
    if ( nv ) {
        s_hscroll_left_button = 6;
        s_hscroll_right_button = 7;
    } else {
        s_hscroll_left_button = 0;
        s_hscroll_right_button = 0;
    }
    return 0;
}

void FieldListView::changeDelayedEntryPressedEvent( bool nv )
{
    s_enable_delayed_entry_pressed_event = nv;
}

int FieldListView::singleSelectRow( int row, bool state )
{
  int i, selected = 0;
  bool state_inv = ( state == false ) ? true : false;

  if ( isValidRow( row ) == false ) return 0;

  for ( i = 0; i < elements; i++ ) {
    if ( i == row ) continue;
    
    if ( getSelect( i ) != state_inv ) {
      setSelect( i, state_inv );
      runSelectHandler( i );
      selected++;
    }
  }

  setSelect( row, state );
  setActiveRow( row );
  runSelectHandler( row );
  selected++;

  return selected;
}

unsigned int FieldListView::getCurrentMouseButtonState( Window twin )
{
  if ( twin != win && twin != vbarwin && twin != hbarwin ) return 0;

  unsigned int buttons;
  int mx, my;
  
  _aguix->queryPointer( twin, &mx, &my, &buttons );
  return buttons;
}

FieldListView::ColorDef::ColorDef()
{
  // default colors
  fg[ 0 ] = 1;
  fg[ 1 ] = 2;
  fg[ 2 ] = 1;
  fg[ 3 ] = 2;
  bg[ 0 ] = 0;
  bg[ 1 ] = 1;
  bg[ 2 ] = 7;
  bg[ 3 ] = 7;
}

FieldListView::ColorDef::~ColorDef()
{
}

void FieldListView::ColorDef::setFG( colorclass_t cc, int col )
{
  if ( cc < 0 || cc > 3 ) return;
  fg[cc] = col;
}

int FieldListView::ColorDef::getFG( colorclass_t cc ) const
{
  if ( cc < 0 || cc > 3 ) return 0;
  return fg[cc];
}

void FieldListView::ColorDef::setBG( colorclass_t cc, int col )
{
  if ( cc < 0 || cc > 3 ) return;
  bg[cc] = col;
}

int FieldListView::ColorDef::getBG( colorclass_t cc ) const
{
  if ( cc < 0 || cc > 3 ) return 0;
  return bg[cc];
}

void FieldListView::setColor( int row, const ColorDef &col )
{
  if ( ( row < 0 ) || ( row >= elements ) ) return;
  
  elementarray[ row ]->setFG( CC_NORMAL, col.getFG( CC_NORMAL ) );
  elementarray[ row ]->setFG( CC_ACTIVE, col.getFG( CC_ACTIVE ) );
  elementarray[ row ]->setFG( CC_SELECT, col.getFG( CC_SELECT ) );
  elementarray[ row ]->setFG( CC_SELACT, col.getFG( CC_SELACT ) );
  elementarray[ row ]->setBG( CC_NORMAL, col.getBG( CC_NORMAL ) );
  elementarray[ row ]->setBG( CC_ACTIVE, col.getBG( CC_ACTIVE ) );
  elementarray[ row ]->setBG( CC_SELECT, col.getBG( CC_SELECT ) );
  elementarray[ row ]->setBG( CC_SELACT, col.getBG( CC_SELACT ) );
}

FieldListView::ColorDef FieldListView::getColor( int row ) const
{
  ColorDef col;
  
  if ( ( row < 0 ) || ( row >= elements ) ) return col;
  
  col.setFG( CC_NORMAL, elementarray[ row ]->getFG( CC_NORMAL ) );
  col.setFG( CC_ACTIVE, elementarray[ row ]->getFG( CC_ACTIVE ) );
  col.setFG( CC_SELECT, elementarray[ row ]->getFG( CC_SELECT ) );
  col.setFG( CC_SELACT, elementarray[ row ]->getFG( CC_SELACT ) );
  col.setBG( CC_NORMAL, elementarray[ row ]->getBG( CC_NORMAL ) );
  col.setBG( CC_ACTIVE, elementarray[ row ]->getBG( CC_ACTIVE ) );
  col.setBG( CC_SELECT, elementarray[ row ]->getBG( CC_SELECT ) );
  col.setBG( CC_SELACT, elementarray[ row ]->getBG( CC_SELACT ) );
  return col;
}

bool FieldListView::checkForValidButtonMod( unsigned int button,
                                            unsigned int keystate,
                                            unsigned int matching_mod )
{
    if ( KEYSTATEMASK( keystate ) == matching_mod ) return true;
    if ( matching_mod != 0 ) return false;
    
    if ( button == _activate_button &&
         KEYSTATEMASK( keystate ) == _activate_mod ) return false;
    
    if ( button == _scroll_button &&
         KEYSTATEMASK( keystate ) == _scroll_mod ) return false;

    if ( button == _entry_pressed_msg_button &&
         KEYSTATEMASK( keystate ) == _entry_pressed_msg_mod ) return false;
    
    return true;
}

void FieldListView::handleEntryPressed( Message *msg )
{
    int j;
    int element;
    AGMessage *agmsg;
    
    if ( isCreated() == false ) return;
    
    if ( msg->type == ButtonPress ) {
        if ( msg->window != win ) return;
        
        j = getRowForMouseY( msg->mousey );
        if ( j < 0 ) return;
        if ( j >= maxdisplayv ) return;
        
        element = j + yoffset;
        if ( isValidRow( element ) == false ) {
            element = -1;
        } else {
            //setActiveRow( element );
            //runSelectHandler( element );
        }
        
        agmsg = AGUIX_allocAGMessage();
        agmsg->fieldlv.lv = this;
        agmsg->fieldlv.row = element;
        agmsg->fieldlv.time = msg->time;
        agmsg->fieldlv.mouse = true;
        agmsg->type = AG_FIELDLV_ENTRY_PRESSED;
        msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
    }
}

void FieldListView::simpleRedrawUpdatedRows()
{
    if ( m_complete_update == true ) {
        m_complete_update = false;
        m_updated_rows.clear();
        cond_redraw();
    } else {
        for ( std::set<int>::const_iterator it1 = m_updated_rows.begin();
              it1 != m_updated_rows.end();
              it1++ ) {
            redrawLine( *it1 );
            drawBuffer( *it1 );
        }
        m_updated_rows.clear();
    }
}

// just a little helper function to limit redraws to approx. 25 fps
// for applications with a lot of updates
void FieldListView::timeLimitedRedraw()
{
    struct timeval now;
    double diff;
    
    gettimeofday( &now, NULL );
    diff = now.tv_sec + now.tv_usec / 1000000.0;
    diff -= m_last_update_time.tv_sec + m_last_update_time.tv_usec / 1000000.0;

    // hard coded FPS limit
    if ( diff >= ( 1.0 / 25.0 ) ) {
        redraw();
        m_last_update_time = now;
    }
}

void FieldListView::setGlobalFieldSpace( int spacing )
{
    if ( spacing >= 0 ) {
        m_global_field_spacing = spacing;
    }
}

int FieldListView::getFieldSpace( int field ) const
{
    // the field space is returned even if m_hide_empty_field is set
    // which actually makes it zero width. So the field is effectively
    // smaller than the field space which must be considered when
    // using this value.
    if ( m_field_spacing.at( field ) >= 0 ) {
        return m_field_spacing.at( field );
    }

    return m_global_field_spacing;
}

void FieldListView::setFieldSpace( int field, int spacing )
{
    m_field_spacing.at( field ) = spacing;
}

void FieldListView::setHighlightSegments( int row, int field, const std::vector< size_t > &segments )
{
    if ( row < 0 || row >= elements ) return;
    if ( field < 0 ) return;

    elementarray[ row ]->setHighlightSegments( field, segments );

    if ( isRowVisible( row ) == true ) {
        cond_changed = true;
    }
}

const std::vector< size_t > *FieldListView::getHighlightSegments( int row, int field ) const
{
    if ( row < 0 || row >= elements ) return NULL;
    if ( field < 0 ) return NULL;

    return elementarray[ row ]->getHighlightSegments( field );
}

void FieldListView::clearHighlightSegments()
{
    for ( int row = 0; row < elements; row++ ) {
        elementarray[ row ]->clearHighlightSegments();
    }
}

void FieldListView::sendScrollEvent( int type )
{
    if ( type == AG_FIELDLV_VSCROLL ) {
        AGMessage *agmsg = AGUIX_allocAGMessage();
        agmsg->fieldlv.lv = this;
        agmsg->fieldlv.row = getYOffset();
        agmsg->fieldlv.time = time( NULL );
        agmsg->fieldlv.mouse = true;
        agmsg->type = type;
        msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
    } else if ( type == AG_FIELDLV_HSCROLL ) {
        AGMessage *agmsg = AGUIX_allocAGMessage();
        agmsg->fieldlv.lv = this;
        agmsg->fieldlv.row = getXOffset();
        agmsg->fieldlv.time = time( NULL );
        agmsg->fieldlv.mouse = true;
        agmsg->type = type;
        msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
    }
}

int FieldListView::getHeaderTextWidth( int field )
{
    if ( field < 0 || field >= fields ) return 0;

    if ( getFieldText( field ).empty() ) return 0;

    if ( m_header_text_width.at( field ) == 0 ) {
        int tw = _aguix->getTextWidth( getFieldText( field ).c_str(), font ) + m_header_border_leftright * 2;

        m_header_text_width.at( field ) = tw;
    }

    return m_header_text_width.at( field );
}

void FieldListView::setConsiderHeaderWidthForDynamicWidth( bool nv )
{
    //TODO also optionally include sort hint
    m_consider_header_for_dynamic_width = nv;
}

bool FieldListView::getConsiderHeaderWidthForDynamicWidth() const
{
    return m_consider_header_for_dynamic_width;
}

void FieldListView::setDefaultColorMode( precolor_t mode )
{
    m_default_color_mode = mode;
}

void FieldListView::initStaticDefaultColors()
{
    int c = _aguix->getFaces().getColor( "listview-normal-fg" );
    if ( c < 0 ) c = 1;
    s_default_element_colors.setFG( CC_NORMAL, c );

    c = _aguix->getFaces().getColor( "listview-select-fg" );
    if ( c < 0 ) c = 2;
    s_default_element_colors.setFG( CC_SELECT, c );

    c = _aguix->getFaces().getColor( "listview-active-fg" );
    if ( c < 0 ) c = 1;
    s_default_element_colors.setFG( CC_ACTIVE, c );

    c = _aguix->getFaces().getColor( "listview-selact-fg" );
    if ( c < 0 ) c = 2;
    s_default_element_colors.setFG( CC_SELACT, c );

    c = _aguix->getFaces().getColor( "listview-normal-bg" );
    if ( c < 0 ) c = 0;
    s_default_element_colors.setBG( CC_NORMAL, c );

    c = _aguix->getFaces().getColor( "listview-select-bg" );
    if ( c < 0 ) c = 1;
    s_default_element_colors.setBG( CC_SELECT, c );

    c = _aguix->getFaces().getColor( "listview-active-bg" );
    if ( c < 0 ) c = 7;
    s_default_element_colors.setBG( CC_ACTIVE, c );

    c = _aguix->getFaces().getColor( "listview-selact-bg" );
    if ( c < 0 ) c = 7;
    s_default_element_colors.setBG( CC_SELACT, c );
}

void FieldListView::setOnDemandCallback( std::function< void( FieldListView*,
                                                              int row,
                                                              int field,
                                                              struct on_demand_data &data ) > cb )
{
    m_ondemand_callback = cb;
}

void FieldListView::clearOnDemandDataAvailableFlag()
{
    for ( int i = 0; i < elements; i++ ) {
        clearOnDemandDataAvailableFlag( i );
    }
}

void FieldListView::clearOnDemandDataAvailableFlag( int row )
{
    if ( row < 0 || row >= elements ) return;

    elementarray[ row ]->setOnDemandDataAvailable( false );
}

void FieldListView::setSortHint( int field, header_sort_hint_t hint )
{
    if ( field < 0 || field >= fields ) {
        sort_hint.field = -1;
    } else {
        sort_hint.field = field;
        sort_hint.hint = hint;
    }

    redrawHeader();
}

void FieldListView::unsetSortHint()
{
    sort_hint.field = -1;

    redrawHeader();
}

void FieldListView::setStrikeOut( int row, int field, bool strike_out )
{
    if ( row < 0 || row >= elements ) return;
    elementarray[ row ]->setStrikeOut( field, strike_out );
}

bool FieldListView::getStrikeOut( int row, int field ) const
{
    if ( row < 0 || row >= elements ) return false;
    return elementarray[ row ]->getStrikeOut( field );
}

void FieldListView::setStrikeOutStart( int row, int field, size_t strike_out_start )
{
    if ( row < 0 || row >= elements ) return;
    elementarray[ row ]->setStrikeOutStart( field, strike_out_start );
}

size_t FieldListView::getStrikeOutStart( int row, int field ) const
{
    if ( row < 0 || row >= elements ) return 0;
    return elementarray[ row ]->getStrikeOutStart( field );
}

int FieldListView::getRowYPosition( int element )
{
    if ( isCreated() == false ) return -1;
    if ( element < 0 || element >= elements ) return -1;
    if ( element < yoffset || element >= ( yoffset + maxdisplayv ) ) return -1;

    int vis = element - yoffset;

    int ty = vis * elementHeight + oy;

    return ty;
}

void FieldListView::drawSelectStateVBar()
{
    if ( ! m_show_selection_in_vbar ) return;

    int th = getInnerHeight();
    int tw = vbar_width;

    if ( th <= 4 ) return;
    th -= 4;

    if ( tw <= 2 ) return;
    tw -= 2;
    
    int tx = 1;
    int ty = tw + 2;

    if ( th <= 2 * tw ) return;
    th -= 2 * tw;

    int check_res = checkVBarSelectBuffer( tw, th );

    if ( m_vbar_selectbuffer != None ) {
        if ( check_res == 1 || m_vbar_rebuild_required ) {
            m_vbar_select_dirty.resize( th );

            m_vbar_select_count.clear();
            m_vbar_select_count.resize( th );
        
            for ( int e = 0; e < elements; e++ ) {
                updateSelectState( e, true );
            }
      
            _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg2" ) );
            _aguix->FillRectangle( m_vbar_selectbuffer,
                                   0, 0,
                                   m_vbar_selectbuffer_w,
                                   m_vbar_selectbuffer_h );
            _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-selection" ) );

            for ( int sel_y = 0; sel_y < th; sel_y++ ) {
                if ( m_vbar_select_count[ sel_y ] > 0 ) {
                    _aguix->DrawLine( m_vbar_selectbuffer,
                                      2, sel_y,
                                      tw - 3, sel_y );
                }
            }

            m_vbar_rebuild_required = false;
        } else if ( m_vbar_select_some_dirty ) {
            for ( int sel_y = 0; sel_y < th; sel_y++ ) {
                if ( m_vbar_select_dirty[ sel_y ] ) {
                    if ( m_vbar_select_count[ sel_y ] > 0 ) {
                        _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-selection" ) );
                    } else {
                        _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg2" ) );
                    }

                    _aguix->DrawLine( m_vbar_selectbuffer,
                                      2, sel_y,
                                      tw - 3, sel_y );

                    m_vbar_select_dirty[ sel_y ] = false;
                }
            }
        }

        _aguix->copyArea( m_vbar_selectbuffer, vbarwin, 0, 0, tw, th, tx, ty  );
    }

    m_vbar_select_some_dirty = false;
}

void FieldListView::freeVBarSelectBuffer()
{
    if ( m_vbar_selectbuffer != None ) {
        _aguix->freePixmap( m_vbar_selectbuffer );
        m_vbar_selectbuffer = None;
        m_vbar_selectbuffer_w = m_vbar_selectbuffer_h = -1;
    }
}

int FieldListView::checkVBarSelectBuffer( int tw, int th )
{
    if ( m_vbar_selectbuffer != None &&
         m_vbar_selectbuffer_w == tw &&
         m_vbar_selectbuffer_h == th ) return 0;

    freeVBarSelectBuffer();

    if ( isCreated() == true ) {
        m_vbar_selectbuffer = _aguix->createPixmap( win, tw, th );
        if ( m_vbar_selectbuffer != None ) {
            m_vbar_selectbuffer_w = tw;
            m_vbar_selectbuffer_h = th;
            return 1;
        }
    }

    return -1;
}

void FieldListView::updateSelectState( int element, bool only_selected )
{
    if ( ! m_show_selection_in_vbar ) return;

    if ( element < 0 || element >= elements ) return;

    int th = getInnerHeight() - 4;
    int tw = vbar_width - 2;

    th -= 2 * tw;

    if ( m_vbar_select_count.size() != (size_t)th ) return;

    size_t pixels_pos =  element * th / elements;
    size_t next_pixels_pos = ( element + 1 ) * th / elements;
    if ( next_pixels_pos == pixels_pos ) {
        next_pixels_pos++;
    }

    m_vbar_select_dirty.resize( th );

    if ( elementarray[ element ]->getSelect() == true ) {
        for ( size_t p = pixels_pos;
              p < next_pixels_pos && p < (size_t)th; p++ ) {
            m_vbar_select_count.at( p )++;

            if ( m_vbar_select_count.at( p ) == 1 ) {
                m_vbar_select_dirty.at( p ) = true;
                m_vbar_select_some_dirty = true;
            }
        }
    } else if ( ! only_selected ) {
        for ( size_t p = pixels_pos;
              p < next_pixels_pos && p < (size_t)th; p++ ) {
            m_vbar_select_count.at( p )--;

            if ( m_vbar_select_count.at( p ) == 0 ) {
                m_vbar_select_dirty.at( p ) = true;
                m_vbar_select_some_dirty = true;
            } else if ( m_vbar_select_count.at( p ) < 0 ) {
                m_vbar_select_count.at( p ) = 0;
            }
        }
    }
}

void FieldListView::setShowSelectionInVBar( bool nv )
{
    m_show_selection_in_vbar = nv;
}

void FieldListView::setHideEmptyField( int field, bool v )
{
    m_hide_empty_field.at( field ) = v;
}
