/////////////////////////////////////////////////////////////////////////////
// Name:        wxGenericListCtrl
// Purpose:     
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/07/27
// RCS-ID:      $Id: glist.cpp,v 1.3 2001/04/21 11:48:18 georgetasker Exp $
// Copyright:   (C) Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// PS: Mark, these 2-blank spacings suck! Isntead of single TAB
//     i have to do twiche SPACE - sorta beats off my fingertips
//	   in a long run.
    
#ifdef __GNUG__
    #pragma implementation "genlist.cpp"
    #pragma interface "genlist.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "wx/settings.h"

#include "wx/wspp/glist.h"
#include <stdlib.h> // qsort()

static wxString IntToStr(int i)
{
  char buf[50];
  sprintf(buf, "%d", i);

  return wxString(buf);
}
/* not used
static wxString DoubleToStr(double d)
{
 char buf[50];
 sprintf(buf, "%f", (float)d);

 return buf;
}
*/
/***** Implementation for class wxListModel *****/

static int simple_list_compare_descending(wxListModel* pList, int row1, int row2, int col)
{
  return pList->GetValue(row1, col).CompareTo(pList->GetValue(row2, col));
}

static int simple_list_compare_acending(wxListModel* pList, int row1, int row2, int col)
{
  return pList->GetValue(row2, col).CompareTo(pList->GetValue(row1, col));
}


wxListModel::wxListModel()

  : mRowCount(1000),
    mAscSortFn(simple_list_compare_acending),
    mDescSortFn(simple_list_compare_descending)
{}

wxListModel::~wxListModel()
{}

void wxListModel::PrepareRow(int row)
{
  /* nothing */
}

const wxString wxListModel::GetValue(int row, int col)
{
  return IntToStr(row+1) + "-" + IntToStr(col+1);
}

int wxListModel::GetRowCount()
{
  return mRowCount;
}								  

int wxListModel::GetFinalRowCount()
{
  return GetRowCount();
}

void wxListModel::SetRowCount(int count)
{
  mRowCount = count;
}

void wxListModel::Sort(size_t byColumn, bool acending) {}

void wxListModel::Sort(size_t byColumn, ListModelCompareT sortFn) {}

void wxListModel::SwapRows(int row1, int row2) {}

void wxListModel::SetAscendingSortFunctino(ListModelCompareT ascFn)
{
  mAscSortFn = ascFn;
}

void wxListModel::SetDescendingSortFunction(ListModelCompareT descFn)
{
  mDescSortFn = descFn;
}

int wxListModel::GetImageForRow(int row)
{
  return -1;
}

/***** Implementation for class wxSimpleListModel *****/

wxSimpleListModel::wxSimpleListModel()

{
  mRowCount = 0;
  mColCount = 0;
}

wxSimpleListModel::~wxSimpleListModel()
{
  WX_CLEAR_ARRAY(mValues);
}

void wxSimpleListModel::SetColumnCount(int cols)
{
  mColCount = cols;
}

int wxSimpleListModel::GetColumnCount()
{
  return mColCount;
}

void wxSimpleListModel::AppendRow()
{
 // mValues.Alloc(mColCount);

  for(int i = 0; i != mColCount; ++i)

    mValues.Add(new wxString());

  mRowCount++;
}

void wxSimpleListModel::AppendRows(int nRows)
{
  int n = nRows * mColCount;
  
//  mValues.Alloc(n);

  for(int i = 0; i != n; ++i)

    mValues.Add(new wxString());

  mRowCount += nRows;
}

void wxSimpleListModel::SetValue(const char* value, int row, int col)
{
  *mValues[ row * mColCount + col ] = value;
}

const wxString wxSimpleListModel::GetValue(int row, int col)
{
  return *mValues[ row * mColCount + col ];
}


ListModelCompareT _gCompFn;
int _gSortCol;
wxListModel* _gpSortModel = NULL;
void* _gListBase = NULL;
int _gElementWidth = 0;

static int _internal_compare_function(const void* e1, const void* e2)
{
  _gpSortModel->GetValue(0, 0);

  int row1 = (int)((char*)e1 - (char*)_gListBase) / _gElementWidth;
  int row2 = (int)((char*)e2 - (char*)_gListBase) / _gElementWidth;

  return _gCompFn(_gpSortModel, row1, row2, _gSortCol);
}

void wxSimpleListModel::Sort(size_t byColumn, bool acending) 
{
  if (acending)

    Sort(byColumn, mAscSortFn);
  else
    Sort(byColumn, mDescSortFn);
}

void wxSimpleListModel::Sort(size_t byColumn, ListModelCompareT compFn) 
{
  if (mValues.Count() <= 1) return;

  _gCompFn = compFn;
  _gSortCol = byColumn;
  _gpSortModel = this;
  wxString** pp = &mValues[0];
  _gListBase = (void*)pp;
  _gElementWidth = mColCount * sizeof(long);

  int cnt = mValues.Count() / mColCount;

  qsort(pp, cnt, _gElementWidth,
       _internal_compare_function);
}

