/////////////////////////////////////////////////////////////////////////////
// Name:        rowdragpl.cpp
// Purpose:     cbRowDragPlugin implementation.
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     06/10/98
// RCS-ID:      $Id: rowdragpl.cpp 38927 2006-04-26 16:27:20Z PC $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

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

#include "wx/fl/rowdragpl.h"

#define MINIMAL_ROW_DRAG_OFS  5

// parameters for row-hints of NC-look

#define TRIANGLE_OFFSET       2
#define TRIANGLE_TO_PAT_GAP   2
#define PAT_OFFSET            2
#define COLLAPSED_ICON_WIDTH  45
#define COLLAPSED_ICON_HEIGHT 9
#define ROW_DRAG_HINT_WIDTH   10
#define ICON_TRIAN_WIDTH      6
#define ICON_TRIAN_HEIGHT     3

/***** Implementation for class cbHiddenBarInfo *****/

IMPLEMENT_DYNAMIC_CLASS( cbHiddenBarInfo, wxObject )

/***** Implementation for class cbRowDragPlugin *****/

IMPLEMENT_DYNAMIC_CLASS( cbRowDragPlugin, cbPluginBase )

BEGIN_EVENT_TABLE( cbRowDragPlugin, cbPluginBase )

    EVT_PL_LEFT_DOWN          ( cbRowDragPlugin::OnLButtonDown        )
    EVT_PL_LEFT_UP              ( cbRowDragPlugin::OnLButtonUp          )
    EVT_PL_MOTION              ( cbRowDragPlugin::OnMouseMove          )

    EVT_PL_DRAW_PANE_DECOR    ( cbRowDragPlugin::OnDrawPaneBackground )

END_EVENT_TABLE()

// FIXME:: how to eliminated these cut and pasted constructors?

cbRowDragPlugin::cbRowDragPlugin(void)

    : mHightColor          ( 192, 192, 255 ),
      mLowColor            ( 192, 192, 192 ),
      mTrianInnerColor     ( 0,0,255 ),
      mTrianInnerPen       ( mTrianInnerColor, 1, wxSOLID ),
 
      mDragStarted         ( false ),
      mDecisionMode        ( false ),
      mCurDragOfs          ( 0 ),
      mCaptureIsOn         ( false ),
      mSvTopMargin         ( -1 ),
      mSvBottomMargin      ( -1 ),
      mSvLeftMargin        ( -1 ),
      mSvRightMargin       ( -1 ),
   
      mpPaneImage          ( NULL ),
      mpRowImage           ( NULL ),
      mpCombinedImage        ( NULL ),

      mpRowInFocus         ( NULL ),
      mCollapsedIconInFocus( -1 ),
      
      mpPane               ( NULL )
{
}

cbRowDragPlugin::cbRowDragPlugin( wxFrameLayout* pLayout, int paneMask )

    : cbPluginBase( pLayout, paneMask ),
      
         mHightColor          ( 192, 192, 255 ),
      mLowColor            ( 192, 192, 192 ),
      mTrianInnerColor     ( 0,0,255 ),
      mTrianInnerPen       ( mTrianInnerColor, 1, wxSOLID ),
   
      mDragStarted         ( false ),
      mDecisionMode        ( false ),
      mCurDragOfs          ( 0 ),
      mCaptureIsOn         ( false ),
      mSvTopMargin         ( -1 ),
      mSvBottomMargin      ( -1 ),
      mSvLeftMargin        ( -1 ),
      mSvRightMargin       ( -1 ),
      
      mpPaneImage          ( NULL ),
      mpRowImage           ( NULL ),
      mpCombinedImage        ( NULL ),

      mpRowInFocus         ( NULL ),
      mCollapsedIconInFocus( -1 ),
      
      mpPane               ( NULL )
{
}

cbRowDragPlugin::~cbRowDragPlugin()
{
}

// handlers for plugin events
void cbRowDragPlugin::OnMouseMove( cbMotionEvent& event )
{
    // short-cuts
    wxPoint pos = event.mPos;
    mpPane      = event.mpPane;

    mpPane->PaneToFrame( &pos.x, &pos.y );

    if ( !mDragStarted )
    {
        if ( mDecisionMode && mpRowInFocus )
        {
            int ofs;

            if ( mpPane->IsHorizontal() )

                ofs = pos.y - mDragOrigin.y;
            else
                ofs = pos.x - mDragOrigin.x;

            // check if the item was dragged sufficeintly
            // far, enough to consider that user really intends 
            // to drag it

            if ( ofs >= MINIMAL_ROW_DRAG_OFS ||
                 ofs <= -MINIMAL_ROW_DRAG_OFS )
            {
                // DBG::
                //.wxPoint pos = event.mPos;
                //wxPoint drg = mDragOrigin;
                //int dif = event.mPos.x - mDragOrigin.x;

                mDragStarted  = true;
                mDecisionMode = false;
                mDragOrigin   = pos;

                PrepareForRowDrag();
                return;
            }

            // this plugin "eats" all mouse input while item is dragged,
            return;
        }

        cbRowInfo* pRow = GetFirstRow();

        bool focusFound = false;

        while( pRow )
        {
            if ( HitTestRowDragHint( pRow, pos ) )
            {
                CheckPrevItemInFocus( pRow, -1 );
                SetMouseCapture( true );

                focusFound = true;

                mpRowInFocus          = pRow;
                mCollapsedIconInFocus = -1;
                break;
            }

            pRow = pRow->mpNext;
        }

        if ( !focusFound )
        {
            int hrCnt = GetHRowsCountForPane( event.mpPane );

            for( int i = 0; i != hrCnt; ++i )
            {
                if ( HitTestCollapsedRowIcon( i, pos ) )
                {
                    CheckPrevItemInFocus( NULL, i );
                    SetMouseCapture( true );

                    focusFound = true;

                    mCollapsedIconInFocus = i;
                    mpRowInFocus          = NULL;
                    break;
                }
            }
        }

        if ( !focusFound && ItemIsInFocus() )
        {
            // kill focus from item previously been in focus
            UnhighlightItemInFocus();

            mpRowInFocus          = NULL;
            mCollapsedIconInFocus = -1;
            SetMouseCapture( false );
        }

        if ( !ItemIsInFocus() ) 

                // delegate it to other plugins
                event.Skip();
    }
    else
    {
        // otherwise mouse pointer moves, when dragging is started

        if ( mpPane->IsHorizontal() )
        {
            // row is dragged up or down;
            ShowDraggedRow( pos.y - mDragOrigin.y );
        }
        else
        {
            // row is dragged left or right
            ShowDraggedRow( pos.x - mDragOrigin.x );
        }

        // this plugin "eats" all mouse input while item is dragged,
    }
}

void cbRowDragPlugin::OnLButtonDown( cbLeftDownEvent& event )
{
    mpPane = event.mpPane;

    // DBG::
    wxASSERT( !mDragStarted && !mDecisionMode );

    if ( ItemIsInFocus() )
    {
        mDecisionMode = true;

        wxPoint pos = event.mPos;
        mpPane->PaneToFrame( &pos.x, &pos.y );

        mDragOrigin = pos;

        SetMouseCapture( true );
    }
    else
        // propagate event to other plugins
        event.Skip();
}

void cbRowDragPlugin::OnLButtonUp  ( cbLeftUpEvent& event )
{
    if ( !mDragStarted && !mDecisionMode ) 
    {
        event.Skip();
        return;
    }

    mpPane = event.mpPane;

    if ( mDecisionMode )
    {
        cbDockPane* pPane = mpPane;

        SetMouseCapture( false );

        mDecisionMode = false;
        mDragStarted  = false;

        wxPoint frmPos = event.mPos;
        pPane->PaneToFrame( &frmPos.x, &frmPos.y );

        if ( mpRowInFocus ) 
        {
            CollapseRow( mpRowInFocus );
            mpRowInFocus = 0;
        }
        else
        {
            ExpandRow( mCollapsedIconInFocus );
            mCollapsedIconInFocus = -1;
        }

        mpRowInFocus  = NULL;
        mpPane = pPane;

        pPane->FrameToPane( &frmPos.x, &frmPos.y );

        // give it another try after relayouting bars

        cbMotionEvent moveEvt( frmPos, pPane );
        this->OnMouseMove( moveEvt );

        // this plugin has "eaten" the mouse-up event

        return;
    }
    else
    {
        // otherwise, the dragged row was dropped, determine
        // where to insert it

        // restore initial pane appearence
        ShowPaneImage();
        FinishOnScreenDraw();

        cbRowInfo* pRow = GetFirstRow();

        mpLayout->GetUpdatesManager().OnStartChanges();

        pRow->mUMgrData.SetDirty(true);

        cbBarInfo* pBar = mpRowInFocus->mBars[0];

        while ( pBar )
        {
            pBar->mUMgrData.SetDirty(true);

            if ( pBar->mpBarWnd )
            {
                // do complete refresh
                pBar->mpBarWnd->Show(false);
                pBar->mpBarWnd->Show(true);
            }

            pBar = pBar->mpNext;
        }

        while( pRow )
        {
            if ( mCurDragOfs < pRow->mRowY )
            {
                InsertDraggedRowBefore( pRow );
                break;
            }

            pRow = pRow->mpNext;
        }

        if ( pRow == NULL ) InsertDraggedRowBefore( NULL );

        mpRowInFocus = NULL;

        mpLayout->RecalcLayout(false);

        // finish change "transaction"
        mpLayout->GetUpdatesManager().OnFinishChanges();
        mpLayout->GetUpdatesManager().UpdateNow();

        // finish drag action
        SetMouseCapture( false );
        mDragStarted = false;
    }
}

void cbRowDragPlugin::OnDrawPaneBackground ( cbDrawPaneDecorEvent& event )
{
    mpPane = event.mpPane;

    // FIXME:: this may harm operation of other plugins

    if ( GetNextHandler() && mpPane->GetRowList().GetCount() )
    {
        // first, let other plugins add their decorations now
    
        GetNextHandler()->ProcessEvent( event );
        event.Skip(false);
    }

    wxClientDC dc( &mpLayout->GetParentFrame() );

    dc.SetClippingRegion( mpPane->mBoundsInParent.x,
                          mpPane->mBoundsInParent.y,
                          mpPane->mBoundsInParent.width,
                          mpPane->mBoundsInParent.height );

    int cnt = GetHRowsCountForPane( event.mpPane );

    if ( cnt > 0 ) 

        DrawCollapsedRowsBorder( dc );

    if ( mpPane->GetRowList().GetCount() )
    
        DrawRowsDragHintsBorder( dc );

    cbRowInfo* pRow = GetFirstRow();

    while( pRow )
    {
        DrawRowDragHint( pRow, dc, false );
        pRow = pRow->mpNext;
    }

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

        DrawCollapsedRowIcon(i, dc, false );
}

int cbRowDragPlugin::GetHRowsCountForPane( cbDockPane* pPane )
{
    wxNode* pNode = mHiddenBars.GetFirst();

    int maxIconNo = -1;

    while( pNode )
    {
        cbHiddenBarInfo* pHBInfo = (cbHiddenBarInfo*)pNode->GetData();

        if ( pHBInfo->mAlignment == pPane->mAlignment )

            maxIconNo = wxMax( maxIconNo, pHBInfo->mIconNo );

        pNode = pNode->GetNext();
    }

    return ( maxIconNo + 1 );
}

int cbRowDragPlugin::GetCollapsedRowIconHeight()
{
    return COLLAPSED_ICON_HEIGHT;
}

int cbRowDragPlugin::GetRowDragHintWidth()
{
    return ROW_DRAG_HINT_WIDTH;
}