void wxSimpleListModel::SwapRows(int row1, int row2)
{
  int pos1 = row1 * mColCount, pos2 = row2 * mColCount;

  for(int i = 0; i != mColCount; ++i)
  {
    wxString* tmp = mValues[pos1+i];
    mValues[pos1+i] = mValues[pos2+i];

    mValues[pos2+i] = tmp;
  }
}

/***** Implementation for class wxGenericListCtrl *****/

IMPLEMENT_CLASS(wxGenericListCtrl, wxScrolledWindow)

BEGIN_EVENT_TABLE(wxGenericListCtrl, wxScrolledWindow)

  EVT_ERASE_BACKGROUND(wxGenericListCtrl::OnEraseBackground)

  EVT_PAINT(wxGenericListCtrl::OnPaint)
  EVT_LEFT_DOWN(wxGenericListCtrl::OnLeftDown)
  EVT_MOTION(wxGenericListCtrl::OnMotion)
  EVT_LEFT_UP(wxGenericListCtrl::OnLeftUp)
  EVT_KEY_DOWN(wxGenericListCtrl::OnKeyDown)

  EVT_SIZE(wxGenericListCtrl::OnSize)

END_EVENT_TABLE()

wxGenericListCtrl::wxGenericListCtrl()

	: wxScrolledWindow()
{
	SetDefaults();
}

void wxGenericListCtrl::SetDefaults()
{
    mRowHeight = 18;
    mLeadingHeight = 2;
    mHeadingHeight = 20;
    mpModel = NULL;
    mRowsPerUnit = 1;
    mFocusedRow = 0;

    mHeadingDragStarted = FALSE;
    mHeadingPart = -1;
    mDraggedHeading = -1;
    mPressedHeading = -1;
    mRowWidth = 0;
    mTextLeftMargin = 7;
    mTextRightMargin = 7;

    mpBufBmp = NULL;
    mpBufDc = NULL;
    mUserBackBuffer = TRUE;
    mpCellPainter = NULL;

    mSelColour = wxColour( 0, 0, 0x80 );

    mShowSortMarker = TRUE;
    mLastSortedCol = -1;

    mpImageList = NULL;
    mHorizScrollStep = 50; 
}

wxGenericListCtrl::wxGenericListCtrl( wxWindow* parent, wxWindowID id, 
									  const wxPoint& pos, const wxSize& size, 
									  long style, const wxString& name )

  : wxScrolledWindow(parent, id, pos, size, style, name )
{
  SetDefaults();
  EnableScrolling(TRUE, FALSE);

  mSelection.Add(mFocusedRow);

  mOldFont = this->GetFont();

  SetBackgroundColour( wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOW ) );
}

wxGenericListCtrl::~wxGenericListCtrl()
{
  if (mpBufDc)
  {
    mpBufDc->SelectObject(wxNullBitmap);
    delete mpBufDc;

    delete mpBufBmp;
  }

  if (mpModel) delete mpModel;

  if (mpCellPainter) delete mpCellPainter;
}

void wxGenericListCtrl::RemoveAllColumns()
{
  mColPositions.Clear();
  mColWidths.Clear();
  mColNames.Clear();
  mColSortOptions.Clear();
  mColAlignOptions.Clear();
}

void wxGenericListCtrl::SetSortOptionForColumn(int col, bool acending)
{
  mColSortOptions.Add((int)acending);
}

int wxGenericListCtrl::GetLastSortedColumn()
{
  return mLastSortedCol;
}

bool wxGenericListCtrl::IsSortedAscending()
{
  if (mLastSortedCol == -1) return FALSE;

  return mColSortOptions[ mLastSortedCol ] == TRUE;
}

void wxGenericListCtrl::AddColumn(const wxString& name, int width)
{
  mColNames.Add(name);

  int pos = 0;

  if (mColPositions.Count()) 
    
    pos = mColPositions[ mColPositions.Count() - 1 ] + 
        mColWidths[ mColWidths.Count() - 1 ];

  mColPositions.Add(pos);
  mColWidths.Add(width);
  mColSortOptions.Add(FALSE); // descending by "default"...
  mColAlignOptions.Add(wxLEFT); 

  mRowWidth = wxMax(pos + width, GetWidth());
}

void wxGenericListCtrl::SetModel(wxListModel* pModel)
{
  if (mpModel) delete mpModel;

  mpModel = pModel;
}

wxListModel* wxGenericListCtrl::GetModel()
{
  return mpModel;
}

int wxGenericListCtrl::GetRowAt(int yPos)
{
  if (yPos < mHeadingHeight) return -1;

  return GetFirstVisibleRow() + (yPos - mHeadingHeight) / mRowHeight;
}

bool wxGenericListCtrl::IsVisible(int row)
{
  int firstRow = 0, rowsVisible = 0;
  GetPageInfo(firstRow, rowsVisible);

  return row >= firstRow && row < firstRow + rowsVisible;
}