void cbRowDragPlugin::SetPaneMargins()
{
    int hiddenRowsCnt = GetHRowsCountForPane( mpPane );

    if ( mSvTopMargin == -1 )
    {
        mSvTopMargin    = mpPane->mTopMargin;
        mSvBottomMargin    = mpPane->mBottomMargin;
        mSvLeftMargin   = mpPane->mLeftMargin;
        mSvRightMargin  = mpPane->mRightMargin;
    }

    if ( mpPane->IsHorizontal() )
    {
        mpPane->mTopMargin    = mSvTopMargin;
        mpPane->mBottomMargin = ( hiddenRowsCnt == 0 ) 
                                ?  mSvBottomMargin 
                                :  mSvBottomMargin + GetCollapsedRowIconHeight();

        mpPane->mLeftMargin   = mSvLeftMargin + GetRowDragHintWidth();
        mpPane->mRightMargin  = mSvRightMargin;
    }
    else
    {
        mpPane->mTopMargin    = mSvTopMargin;
        mpPane->mBottomMargin = mSvBottomMargin + GetRowDragHintWidth();

        mpPane->mLeftMargin   = mSvLeftMargin;
        mpPane->mRightMargin  = ( hiddenRowsCnt == 0 ) ?
                                mSvRightMargin : mSvRightMargin + GetCollapsedRowIconHeight();
    }
}

void cbRowDragPlugin::OnInitPlugin()
{
    cbDockPane** panes = mpLayout->GetPanesArray();

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

        if ( panes[i]->MatchesMask( mPaneMask ) )
        {
            mpPane = panes[i];

            SetPaneMargins();
        }
}

/*** helpers for drag&drop ***/

void cbRowDragPlugin::SetMouseCapture( bool captureOn )
{
    if ( mCaptureIsOn == captureOn ) return;

    if ( captureOn ) 
    {
        mpLayout->CaptureEventsForPane( mpPane );
        mpLayout->CaptureEventsForPlugin( this );
    }
    else 
    {
        mpLayout->ReleaseEventsFromPane( mpPane );
        mpLayout->ReleaseEventsFromPlugin( this );
    }

    mCaptureIsOn = captureOn;
}

void cbRowDragPlugin::UnhighlightItemInFocus()
{
    wxClientDC dc( &mpLayout->GetParentFrame() );

    if ( mpRowInFocus ) 

        DrawRowDragHint( mpRowInFocus, dc, false );
    else
    if ( mCollapsedIconInFocus != - 1 )

        DrawCollapsedRowIcon( mCollapsedIconInFocus, dc, false );
}

void cbRowDragPlugin::ShowDraggedRow( int offset )
{
    // create combined image of pane and dragged
    // row on it, in the mpCombinedImage bitmap

    if ( mpPane->IsHorizontal() )
    {
        if ( mInitialRowOfs + offset + mRowImgDim.y > mCombRect.y + mCombRect.height )

            offset = mCombRect.y + mCombRect.height - mRowImgDim.y - mInitialRowOfs;

        if ( mInitialRowOfs + offset < mCombRect.y )

            offset = mCombRect.y - mInitialRowOfs;

        int x, y = mInitialRowOfs + offset;
        mpPane->FrameToPane( &x, &y );
        mCurDragOfs = y;
    }
    else
    {
        if ( mInitialRowOfs + offset + mRowImgDim.x > mCombRect.x + mCombRect.width )

            offset = mCombRect.x + mCombRect.width - mRowImgDim.x - mInitialRowOfs;

        if ( mInitialRowOfs + offset < mCombRect.x )

            offset = mCombRect.x - mInitialRowOfs;

        int x = mInitialRowOfs + offset, y;
        mpPane->FrameToPane( &x, &y );
        mCurDragOfs = x;
    }

    wxMemoryDC rowImgDc;
    rowImgDc.SelectObject ( *mpRowImage );

    wxMemoryDC paneImgDc;
    paneImgDc.SelectObject( *mpPaneImage );

    wxMemoryDC combImgDc;
    combImgDc.SelectObject( *mpCombinedImage );

    combImgDc.Blit( 0,0, mCombRect.width, mCombRect.height,
                    &paneImgDc, 0,0, wxCOPY );

    if ( mpPane->IsHorizontal() )
    {    
        combImgDc.Blit( 0, mInitialRowOfs + offset - mCombRect.y,
                        mCombRect.width, mRowImgDim.y,
                        &rowImgDc, 0,0, wxCOPY );
    }
    else
    {
        combImgDc.Blit( mInitialRowOfs + offset - mCombRect.x,
                        0,
                        mRowImgDim.x, mCombRect.height,
                        &rowImgDc, 0,0, wxCOPY );
    }

    int scrX = mCombRect.x,
        scrY = mCombRect.y;

    mpLayout->GetParentFrame().ClientToScreen( &scrX, &scrY );

    mpScrDc->Blit( scrX, scrY, mCombRect.width, mCombRect.height,
                   &combImgDc, 0,0, wxCOPY );

    rowImgDc .SelectObject( wxNullBitmap );
    paneImgDc.SelectObject( wxNullBitmap );
    combImgDc.SelectObject( wxNullBitmap );
}

wxBitmap* cbRowDragPlugin::CaptureDCArea( wxDC& dc, wxRect& area )
{
    wxBitmap* pBmp = new wxBitmap( int(area.width), int(area.height) );

    wxMemoryDC mdc;
    mdc.SelectObject( *pBmp );

    mdc.Blit( 0,0, area.width, area.height, &dc, area.x, area.y, wxCOPY );
    mdc.SelectObject( wxNullBitmap );

    return pBmp;
}

void cbRowDragPlugin::PrepareForRowDrag()
{
    wxRect rowBounds = mpRowInFocus->mBoundsInParent;

    if ( mpPane->IsHorizontal() )
    {
        mCombRect         = mpPane->mBoundsInParent;

        mCombRect.x += mpPane->mLeftMargin - ROW_DRAG_HINT_WIDTH - 1;
        mCombRect.y += mpPane->mTopMargin;

        mCombRect.width  -= mpPane->mLeftMargin + mpPane->mRightMargin - ROW_DRAG_HINT_WIDTH - 1 - 1;
        mCombRect.height -= mpPane->mTopMargin  + mpPane->mBottomMargin;

        mCombRect.height += 2*rowBounds.height;
        mCombRect.y      -= rowBounds.height;
        mInitialRowOfs     = rowBounds.y;

        rowBounds.y      -= 1;
        rowBounds.height += 2;
        rowBounds.x      = mCombRect.x;
        rowBounds.width  = mCombRect.width;

        mRowImgDim.y     = rowBounds.height;
    }
    else
    {
        mCombRect = mpPane->mBoundsInParent;

        mCombRect.y += mpPane->mTopMargin  - 1; 
        mCombRect.x += mpPane->mLeftMargin - 1;
            ;
        mCombRect.height -= mpPane->mTopMargin  + mpPane->mBottomMargin - ROW_DRAG_HINT_WIDTH - 1 - 1;
        mCombRect.width  -= mpPane->mLeftMargin + mpPane->mRightMargin;

        mCombRect.width += 2*rowBounds.width;
        mCombRect.x     -= rowBounds.width;
        mInitialRowOfs    = rowBounds.x;

        rowBounds.x      -= 1;
        rowBounds.width  += 2;
        rowBounds.y      = mCombRect.y;
        rowBounds.height = mCombRect.height;

        mRowImgDim.x     = rowBounds.width;
    }
    // output cobination results onto frame's client area
    wxScreenDC::StartDrawingOnTop(&mpLayout->GetParentFrame());
    mpScrDc = new wxScreenDC();

    int x = mCombRect.x, y = mCombRect.y;
    mpLayout->GetParentFrame().ClientToScreen( &x, &y );

    wxRect scrRect = mCombRect;
    scrRect.x = x;
    scrRect.y = y;

    mpPaneImage = CaptureDCArea( *mpScrDc, scrRect );

    wxMemoryDC mdc;
    mdc.SelectObject( *mpPaneImage );
    mdc.SetDeviceOrigin( -mCombRect.x, -mCombRect.y );

    DrawRectShade( rowBounds, mdc, -1, mpLayout->mGrayPen,  mpLayout->mDarkPen  );
    DrawRectShade( rowBounds, mdc, 0, mpLayout->mLightPen, mpLayout->mBlackPen );

    mpRowImage = CaptureDCArea( mdc, rowBounds );

    // draw dark empty-row placeholder
    DrawEmptyRow( mdc, rowBounds );

    //DrawRectShade( rowBounds, mdc, 0, mpLayout->mGrayPen,  mpLayout->mDarkPen  );
    DrawRectShade( rowBounds, mdc, -1, mpLayout->mGrayPen, mpLayout->mGrayPen );

    mdc.SelectObject( wxNullBitmap );

    mpCombinedImage = new wxBitmap( int(mCombRect.width), int(mCombRect.height) );

    // show it for the first time
    ShowDraggedRow( 0 );
}