int wxGenericListCtrl::GetColumnCount()
{
  return mColWidths.Count();
}

wxString wxGenericListCtrl::GetColumnName(int col)
{
  return mColNames[ col ];
}

int wxGenericListCtrl::GetColumnIndex(const wxString& columnName)
{
  for(size_t i = 0; i != mColNames.GetCount(); ++i)
    
    if (mColNames[i] == columnName)

      return (int)i;

  return -1;
}

int wxGenericListCtrl::GetColumnWidth(int col)
{
  return mColWidths[ col ];
}

wxStringList& wxGenericListCtrl::GetColumnNames()
{
  return mColNames;
}

void wxGenericListCtrl::SelectRow(int row)
{
  if (row < 0) row = 0;

  int rowCount = mpModel->GetRowCount();

  if (row > rowCount) 

    row = mpModel->GetRowCount() - 1;

  int oldSel = mFocusedRow;
  mFocusedRow = row;

  wxClientDC dc(this);
  PrepareDC(dc);

  bool wasMultiple = mSelection.Count() > 1;
  mSelection.Clear();

  if (IsVisible(oldSel) && rowCount)
  
    DrawRows(dc, oldSel, oldSel + 1);

  mFocusedRow = row;

  mSelection.Add(mFocusedRow);

  if (!SyncPageWithSelection() && rowCount)
  {
    if (wasMultiple)

      DrawPageContent(dc);
    else
      DrawRows(dc, row, row + 1); // fine-grained update

    FlushBufferDc(dc);
  }
}

int wxGenericListCtrl::GetFocusedRow() 
{ 
  return mFocusedRow; 
}

void wxGenericListCtrl::SetFocusedRow(int row)
{
  if (!IsSelected(row)) 

    return;

  mFocusedRow = row;
}

wxArrayInt& wxGenericListCtrl::GetSelection()
{
  return mSelection;
}

void wxGenericListCtrl::SetSelection(wxArrayInt& rows)
{
  if (!rows.Count()) return;

  mSelection  = rows;
  mFocusedRow = rows[0];
}

bool wxGenericListCtrl::IsSelected(int row)
{
  for(size_t i = 0; i != mSelection.Count(); ++i)

    if (mSelection[i] == row)

      return TRUE;

  return FALSE;
}

void wxGenericListCtrl::SelectRow(int row, bool select)
{
  if (select)
  {
    mSelection.Add(row);
    mFocusedRow = row; // move focus to newly added selection
  }
  else
  if (!select && 
     mSelection.Count() != 1  // there's alway should be at least one row selected
    )
  {
    mSelection.Remove((int)row);

    // move focus to the first selected row, if the focused one was unselected
    if (!IsSelected(mFocusedRow))

      mFocusedRow = mSelection[0];
  }
}

int wxGenericListCtrl::GetHorzintalScrollStep()
{
  return mHorizScrollStep;
}

void wxGenericListCtrl::SetHorizontalScrollStep(int pixels)
{
  mHorizScrollStep = pixels;
}

void wxGenericListCtrl::SetReduceFlicker(bool reduce)
{
  mUserBackBuffer = reduce;
}

void wxGenericListCtrl::SetShowSortMarker(bool show)
{
  mShowSortMarker = show;
}

void wxGenericListCtrl::SetCellPainter(wxListCellPainter* pPainter)
{
  if (mpCellPainter) delete mpCellPainter;

  mpCellPainter = pPainter;
}

void wxGenericListCtrl::SetRowBitmap(const wxBitmap& bmp)
{
  mRowBitmap = bmp;
}

void wxGenericListCtrl::SetImageList(wxImageList* pList)
{
  if (mpImageList) delete mpImageList;

  mpImageList = pList;
}

// event handlers 

void wxGenericListCtrl::OnLeftDown(wxMouseEvent& event)
{
  if (event.GetY() < mHeadingHeight)
  {
    if (HitTestHeadings(ToViewX(event), mDraggedHeading, mHeadingPart))
    {
      mHeadingDragStarted = TRUE;

      if (mHeadingPart == HITS_INTERIOR)
      {
        mPressedHeading = mDraggedHeading;

        UpdateHeadings();
      }

      CaptureMouse();

      return;
    }
  }
  else
  {
    int row = GetRowAt(event.GetY());

    if (row != -1)
    {
      if (event.ControlDown()) // toggles selection on the clicked-row
      {
        SelectRow(row, !IsSelected(row));

        Refresh();
      }
      else
      if (event.ShiftDown()) // selects range from current focus till clicked-row
      {
        mSelection.Clear();

        int from = mFocusedRow;
        int till = row;

        if (from > till)
        {
          int tmp = till;
          till = from;
          from = tmp;
        }

        for(int i = from; i <= till; ++i)

          SelectRow(i, TRUE);

        mFocusedRow = row; // set focus to the clicked row

        Refresh();
      }
      else
        SelectRow(row);
    }
  }
}

void wxGenericListCtrl::OnMotion(wxMouseEvent& event)
{
  if (mHeadingDragStarted)
  {
    int heading = -1, part = 0;

    if (mHeadingPart == HITS_INTERIOR)
    {
      if (event.GetY() < mHeadingHeight)

        HitTestHeadings(ToViewX(event), heading, part);

      if (heading != mDraggedHeading)
      
        mPressedHeading = -1;
      else
        mPressedHeading = mDraggedHeading;

      UpdateHeadings();
    }
    else
    {
      // otherwise, resizing column

      mColWidths[mDraggedHeading] = ToViewX(event) - mColPositions[mDraggedHeading];

      if (mColWidths[mDraggedHeading] < 0)

        mColWidths[mDraggedHeading] = 0;

      RelayoutHeadings(TRUE);

      wxClientDC dc(this);
      PrepareDC(dc);

      DrawHeadings(dc);
      DrawPageContent(dc);

      FlushBufferDc(dc);

      UpdateScrollBars();
    }
  }
  else
  {
    if (event.GetY() < mHeadingHeight)
    {
      if (HitTestHeadings(ToViewX(event), mDraggedHeading, mHeadingPart) && 
        mHeadingPart != HITS_INTERIOR)
      {
        this->SetCursor(wxCURSOR_SIZEWE);
        CaptureMouse();
      }
      else
      {
        mDraggedHeading = -1;
        ReleaseMouse();
        this->SetCursor(wxCURSOR_ARROW );
      }
    }
    else
    {
      if (mDraggedHeading != -1)
      {
        mDraggedHeading = -1;

        ReleaseMouse();
        this->SetCursor(wxCURSOR_ARROW );
      }
    }
  }
}

int wxGenericListCtrl::ToViewX(wxMouseEvent& event)
{
  return event.GetX() + m_xScrollPosition;
}

void wxGenericListCtrl::OnLeftUp(wxMouseEvent& event)
{
  if (mHeadingDragStarted)
  {
    ReleaseMouse();

    mDraggedHeading = -1;

    mHeadingDragStarted = FALSE;
    OnMotion(event); 

    if (mHeadingPart != HITS_INTERIOR)

      this->SetCursor(wxCURSOR_ARROW );

    if (mHeadingPart == HITS_INTERIOR && mPressedHeading != -1)
    {
      ::wxBeginBusyCursor();

      if (mColSortOptions[mPressedHeading])
        
        mColSortOptions[mPressedHeading] = 0;
      else
        mColSortOptions[mPressedHeading] = 1;

      mpModel->Sort(mPressedHeading, mColSortOptions[mPressedHeading] != 0);

      ::wxEndBusyCursor();

      mLastSortedCol = mPressedHeading;

      mPressedHeading = -1;

      wxClientDC dc(this);
      PrepareDC(dc);

      DrawHeadings(dc);
      DrawPageContent(dc);

      FlushBufferDc(dc);

      // TBD:: fire heading-clicked event
    }
  }
}

void wxGenericListCtrl::OnSize(wxSizeEvent& event)
{
  if (mColPositions.Count() != 0)
  {
    int headWidth = mColPositions[ mColPositions.Count() - 1 ] +
            mColWidths[ mColWidths.Count() - 1 ];

    mRowWidth = wxMax(headWidth, GetWidth());

    UpdateScrollBars();
  }

  event.Skip();
}

void wxGenericListCtrl::OnKeyDown(wxKeyEvent& event)
{
  if (event.KeyCode() == WXK_LEFT ||
     event.KeyCode() == WXK_RIGHT
    )
  {
    int vx, vy;
    GetViewStart(&vx, &vy);

    if (event.KeyCode() == WXK_RIGHT) vx += mHorizScrollStep;
                     else vx -= mHorizScrollStep;

    if (vx < 0) vx = 0;

    Scroll(vx, -1);
    UpdateScrollBars();
    Refresh();
  }
  else
  if (event.KeyCode() == WXK_DOWN)
  {
    if (mFocusedRow + 1 < mpModel->GetRowCount())
    {
      int first = GetFirstVisibleRow();
      SelectRow(mFocusedRow + 1);
      first = GetFirstVisibleRow();
      Refresh();
    }
      
  }
  else
  if (event.KeyCode() == WXK_UP)
  {
    if (mFocusedRow != 0)
    
      SelectRow(mFocusedRow - 1);
  }
  else
  if (event.KeyCode() == WXK_HOME)
  
    SelectRow(0);
  else
  if (event.KeyCode() == WXK_END)
  {
    int cnt = event.ControlDown() ? mpModel->GetFinalRowCount()
                    : mpModel->GetRowCount();
    if (cnt)

      SelectRow(mpModel->GetRowCount() - 1);
  }
  else
  if (event.KeyCode() == WXK_NEXT)
  
    SelectRow(mFocusedRow + GetVisibleRowsCount());
  else
  if (event.KeyCode() == WXK_PRIOR)

    SelectRow(mFocusedRow - GetVisibleRowsCount());
  else
    event.Skip();
}