void cbRowDragPlugin::DrawEmptyRow( wxDC& dc, wxRect& rowBounds )
{
    wxBrush bkBrush( mpLayout->mDarkPen.GetColour(), wxSOLID );

    // paint the "dark" empty-row placeholder

    dc.SetBrush( bkBrush );
    dc.SetPen  ( mpLayout->mNullPen );

    dc.DrawRectangle( rowBounds.x, rowBounds.y, 
                      rowBounds.width+1, rowBounds.height+1 );

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::ShowPaneImage()
{
    int scrX = 0, scrY = 0;

    mpLayout->GetParentFrame().ClientToScreen( &scrX, &scrY );

    wxMemoryDC mdc;
    mdc.SelectObject( *mpPaneImage );

    mpScrDc->Blit( mCombRect.x + scrX, mCombRect.y + scrY, 
                   mCombRect.width, mCombRect.height,
                   &mdc, 0,0, wxCOPY );

    mdc.SelectObject( wxNullBitmap );
}

void cbRowDragPlugin::FinishOnScreenDraw()
{
    wxScreenDC::EndDrawingOnTop();

    delete mpScrDc;
    delete mpCombinedImage;
    delete mpPaneImage;
    delete mpRowImage;

    mpScrDc = NULL;
    
    mpCombinedImage = mpPaneImage = mpRowImage = NULL;
}

void cbRowDragPlugin::CollapseRow( cbRowInfo* pRow )
{
    int iconCnt = GetHRowsCountForPane( mpPane );

    mpLayout->GetUpdatesManager().OnStartChanges();

    cbBarInfo* pBar = pRow->mBars[0];

    int rowNo = 0;

    cbRowInfo* pCur = pRow;
    while( pCur->mpPrev ) { ++rowNo; pCur = pCur->mpPrev; }

    while( pBar )
    {
        cbHiddenBarInfo* pHBInfo = new cbHiddenBarInfo();
                            
        pHBInfo->mpBar      = pBar;
        pHBInfo->mRowNo     = rowNo;
        pHBInfo->mIconNo    = iconCnt;
        pHBInfo->mAlignment    = mpPane->mAlignment;

        mHiddenBars.Append( (wxObject*) pHBInfo );

        // hide it
        if ( pBar->mpBarWnd )

            pBar->mpBarWnd->Show( false );

        pBar->mState = wxCBAR_HIDDEN;

        cbBarInfo* pNext = pBar->mpNext;

        pBar->mpRow  = NULL;
        pBar->mpNext = NULL;
        pBar->mpPrev = NULL;

        pBar = pNext;
    }

    mpPane->GetRowList().Remove( pRow );
    mpPane->InitLinksForRows();

    delete pRow;

    SetPaneMargins();

    mpLayout->RecalcLayout(false);

    mpRowInFocus = NULL;

    mpLayout->GetUpdatesManager().OnFinishChanges();
    mpLayout->GetUpdatesManager().UpdateNow();
}

void cbRowDragPlugin::ExpandRow( int collapsedIconIdx )
{
    mpLayout->GetUpdatesManager().OnStartChanges();

    cbRowInfo* pNewRow = new cbRowInfo();

    wxNode* pNode = mHiddenBars.GetFirst();

    int rowNo = 0;

    // move bars from internal list to the newly expanded row 

    while( pNode )
    {
        cbHiddenBarInfo* pHBInfo = (cbHiddenBarInfo*)pNode->GetData();

        if ( pHBInfo->mAlignment     == mpPane->mAlignment &&
             pHBInfo->mIconNo        == collapsedIconIdx   )
        {
            rowNo = pHBInfo->mRowNo;

            if ( pHBInfo->mpBar->mState == wxCBAR_HIDDEN )
            {
                pNewRow->mBars.Add( pHBInfo->mpBar );

                pHBInfo->mpBar->mState = ( mpPane->IsHorizontal() ) 
                                         ? wxCBAR_DOCKED_HORIZONTALLY
                                         : wxCBAR_DOCKED_VERTICALLY;
            }

            // remove bar info from internal list

            wxNode* pNext = pNode->GetNext();

            delete pHBInfo;
            mHiddenBars.DeleteNode( pNode );

            pNode = pNext;
        }
        else
        {
            // decrease incon numbers with higher indicies, since this
            // row is now removed from the hidden-rows list

            if ( pHBInfo->mIconNo    > collapsedIconIdx &&
                 pHBInfo->mAlignment == mpPane->mAlignment )

                --pHBInfo->mIconNo;

            pNode = pNode->GetNext();
        }
    }

    mpPane->InitLinksForRow( pNewRow );

    // insert row into pane at it's original position

    if ( pNewRow->mBars.GetCount() )
    {
        cbRowInfo* beforeRowNode = mpPane->GetRow( rowNo );

        mpPane->InsertRow( pNewRow, beforeRowNode );
    }
    else
        delete pNewRow;

    SetPaneMargins();

    mpLayout->RecalcLayout(false);

    mCollapsedIconInFocus = -1;

    mpLayout->GetUpdatesManager().OnFinishChanges();
    mpLayout->GetUpdatesManager().UpdateNow();


    /*
    wxNode* pRowNode = mHiddenRows.Nth( collapsedIconIdx );

    mpLayout->GetUpdatesManager().OnStartChanges();

    // insert at the end of rows list
    mpPane->InsertRow( pRowNode, NULL );

    int success = mHiddenRows.DeleteNode( pRowNode );
    // DBG::
    wxASSERT( success );

    SetPaneMargins();

    mpLayout->RecalcLayout(false);

    mCollapsedIconInFocus = -1;

    mpLayout->GetUpdatesManager().OnFinishChanges();
    mpLayout->GetUpdatesManager().UpdateNow();
    */
}

void cbRowDragPlugin::InsertDraggedRowBefore( cbRowInfo* pBeforeRow )
{
    if ( mpRowInFocus != pBeforeRow &&
         mpRowInFocus->mpNext != pBeforeRow 
       )
    {
        mpPane->GetRowList().Remove( mpRowInFocus );

        mpPane->InsertRow( mpRowInFocus, pBeforeRow );
    }
    else 
    {
        // otherwise, nothing has happned (row positions do not change)

        //wxClientDC dc( &mpLayout->GetParentFrame() );

        //mpPane->PaintRow( mpRowInFocus, dc );
        //DrawRowDragHint( mpRowInFocus, dc, false );
    }
}

bool cbRowDragPlugin::ItemIsInFocus()
{
    return ( mpRowInFocus || mCollapsedIconInFocus != - 1 );
}

void cbRowDragPlugin::CheckPrevItemInFocus( cbRowInfo* pRow, int iconIdx )
{
    wxClientDC dc( &mpLayout->GetParentFrame() );

    if ( pRow != NULL && mpRowInFocus == pRow ) return;
    if ( iconIdx != -1 && mCollapsedIconInFocus == iconIdx ) return;

    UnhighlightItemInFocus();

    if ( iconIdx != - 1 )
    
        DrawCollapsedRowIcon( iconIdx, dc, true );

    else
    if ( pRow != NULL )

        DrawRowDragHint( pRow, dc, true );
}

cbRowInfo* cbRowDragPlugin::GetFirstRow()
{
    return ( mpPane->GetRowList().GetCount() ) 
           ? mpPane->GetRowList()[0]
           : NULL;
}

/*** "hard-coded" metafile for NN-look ***/

void cbRowDragPlugin::DrawTrianUp( wxRect& inRect, wxDC& dc )
{
    int xOfs = (inRect.width - ICON_TRIAN_WIDTH)/2;

    wxBrush br( mTrianInnerColor, wxSOLID );

    dc.SetBrush( br );
    dc.SetPen( mpLayout->mBlackPen );

    wxPoint points[3];
    points[0].x = inRect.x + xOfs;
    points[0].y = inRect.y + inRect.height - 1;
    points[1].x = inRect.x + xOfs + ICON_TRIAN_WIDTH/2 + 1;
    points[1].y = inRect.y + inRect.height - 2 - ICON_TRIAN_HEIGHT;
    points[2].x = inRect.x + xOfs + ICON_TRIAN_WIDTH+1;
    points[2].y = inRect.y + inRect.height - 1;

    dc.DrawPolygon( 3, points );

    // higlight upper-right edge of triangle
    dc.SetPen( mpLayout->mLightPen );
    dc.DrawLine( points[2].x, points[2].y,
                 points[0].x, points[0].y );

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::DrawTrianDown( wxRect& inRect, wxDC& dc )
{
    int xOfs = (inRect.width - ICON_TRIAN_WIDTH)/2;

    wxBrush br( mTrianInnerColor, wxSOLID );

    dc.SetBrush( br );
    dc.SetPen( mpLayout->mBlackPen );

    wxPoint points[3];
    points[0].x = inRect.x + xOfs;
    points[0].y = inRect.y;
    points[1].x = inRect.x + xOfs + ICON_TRIAN_WIDTH;
    points[1].y = inRect.y;
    points[2].x = inRect.x + xOfs + ICON_TRIAN_WIDTH/2;
    points[2].y = inRect.y + ICON_TRIAN_HEIGHT;

    dc.DrawPolygon( 3, points );

    // higlight upper-right edge of triangle
    dc.SetPen( mpLayout->mLightPen );
    dc.DrawLine( points[2].x, points[2].y,
                 points[1].x, points[1].y );

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::DrawTrianRight( wxRect& inRect, wxDC& dc )
{
    int yOfs = (inRect.height - ICON_TRIAN_WIDTH)/2;

    wxBrush br( mTrianInnerColor, wxSOLID );

    dc.SetBrush( br );
    dc.SetPen( mpLayout->mBlackPen );

    wxPoint points[3];
    points[0].x = inRect.x;
    points[0].y = inRect.y + yOfs + ICON_TRIAN_WIDTH;
    points[1].x = inRect.x;
    points[1].y = inRect.y + yOfs;
    points[2].x = inRect.x + ICON_TRIAN_HEIGHT;
    points[2].y = inRect.y + yOfs + ICON_TRIAN_WIDTH/2;

    dc.DrawPolygon( 3, points );

    // higlight upper-right edge of triangle
    dc.SetPen( mpLayout->mLightPen );
    dc.DrawLine( points[0].x, points[0].y,
                 points[2].x, points[2].y );

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::Draw3DPattern( wxRect& inRect, wxDC& dc )
{
    for( int y = inRect.y; y < inRect.y + inRect.height; y+=3 )
    
        for( int x = inRect.x; x < inRect.x + inRect.width; x+=3 )
        {
            dc.SetPen( mpLayout->mLightPen );
            dc.DrawPoint( x,y );
            dc.SetPen( mpLayout->mBlackPen );
            dc.DrawPoint( x+1, y+1 );
        }
}

void cbRowDragPlugin::DrawRombShades( wxPoint& p1, wxPoint& p2, 
                                      wxPoint& p3, wxPoint& p4,
                                      wxDC& dc )
{
    dc.SetPen( mpLayout->mLightPen );
    dc.DrawLine( p1.x, p1.y, p2.x, p2.y );
    dc.DrawLine( p2.x, p2.y, p3.x, p3.y );
    dc.SetPen( mpLayout->mDarkPen );
    dc.DrawLine( p3.x, p3.y, p4.x, p4.y );
    dc.DrawLine( p4.x, p4.y, p1.x, p1.y );
}

void cbRowDragPlugin::DrawOrtoRomb( wxRect& inRect, wxDC& dc, const wxBrush& bkBrush )
{
    dc.SetBrush( bkBrush );
    dc.SetPen( mpLayout->mBlackPen );

    wxPoint points[4];

    if ( inRect.width > inRect.height )
    {
        // horizontal orienation
        points[0].x = inRect.x;
        points[0].y = inRect.y + inRect.height;
        points[1].x = inRect.x;
        points[1].y = inRect.y;
        points[2].x = inRect.x + inRect.width;
        points[2].y = inRect.y;
        points[3].x = inRect.x + inRect.width - COLLAPSED_ICON_HEIGHT;
        points[3].y = inRect.y + inRect.height;

        dc.DrawPolygon( 4, points );

        // squeeze romb's bounds to create an inner-shade shape
        ++points[0].x;
        --points[0].y;
        ++points[1].x;
        ++points[1].y;
        --points[2].x; --points[2].x;
        ++points[2].y;
        --points[3].y;

        DrawRombShades( points[0], points[1], points[2], points[3], dc );
    }
    else
    {
        // vertical orientation
        points[0].x = inRect.x + inRect.width;
        points[0].y = inRect.y + inRect.height;
        points[1].x = inRect.x;
        points[1].y = inRect.y + inRect.height;
        points[2].x = inRect.x;
        points[2].y = inRect.y;
        points[3].x = inRect.x + inRect.width;
        points[3].y = inRect.y + COLLAPSED_ICON_HEIGHT;

        dc.DrawPolygon( 4, points );

        // squeeze romb's bounds to create an inner-shade shape
        --points[0].y ;
        --points[0].x;
        ++points[1].x;
        --points[1].y;
        ++points[2].y; ++points[2].y;
        ++points[2].x;
        --points[3].x;

        DrawRombShades( points[1], points[2], points[3], points[0], dc );
    }

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::DrawRomb( wxRect& inRect, wxDC& dc, const wxBrush& bkBrush )
{
    wxPoint points[4];

    dc.SetBrush( bkBrush );
    dc.SetPen( mpLayout->mBlackPen );

    if ( inRect.width > inRect.height )
    {
        // horizontal orientation
        points[0].x = inRect.x;
        points[0].y = inRect.y + inRect.height;
        points[1].x = inRect.x + COLLAPSED_ICON_HEIGHT;
        points[1].y = inRect.y;
        points[2].x = inRect.x + inRect.width;
        points[2].y = inRect.y;
        points[3].x = inRect.x + inRect.width - COLLAPSED_ICON_HEIGHT;
        points[3].y = inRect.y + inRect.height;

        dc.DrawPolygon( 4, points );

        // squeeze romb's bounds to create an inner-shade shape
        ++points[0].x ;++points[0].x ;
        --points[0].y;
        ++points[1].y;
        --points[2].x; --points[2].x;
        ++points[2].y;
        //--points[3].x ;
        --points[3].y;

        DrawRombShades( points[0], points[1], points[2], points[3], dc );

    }
    else
    {
        // vertical orientation
        points[0].x = inRect.x + inRect.width;
        points[0].y = inRect.y + inRect.height;
        points[1].x = inRect.x;
        points[1].y = inRect.y + inRect.height - COLLAPSED_ICON_HEIGHT;
        points[2].x = inRect.x;
        points[2].y = inRect.y;
        points[3].x = inRect.x + inRect.width;
        points[3].y = inRect.y + COLLAPSED_ICON_HEIGHT;

        dc.DrawPolygon( 4, points );

        // squeeze romb's bounds to create an inner-shade shape
        --points[0].y ;--points[0].y ;
        --points[0].x;
        ++points[1].x;
        ++points[2].y; ++points[2].y;
        ++points[2].x;
        --points[3].x;

        DrawRombShades( points[1], points[2], points[3], points[0], dc );
    }

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::DrawRectShade( wxRect& inRect, wxDC& dc, 
                                     int level, const wxPen& upperPen, const wxPen& lowerPen )
{
    // upper shade
    dc.SetPen( upperPen );
    dc.DrawLine( inRect.x - level, 
                 inRect.y - level, 
                 inRect.x + inRect.width - 1 + level, 
                 inRect.y - level);
    dc.DrawLine( inRect.x - level, inRect.y - level,
                 inRect.x - level, inRect.y + inRect.height - 1 + level );

    // lower shade
    dc.SetPen( lowerPen );
    dc.DrawLine( inRect.x - level, 
                 inRect.y + inRect.height - 1 + level,
                 inRect.x + inRect.width  + level,
                 inRect.y + inRect.height - 1 + level);
    dc.DrawLine( inRect.x + inRect.width - 1 + level,
                 inRect.y - level,
                 inRect.x + inRect.width - 1 + level,
                 inRect.y + inRect.height + level);

    dc.SetBrush( wxNullBrush );
}

void cbRowDragPlugin::Draw3DRect( wxRect& inRect, wxDC& dc, const wxBrush& bkBrush )
{
    dc.SetPen( mpLayout->mNullPen );
    dc.SetBrush( bkBrush );

    dc.DrawRectangle( inRect.x, inRect.y,
                      inRect.width, inRect.height );

    DrawRectShade( inRect, dc, 0, mpLayout->mLightPen, mpLayout->mDarkPen );
}

int  cbRowDragPlugin::GetCollapsedIconsPos()
{
    RowArrayT& rows = mpPane->GetRowList();

    if ( rows.GetCount() == 0 ) 
    {
        if ( mpPane->IsHorizontal() )
            
            return mpPane->mBoundsInParent.y + mpPane->mTopMargin;
        else
            return mpPane->mBoundsInParent.x + mpPane->mLeftMargin;
    }

    wxRect& bounds = rows[ rows.GetCount() - 1 ]->mBoundsInParent;

    if ( mpPane->IsHorizontal() )

        return bounds.y + bounds.height + 1;
    else
        return bounds.x + bounds.width  + 1;

}

void cbRowDragPlugin::GetRowHintRect( cbRowInfo* pRow, wxRect& rect )
{
    wxRect& bounds = pRow->mBoundsInParent;

    if ( mpPane->IsHorizontal() )
    {
        rect.x = bounds.x - ROW_DRAG_HINT_WIDTH - 1;
        rect.y = bounds.y;
        rect.width = ROW_DRAG_HINT_WIDTH;
        rect.height = bounds.height;
    }
    else
    {
        rect.x = bounds.x;
        rect.y = bounds.y + bounds.height + 1;
        rect.width = bounds.width;
        rect.height = ROW_DRAG_HINT_WIDTH;
    }
}

void cbRowDragPlugin::GetCollapsedInconRect( int iconIdx, wxRect& rect )
{
    int upper = GetCollapsedIconsPos();

    int right = (iconIdx == 0 ) 
                ? 0 : iconIdx * (COLLAPSED_ICON_WIDTH - COLLAPSED_ICON_HEIGHT);

    if ( mpPane->IsHorizontal() )
    {
        rect.x = mpPane->mBoundsInParent.x + mpPane->mLeftMargin - ROW_DRAG_HINT_WIDTH - 1
                 + right;

        rect.y = upper;
        rect.width  = COLLAPSED_ICON_WIDTH;
        rect.height = COLLAPSED_ICON_HEIGHT;
    }
    else
    {
         rect.x = upper;
         rect.y = mpPane->mBoundsInParent.y + mpPane->mBoundsInParent.height 
                 - mpPane->mBottomMargin + ROW_DRAG_HINT_WIDTH + 1
                 - right - COLLAPSED_ICON_WIDTH;

        rect.height = COLLAPSED_ICON_WIDTH;
        rect.width  = COLLAPSED_ICON_HEIGHT;
    }
}

/*** overridables ***/

void cbRowDragPlugin::DrawCollapsedRowIcon( int index, wxDC& dc, bool isHighlighted )
{
    wxRect rect;
    GetCollapsedInconRect( index, rect );

    wxBrush  hiBrush ( mHightColor, wxSOLID );
    wxBrush  lowBrush( mLowColor,   wxSOLID );
    wxBrush& curBrush = ( isHighlighted ) ? hiBrush : lowBrush;

    if ( mpPane->IsHorizontal() )
    {                 
        if ( index == 0 )

            DrawOrtoRomb( rect, dc, curBrush );
        else
            DrawRomb( rect, dc, curBrush );

        int triOfs = (index == 0) ? TRIANGLE_OFFSET : TRIANGLE_OFFSET + COLLAPSED_ICON_HEIGHT;

        wxRect triRect;
        triRect.x = triOfs + rect.x;

        triRect.width = ICON_TRIAN_HEIGHT;
        triRect.y = rect.y;
        triRect.height = rect.height;

        DrawTrianRight( triRect, dc );

        wxRect patRect;
        patRect.x      = triOfs + ICON_TRIAN_HEIGHT + TRIANGLE_TO_PAT_GAP + rect.x;
        patRect.y      = rect.y + PAT_OFFSET; 
        patRect.width  = rect.width - (patRect.x - rect.x) - COLLAPSED_ICON_HEIGHT - PAT_OFFSET;
        patRect.height = rect.height - PAT_OFFSET*2;

        Draw3DPattern( patRect, dc );
    }
    else
    {
        if ( index == 0 )

            DrawOrtoRomb( rect, dc, curBrush );
        else
            DrawRomb( rect, dc, curBrush );

        int triOfs = (index == 0) 
                     ? TRIANGLE_OFFSET + ICON_TRIAN_HEIGHT
                     : TRIANGLE_OFFSET + COLLAPSED_ICON_HEIGHT + ICON_TRIAN_HEIGHT;

        wxRect triRect;
        triRect.y      = rect.y + rect.height - triOfs;
        triRect.x      = rect.x;
        triRect.width  = rect.width;
        triRect.height = ICON_TRIAN_HEIGHT;

        DrawTrianUp( triRect, dc );

        wxRect patRect;
        patRect.y      = rect.y + COLLAPSED_ICON_HEIGHT + PAT_OFFSET;
        patRect.x      = rect.x + PAT_OFFSET; 
        patRect.width  = rect.width - 2*PAT_OFFSET ;
        patRect.height = rect.height - triOfs - 2*PAT_OFFSET - COLLAPSED_ICON_HEIGHT;

        Draw3DPattern( patRect, dc );
    }
}

void cbRowDragPlugin::DrawRowDragHint( cbRowInfo* pRow , wxDC& dc, bool isHighlighted )
{
    wxRect rect;
    GetRowHintRect( pRow, rect );

    wxBrush  hiBrush ( mHightColor, wxSOLID );
    wxBrush  lowBrush( mLowColor,   wxSOLID );
    wxBrush& curBrush = ( isHighlighted ) ? hiBrush : lowBrush;

    Draw3DRect( rect, dc, curBrush );

    if ( mpPane->IsHorizontal() )
    {
        wxRect triRect;
        triRect.y       = rect.y + TRIANGLE_OFFSET;
        triRect.x       = rect.x;
        triRect.width  = rect.width;
        triRect.height = ICON_TRIAN_HEIGHT;

        DrawTrianDown( triRect, dc );

        wxRect patRect;
        patRect.x      = rect.x + PAT_OFFSET;
        patRect.y      = rect.y + TRIANGLE_OFFSET + ICON_TRIAN_HEIGHT + TRIANGLE_TO_PAT_GAP;
        patRect.width  = rect.width - 2*PAT_OFFSET;
        patRect.height = rect.height - ( patRect.y - rect.y ) - PAT_OFFSET;
        Draw3DPattern( patRect, dc );

        dc.SetPen( mpLayout->mLightPen );
        dc.DrawLine( rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height );
    }
    else
    {
        wxRect triRect;
        triRect.x       = rect.x + TRIANGLE_OFFSET;
        triRect.y       = rect.y;
        triRect.height = rect.height;
        triRect.width  = ICON_TRIAN_HEIGHT;

        DrawTrianRight( triRect, dc );

        wxRect patRect;
        patRect.y      = rect.y + PAT_OFFSET;
        patRect.x      = rect.x + TRIANGLE_OFFSET + ICON_TRIAN_HEIGHT + TRIANGLE_TO_PAT_GAP;
        patRect.height = rect.height - 2*PAT_OFFSET;
        patRect.width  = rect.width - ( patRect.x - rect.x ) - PAT_OFFSET;
        Draw3DPattern( patRect, dc );

        dc.SetPen( mpLayout->mLightPen );
        dc.DrawLine( rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height );
    }
}

void cbRowDragPlugin::DrawRowsDragHintsBorder( wxDC& WXUNUSED(dc) )
{
    // FIXME:: what was that?
}

void cbRowDragPlugin::DrawCollapsedRowsBorder( wxDC& dc )
{
    int colRowOfs = GetCollapsedIconsPos();
    wxRect& bounds = mpPane->mBoundsInParent;

    wxBrush bkBrush( mpLayout->mGrayPen.GetColour(), wxSOLID );
    dc.SetBrush( bkBrush );
    dc.SetPen( mpLayout->mDarkPen );

    if ( mpPane->IsHorizontal() )
    
        dc.DrawRectangle( bounds.x + mpPane->mLeftMargin - ROW_DRAG_HINT_WIDTH - 1,
                          colRowOfs,
                          bounds.width - mpPane->mLeftMargin - mpPane->mRightMargin + 2 + ROW_DRAG_HINT_WIDTH,
                          COLLAPSED_ICON_HEIGHT + 1);
    else
        dc.DrawRectangle( colRowOfs,
                          bounds.y + mpPane->mTopMargin - 1,
                          COLLAPSED_ICON_HEIGHT + 1,
                          bounds.height - mpPane->mTopMargin - mpPane->mBottomMargin 
                          - ROW_DRAG_HINT_WIDTH - 2 );

    dc.SetBrush( wxNullBrush );
}

static inline bool rect_contains_point( const wxRect& rect, int x, int y )
{
    return ( x >= rect.x &&
             y >= rect.y &&
             x <  rect.x + rect.width  &&
             y <  rect.y + rect.height );
}

bool cbRowDragPlugin::HitTestCollapsedRowIcon( int iconIdx, const wxPoint& pos )
{
    wxRect bounds;
    GetCollapsedInconRect( iconIdx, bounds );

    return rect_contains_point( bounds, pos.x, pos.y );
}

bool cbRowDragPlugin::HitTestRowDragHint( cbRowInfo* pRow, const wxPoint& pos )
{
    wxRect bounds;
    GetRowHintRect( pRow, bounds );

    return rect_contains_point( bounds, pos.x, pos.y );
}