bool wxGenericListCtrl::SyncPageWithSelection()
{
  // this method returns TRUE, when the page content had to be scralled

  if (mpModel->GetRowCount() == 0) 
    
    return FALSE;

  int first = GetFirstVisibleRow();
  int visible = GetVisibleRowsCount();

  if (mFocusedRow >= first && mFocusedRow < first + visible)

    return FALSE;

  if (mFocusedRow < first)

    first = mFocusedRow;
  else
  {
    GetPageInfo(first, visible);

    first = mFocusedRow - visible;

    if (visible != 0) ++first;
  }

  UpdateScrollBars(-1, first / mRowsPerUnit);

  return TRUE;
}

void wxGenericListCtrl::PrepareDC(wxDC& dc)
{
    dc.SetDeviceOrigin(-m_xScrollPosition * m_xScrollPixelsPerLine,
                        0);
}

void wxGenericListCtrl::OnFontChanged()
{
  mOldFont = this->GetFont();

  wxClientDC dc(this);
  dc.SetFont(GetFont());

  wxCoord w = 0, h = 0;
  dc.GetTextExtent("X", &w, &h);

  mRowHeight = h + 2;
  mHeadingHeight = h + 4;

  UpdateScrollBars();
}

void wxGenericListCtrl::OnPaint(wxPaintEvent& event)
{
  wxPaintDC dc(this);

  wxFont newFnt = this->GetFont();

  if (mOldFont.GetFaceName()  != newFnt.GetFaceName()  ||
     mOldFont.GetPointSize() != newFnt.GetPointSize() ||
     mOldFont.GetStyle()     != newFnt.GetStyle()     ||
     mOldFont.GetWeight()    != newFnt.GetWeight() 
    )
  {
    OnFontChanged();
  }

  PrepareDC(dc);

  DrawHeadings(dc);
  DrawPageContent(dc);

  FlushBufferDc(dc);
}

void wxGenericListCtrl::OnEraseBackground(wxEraseEvent& event)
{
}

void wxGenericListCtrl::UpdateHeadings()
{
  wxClientDC dc(this);

  PrepareDC(dc);

  DrawHeadings(dc);

  FlushBufferDc(dc);
}

wxFont wxGenericListCtrl::GetHeadingFont()
{
  return GetFont();
}

wxFont wxGenericListCtrl::GetContentFont()
{
  return GetFont();
}

wxColour& wxGenericListCtrl::GetSelectionColour()
{
  return mSelColour;
}

void wxGenericListCtrl::StretchHeadingsToWidth()
{
  if (mColWidths.Count() == 0) return;

  double* ratios = new double[ mColWidths.Count() ];

  int widthSum = 0; size_t i = 0;
  for(;i != mColWidths.Count(); ++i)

    widthSum += mColWidths[i];

  for(i = 0; i != mColWidths.Count(); ++i)

    ratios[i] = double(mColWidths[i]) / double(widthSum);

  int width = GetWidth(); int spaceTaken = 0;

  for(i = 0; i != mColWidths.Count(); ++i)
  {
    if (i == mColWidths.Count() - 1)

      // assign round-off errors to the last element
      mColWidths[i] = width - spaceTaken;
    else
    {
      int w = int((double)width * ratios[i]);

      mColWidths[i] = w;
      spaceTaken += w;
    }
  }

  delete ratios;

  RelayoutHeadings(TRUE);
}

void wxGenericListCtrl::LayoutHeadingsByWidths()
{
  wxClientDC dc(this);
  PrepareDC(dc);

  dc.SetFont(GetHeadingFont());

  int pos = 0;

  for(size_t i = 0; i != mColWidths.Count(); ++i)
  {
    int w = 0, h = 0;

    dc.GetTextExtent(mColNames[i], &w, &h);

    w += mTextLeftMargin + mTextRightMargin 
       + 20; // account for sort-direciton triangle

    mColWidths[i] = w;
    mColPositions[i] = pos;

    pos += w;
  }

  mRowWidth = wxMax(pos, GetWidth());
}

void wxGenericListCtrl::SetColumnWidth(int col, int width, bool refreshNow)
{
  mColWidths[ col ] = width;

  RelayoutHeadings(TRUE);

  if (refreshNow)

    Refresh();
}

void wxGenericListCtrl::SetColumnName(int col, const wxString& name, bool refreshNow)
{
  wxStringList newList;

  // this is because we cannot assigne new values to
  // elements of wxStringList directly

  for(int i = 0; i != (int)mColNames.GetCount(); ++i)
  {
    if (i == col) newList.Add(name);
           else newList.Add(mColNames[i]);
  }
  
  mColNames = newList;

  if (refreshNow)

    Refresh();
}

void wxGenericListCtrl::SetColumnAlignment(int col, int align)
{
 // printf("\n-I-> wxGenericListCtrl::SetColumnAlignment(int col(%d), int align(%d))",col,align);
  mColAlignOptions[ col ] = align;
}

void wxGenericListCtrl::DrawHeadingCol(wxDC& dc, int x, int width, bool isPressed)
{
  int y = 1;

  if (!isPressed)
  {
    dc.SetPen(*wxWHITE_PEN);
    dc.DrawLine(x,y, x + width,y);
    dc.DrawLine(x,y, x, y + mHeadingHeight);

    dc.SetPen(*wxGREY_PEN);
    dc.DrawLine(x + 1, y + mHeadingHeight - 1,
           x + width, y + mHeadingHeight - 1);
    dc.DrawLine(x + width - 2, y + 1, 
           x + width - 2, y + mHeadingHeight);

    dc.SetPen(*wxBLACK_PEN);
    dc.DrawLine(x + width - 1, y,
           x + width - 1, y + mHeadingHeight);
    dc.DrawLine(x,y-1, x + width, y-1);
    dc.DrawLine(x,y + mHeadingHeight - 2, x + width, y  + mHeadingHeight -2);
  }
  else
  {
    dc.SetPen(*wxGREY_PEN);
    dc.DrawLine(x,y, x + width, y);
    dc.DrawLine(x + width - 1, y, x + width - 1, y +mHeadingHeight);
    dc.DrawLine(x + width - 1, y + mHeadingHeight - 1, x, y + mHeadingHeight - 1);
    dc.DrawLine(x, y + mHeadingHeight - 1, x, y);

    dc.SetPen(*wxBLACK_PEN);
    dc.DrawLine(x,y-1, x + width, y-1);
    dc.DrawLine(x,y + mHeadingHeight - 2, x + width, y  + mHeadingHeight -2);
  }
}

void wxGenericListCtrl::DrawHeadings(wxDC& toDc)
{
  wxDC& dc = GetBufferDc(toDc);

  dc.SetFont(GetHeadingFont());
  dc.SetTextForeground(*wxBLACK);

  wxBrush br(wxSystemSettings::GetSystemColour(wxSYS_COLOUR_3DFACE), wxSOLID);
  dc.SetBrush(br);
  dc.SetPen(*wxTRANSPARENT_PEN);
  dc.DrawRectangle(m_xScrollPosition,0, GetWidth(), mHeadingHeight);

  int x = 0;

  for(size_t i = 0; i != mColPositions.Count(); ++i)
  {
    x = mColPositions[i]; int y = 1; 
    int w = mColWidths[i], h = mHeadingHeight - 2;

    if (IsVisibleColumn(i))
    {
      DrawHeadingCol(dc, x, w, (int)i == mPressedHeading);

      int txtX = x + mTextLeftMargin, txtY = y + mLeadingHeight;

      if ((int)i == mPressedHeading)
      {
        txtX++; txtY++;
      }

      int textWidth = w - mTextLeftMargin - 2;

      if (textWidth > 0)
      {
        dc.SetClippingRegion(x + mTextLeftMargin, y, textWidth, mHeadingHeight);

        dc.DrawText(mColNames[i], txtX, txtY);

        if (mShowSortMarker && (int)i == mLastSortedCol)
        {
          int tmp = x;

          int tw = 0, th = 0; dc.GetTextExtent(mColNames[i], &tw, &th);
          x += tw + 15; y = 0;
          int yOfs = (h - 8) / 2;

          if ((int)i == mPressedHeading)
          {
            x++; y++;
          }

          if (mColSortOptions[i]) // if ascending
          {
            dc.SetPen(*wxGREY_PEN); // upwards-looking triangle
            dc.DrawLine(x, y + yOfs + 8, x + 4, y + yOfs);

            dc.SetPen(*wxWHITE_PEN);
            dc.DrawLine(x + 4, y + yOfs, x + 8, y + yOfs + 8);
            dc.DrawLine(x + 8 - 1, y + yOfs + 8, x - 1, y + yOfs + 8);
          }
          else
          {
            dc.SetPen(*wxWHITE_PEN); // downwards-looking triangle
            dc.DrawLine(x + 8, y + yOfs, x + 4, y + yOfs + 8);

            dc.SetPen(*wxGREY_PEN);
            dc.DrawLine(x + 4, y + yOfs + 8 - 1, x, y + yOfs);
            dc.DrawLine(x, y + yOfs, x + 8 + 1, y + yOfs);
          }

          x = tmp;
        }

        dc.DestroyClippingRegion();
      }
    }

    x += w;
  }

  if (x < mRowWidth)

    DrawHeadingCol(dc, x, mRowWidth - x, FALSE);
}

void wxGenericListCtrl::DrawPageContent(wxDC& dc)
{
  int firstRow = 0, rowsVisible = 0;
  GetPageInfo(firstRow, rowsVisible);

  int tillRow = wxMin(mpModel->GetRowCount(), firstRow + rowsVisible);

  DrawRows(dc, firstRow, tillRow);
  FillEmptyAreas(dc);
}

void wxGenericListCtrl::FillEmptyAreas(wxDC& toDc)
{
  wxDC& dc = GetBufferDc(toDc);

  int firstRow = 0, rowsPerPage = 0;

  GetPageInfo(firstRow, rowsPerPage);

  int visRows = GetVisibleRowsCount();
  int emptyRows = rowsPerPage - visRows;

  wxBrush br(GetBackgroundColour(), wxSOLID);
  dc.SetBrush(br);
  dc.SetPen(*wxTRANSPARENT_PEN);

  dc.DrawRectangle(0, visRows * mRowHeight + mHeadingHeight,
            mRowWidth, (emptyRows + 2) * mHeadingHeight);
}

wxDC& wxGenericListCtrl::GetBufferDc(wxDC& toDc)
{
  if (mUserBackBuffer)
  {
    int w = GetWidth(), h = GetHeight();

    h += 50;

    if (!mpBufBmp || (mpBufBmp->GetWidth() < w ||
                      mpBufBmp->GetHeight() < h) 
      )
    {
      if (mpBufDc) 
      {
        mpBufDc->SelectObject(wxNullBitmap);
        delete mpBufDc;
      }
      
      if (mpBufBmp) delete mpBufBmp;

      mpBufBmp = new wxBitmap(w + 10, h + 10);

      mpBufDc = new wxMemoryDC();

      mpBufDc->SelectObject(*mpBufBmp);
    }

    int vx = 0, vy = 0;
    ViewStart(&vx, &vy);

    mpBufDc->SetDeviceOrigin(-vx, 0);

    return *mpBufDc;
  }
  else
    return toDc;
}

void wxGenericListCtrl::FlushBufferDc(wxDC& toDc)
{
  if (mUserBackBuffer)
  {
    int vx = 0, vy = 0;
    ViewStart(&vx, &vy);

    toDc.Blit(vx,0, GetWidth(), GetHeight() + 50, mpBufDc, 
             vx,0);
  }
}

void wxGenericListCtrl::DrawRows(wxDC& toDc, int from, int till)
{
  wxDC& dc = GetBufferDc(toDc);

  dc.SetFont(GetContentFont());

  int wndWidth = GetWidth();

  int first = GetFirstVisibleRow();

  int y = (from - first) * mRowHeight + mHeadingHeight;

  wxPen dotPen(wxColour(0xFF, 0xFF, 0x00), 1, wxDOT );
  wxBrush selBrush(mSelColour, wxSOLID);
  wxBrush bkBrush(GetBackgroundColour(), wxSOLID);

  for(int row = from; row < till; ++row)
  {
    mpModel->PrepareRow(row);

    if (IsSelected(row))
    {
      dc.SetTextForeground(*wxWHITE);
      dc.SetBrush(selBrush); // selected row has blue background
    }
    else
    {
      dc.SetBrush(bkBrush);
      dc.SetTextForeground(*wxBLACK);
    }

    dc.SetPen(*wxTRANSPARENT_PEN);

    int x = 0;

    dc.DrawRectangle(m_xScrollPosition, y, wndWidth, mRowHeight);

    if (!mpCellPainter)
    {
      if (row == mFocusedRow)
      {
        // draw yellow outline around selected row (MSW-look)

        dc.SetPen(dotPen);
        dc.DrawLine(x,y, x + mRowWidth, y);
        dc.DrawLine(x + mRowWidth, y,
               x + mRowWidth, y + mRowHeight - 1);
        dc.DrawLine(x + mRowWidth, y + mRowHeight - 1,
               x, y + mRowHeight - 1);
        dc.DrawLine(x, y + mRowHeight - 1, x,y);
      }
    }

    wxRect rect;
    for(size_t col = 0; col != mColPositions.Count(); ++col)
    {
      if (IsVisibleColumn(col))
      {
        x = mColPositions[col];
        int w = mColWidths[col];

        if (col == 0 && !mpCellPainter && (mpImageList || mRowBitmap.Ok())
         )
        {
          dc.SetClippingRegion(x, y, w, mRowHeight);

          if (mpImageList)
          {         
            int idx = mpModel->GetImageForRow(row);
            mpImageList->Draw(idx, dc, x,y);

            int iwidth, iheight;
            mpImageList->GetSize(idx, iwidth, iheight);

            x += iwidth;
            w -= iheight;
          }
          else
          {
            dc.DrawBitmap(mRowBitmap, x,y, TRUE);

            x += mRowBitmap.GetWidth();
            w -= mRowBitmap.GetWidth();
          }
          
          dc.DestroyClippingRegion();
        }

        rect.x = x; rect.y = y; rect.width = w; rect.height = mRowHeight;

        if (mpCellPainter)
        {
          mpCellPainter->OnDrawCellBackground(dc, rect, row, col);
          mpCellPainter->OnDrawCellContent(dc, rect, row, col);
        }
        else
        {
          int cellW = w - mTextLeftMargin - mTextRightMargin;

          if (cellW > 0)
          {
            wxString value = mpModel->GetValue(row, col);

            dc.SetClippingRegion(x + mTextLeftMargin, y, cellW, mRowHeight);

            int align = mColAlignOptions[col];
            // printf("\n-I-> wxGenericListCtrl::DrawRows wxRIGHT(%d) - align(%d) in col(%d)\t",wxRIGHT,align,col);
            int tx = x + mTextLeftMargin;

            if (align == wxCENTER)
            {
              int tw = 0, th = 0; dc.GetTextExtent(value, &tw, &th);
              tx = x + mTextLeftMargin + (cellW - tw) / 2;
            }
            else
            if (align == wxRIGHT)
            {
              int tw = 0, th = 0; dc.GetTextExtent(value, &tw, &th);
              tx = x + mTextLeftMargin + cellW - tw;
             // printf("-I-> wxGenericListCtrl::DrawRows wxRIGHT ; tx(%d) = x(%d) + mTextLeftmargin(%d) + cellW(%d) - tw(%d)\n",tx,x,mTextLeftMargin,cellW,tw);
            }

            dc.DrawText(mpModel->GetValue(row, col ), 
                   tx, y + mLeadingHeight);

            dc.DestroyClippingRegion();
          }
        }
      }
    }


    y += mRowHeight;
  }
}

void wxGenericListCtrl::GetPageInfo(int& firstRow, int& rowsVisible)
{
  int x = 0, y = 0;

  ViewStart(&x, &y);

  firstRow = y * mRowsPerUnit;
  rowsVisible = 1 + (GetHeight() - mHeadingHeight) / mRowHeight;

  rowsVisible--; // the one which slips under the horiz-scrollbar

  if (rowsVisible < 0) rowsVisible = 0;
}

int wxGenericListCtrl::GetFirstVisibleRow()
{
  int firstRow = 0, rowsVisible = 0;

  GetPageInfo(firstRow, rowsVisible);

  return firstRow;
}

int wxGenericListCtrl::GetVisibleRowsCount()
{
  int firstRow = 0, rowsVisible = 0;

  GetPageInfo(firstRow, rowsVisible);

  if (firstRow + rowsVisible > mpModel->GetRowCount())

    return mpModel->GetRowCount() - firstRow;
  else
    return rowsVisible;
}

void wxGenericListCtrl::UpdateScrollBars(int scrollX , int scrollY)
{
  EnableScrolling(TRUE, FALSE);

  int nRows = mpModel->GetRowCount();

  mRowsPerUnit = nRows / 32000 + 1;

  int vx = 0, vy = 0;
  ViewStart(&vx, &vy);

  ++nRows;
  ++nRows; // we have to fool scrollwin that we have one more row which
       // actually stands for headings-row

  int totalYUnits = nRows / mRowsPerUnit + ((nRows % mRowsPerUnit != 0) ? 1 : 0);

  //int yPixPerUnit = GetHeight() / mRowsPerUnit + 10 * mRowsPerUnit;

  if (scrollX != -1) vx = scrollX;
  if (scrollY != -1) vy = scrollY;

  SetScrollbars(1, mRowHeight, 
           GetRowWidth(), totalYUnits,   vx, vy);
}

int wxGenericListCtrl::GetWidth()
{
  int w = 0, h = 0;
  GetClientSize(&w, &h);

  return w;
}

int wxGenericListCtrl::GetHeight()
{
  int w = 0, h = 0;
  GetClientSize(&w, &h);

  return h;
}

int wxGenericListCtrl::GetRowWidth() 
{
  return mRowWidth;
}

int wxGenericListCtrl::GetHeadingRowHeight()
{
  return mHeadingHeight;
}

/*** protected methods ***/

bool wxGenericListCtrl::HitTestHeadings(int x, int &heading, int& part)
{
  int i = mColPositions.Count() - 1;
  while(i >= 0)
  {
    int from = mColPositions[i], till = mColPositions[i] + mColWidths[i];

    if (x >= from && x < till)
    {
      heading = i;

      if (i == 0 && x < from + 5)
      {
        part = HITS_LEFT_CORNER;
        return TRUE;
      }

      if (x > till - 5)
      {
        part = HITS_RIGHT_CORNER;
        return TRUE;
      }


      part = HITS_INTERIOR;
      return TRUE;
    }

    --i;
  }

  heading = -1;
  return FALSE;
}

void wxGenericListCtrl::RelayoutHeadings(bool byWidths)
{
  int pos = 0;
  for(size_t i = 0; i != mColPositions.Count(); ++i)
  {
    if (byWidths)
    {
      mColPositions[i] = pos;

      pos += mColWidths[i];
    }
  }

  mRowWidth = wxMax(pos, GetWidth());
}

bool wxGenericListCtrl::IsVisibleColumn(int col)
{
  int vx = 0, vy = 0;
  ViewStart(&vx, &vy);
  int vw = GetWidth();

  int x = mColPositions[ col ];
  int w = mColWidths[ col ];

  if (((x + w) >= vx) && 
     ((x + w) <= vx + vw))

    return TRUE;

  if (x >= vx && x <= vx + vw)

    return TRUE;


  return FALSE;
}
