/*************************************************************************
 *
 *  $RCSfile: frmsel.cxx,v $
 *
 *  $Revision: 1.17 $
 *
 *  last change: $Author: hr $ $Date: 2004-08-02 17:44:39 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef SVX_FRMSEL_HXX
#include "frmsel.hxx"
#endif

#include <algorithm>
#include <math.h>

#ifndef SVX_FRMSELIMPL_HXX
#include "frmselimpl.hxx"
#endif
#ifndef SVX_ACCESSIBLEFRAMESELECTOR_HXX
#include "AccessibleFrameSelector.hxx"
#endif
#ifndef _SVX_DIALMGR_HXX
#include "dialmgr.hxx"
#endif

#ifndef _SVX_DIALOGS_HRC
#include "dialogs.hrc"
#endif
#ifndef SVX_FRMSEL_HRC
#include "frmsel.hrc"
#endif

namespace svx {

using ::com::sun::star::uno::Reference;
using ::com::sun::star::accessibility::XAccessible;

// ============================================================================
// global functions from framebordertype.hxx

FrameBorderType GetFrameBorderTypeFromIndex( size_t nIndex )
{
    DBG_ASSERT( nIndex < FRAMEBORDERTYPE_COUNT,
        "svx::GetFrameBorderTypeFromIndex - invalid index" );
    return static_cast< FrameBorderType >( nIndex + 1 );
}

size_t GetIndexFromFrameBorderType( FrameBorderType eBorder )
{
    DBG_ASSERT( eBorder != FRAMEBORDER_NONE,
        "svx::GetIndexFromFrameBorderType - invalid frame border type" );
    return static_cast< size_t >( eBorder ) - 1;
}

// ============================================================================

namespace {

/** Space between outer control border and any graphical element of the control. */
const long FRAMESEL_GEOM_OUTER    = 2;

/** Space between arrows and usable inner area. */
const long FRAMESEL_GEOM_INNER    = 3;

/** Maximum width to draw a frame border style. */
const long FRAMESEL_GEOM_WIDTH    = 9;

/** Additional margin for click area of outer lines. */
const long FRAMESEL_GEOM_ADD_CLICK_OUTER = 5;

/** Additional margin for click area of inner lines. */
const long FRAMESEL_GEOM_ADD_CLICK_INNER = 2;

// ----------------------------------------------------------------------------

static const frame::Style   OBJ_FRAMESTYLE_DONTCARE( 3, 0, 0 );
static const FrameBorder    OBJ_FRAMEBORDER_NONE( FRAMEBORDER_NONE );

// ----------------------------------------------------------------------------

/** Returns the corresponding flag for a frame border. */
FrameSelFlags lclGetFlagFromType( FrameBorderType eBorder )
{
    switch( eBorder )
    {
        case FRAMEBORDER_LEFT:      return FRAMESEL_LEFT;
        case FRAMEBORDER_RIGHT:     return FRAMESEL_RIGHT;
        case FRAMEBORDER_TOP:       return FRAMESEL_TOP;
        case FRAMEBORDER_BOTTOM:    return FRAMESEL_BOTTOM;
        case FRAMEBORDER_HOR:       return FRAMESEL_INNER_HOR;
        case FRAMEBORDER_VER:       return FRAMESEL_INNER_VER;
        case FRAMEBORDER_TLBR:      return FRAMESEL_DIAG_TLBR;
        case FRAMEBORDER_BLTR:      return FRAMESEL_DIAG_BLTR;
    }
    return FRAMESEL_NONE;
}

/** Converts an SvxBorderLine line width (in twips) to a pixel line width. */
inline sal_uInt16 lclGetPixel( USHORT nWidth )
{
    // convert all core styles expect 0 to a visible UI style (at least 1 pixel), map 1pt to 1pixel
    return nWidth ? std::min< sal_uInt16 >( std::max< sal_uInt16 >( (nWidth + 5) / 20, 1 ), FRAMESEL_GEOM_WIDTH ) : 0;
}

/** Merges the rSource polypolygon into the rDest polypolygon. */
inline void lclPolyPolyUnion( PolyPolygon& rDest, const PolyPolygon& rSource )
{
    const PolyPolygon aTmp( rDest );
    aTmp.GetUnion( rSource, rDest );
}

} // namespace

// ============================================================================
// FrameBorder
// ============================================================================

FrameBorder::FrameBorder( FrameBorderType eType ) :
    meType( eType ),
    meState( FRAMESTATE_HIDE ),
    meKeyLeft( FRAMEBORDER_NONE ),
    meKeyRight( FRAMEBORDER_NONE ),
    meKeyTop( FRAMEBORDER_NONE ),
    meKeyBottom( FRAMEBORDER_NONE ),
    mbEnabled( false ),
    mbSelected( false )
{
}

void FrameBorder::Enable( FrameSelFlags nFlags )
{
    mbEnabled = (nFlags & lclGetFlagFromType( meType )) != 0;
    if( !mbEnabled )
        SetState( FRAMESTATE_HIDE );
}

void FrameBorder::SetCoreStyle( const SvxBorderLine* pStyle )
{
    if( pStyle )
        maCoreStyle = *pStyle;
    else
        maCoreStyle = SvxBorderLine();

    // from twips to points
    maUIStyle.Set( maCoreStyle, 0.05, FRAMESEL_GEOM_WIDTH, true );
    meState = maUIStyle.Prim() ? FRAMESTATE_SHOW : FRAMESTATE_HIDE;
}

void FrameBorder::SetState( FrameBorderState eState )
{
    meState = eState;
    switch( meState )
    {
        case FRAMESTATE_SHOW:
            DBG_ERRORFILE( "svx::FrameBorder::SetState - use SetCoreStyle to make border visible" );
        break;
        case FRAMESTATE_HIDE:
            maCoreStyle = SvxBorderLine();
            maUIStyle.Clear();
        break;
        case FRAMESTATE_DONTCARE:
            maCoreStyle = SvxBorderLine();
            maUIStyle = OBJ_FRAMESTYLE_DONTCARE;
        break;
    }
}

void FrameBorder::AddFocusPolygon( const Polygon& rFocus )
{
    lclPolyPolyUnion( maFocusArea, rFocus );
}

void FrameBorder::MergeFocusToPolyPolygon( PolyPolygon& rPPoly ) const
{
    lclPolyPolyUnion( rPPoly, maFocusArea );
}

void FrameBorder::AddClickRect( const Rectangle& rRect )
{
    lclPolyPolyUnion( maClickArea, Polygon( rRect ) );
}

bool FrameBorder::ContainsClickPoint( const Point& rPos ) const
{
    return Region( maClickArea ).IsInside( rPos );
}

void FrameBorder::MergeClickAreaToPolyPolygon( PolyPolygon& rPPoly ) const
{
    lclPolyPolyUnion( rPPoly, maClickArea );
}

Rectangle FrameBorder::GetClickBoundRect() const
{
    return maClickArea.GetBoundRect();
}

void FrameBorder::SetKeyboardNeighbors(
        FrameBorderType eLeft, FrameBorderType eRight, FrameBorderType eTop, FrameBorderType eBottom )
{
    meKeyLeft = eLeft;
    meKeyRight = eRight;
    meKeyTop = eTop;
    meKeyBottom = eBottom;
}

FrameBorderType FrameBorder::GetKeyboardNeighbor( USHORT nKeyCode ) const
{
    FrameBorderType eBorder = FRAMEBORDER_NONE;
    switch( nKeyCode )
    {
        case KEY_LEFT:  eBorder = meKeyLeft;      break;
        case KEY_RIGHT: eBorder = meKeyRight;     break;
        case KEY_UP:    eBorder = meKeyTop;       break;
        case KEY_DOWN:  eBorder = meKeyBottom;    break;
        default:        DBG_ERRORFILE( "svx::FrameBorder::GetKeyboardNeighbor - unknown key code" );
    }
    return eBorder;
}

// ============================================================================
// FrameSelectorImpl
// ============================================================================

FrameSelectorImpl::FrameSelectorImpl( FrameSelector& rFrameSel ) :
    Resource( SVX_RES( RID_SVXSTR_BORDER_CONTROL ) ),
    mrFrameSel( rFrameSel ),
    maBmpArrows( ResId( BMP_FRMSEL_ARROWS ) ),
    maLeft( FRAMEBORDER_LEFT ),
    maRight( FRAMEBORDER_RIGHT ),
    maTop( FRAMEBORDER_TOP ),
    maBottom( FRAMEBORDER_BOTTOM ),
    maHor( FRAMEBORDER_HOR ),
    maVer( FRAMEBORDER_VER ),
    maTLBR( FRAMEBORDER_TLBR ),
    maBLTR( FRAMEBORDER_BLTR ),
    mnFlags( FRAMESEL_OUTER ),
    mbHor( false ),
    mbVer( false ),
    mbTLBR( false ),
    mbBLTR( false ),
    mbFullRepaint( true ),
    mbAutoSelect( true ),
    mbClicked( false ),
    mbHCMode( false ),
    mpAccess( 0 ),
    maChildVec( 8, static_cast< a11y::AccFrameSelector* >( 0 ) ),
    mxChildVec( 8 )
{
    FreeResource();

    maAllBorders.resize( FRAMEBORDERTYPE_COUNT, 0 );
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_LEFT   ) ] = &maLeft;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_RIGHT  ) ] = &maRight;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_TOP    ) ] = &maTop;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_BOTTOM ) ] = &maBottom;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_HOR    ) ] = &maHor;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_VER    ) ] = &maVer;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_TLBR   ) ] = &maTLBR;
    maAllBorders[ GetIndexFromFrameBorderType( FRAMEBORDER_BLTR   ) ] = &maBLTR;
#if OSL_DEBUG_LEVEL >= 2
    {
        bool bOk = true;
        for( FrameBorderCIter aIt( maAllBorders ); bOk && aIt.Is(); bOk = (*aIt != 0), ++aIt );
        DBG_ASSERT( bOk, "svx::FrameSelectorImpl::FrameSelectorImpl - missing entry in maAllBorders" );
    }
#endif

    //                             left neighbor     right neighbor     upper neighbor    lower neighbor
    maLeft.SetKeyboardNeighbors(   FRAMEBORDER_NONE, FRAMEBORDER_TLBR,  FRAMEBORDER_TOP,  FRAMEBORDER_BOTTOM );
    maRight.SetKeyboardNeighbors(  FRAMEBORDER_BLTR, FRAMEBORDER_NONE,  FRAMEBORDER_TOP,  FRAMEBORDER_BOTTOM );
    maTop.SetKeyboardNeighbors(    FRAMEBORDER_LEFT, FRAMEBORDER_RIGHT, FRAMEBORDER_NONE, FRAMEBORDER_TLBR );
    maBottom.SetKeyboardNeighbors( FRAMEBORDER_LEFT, FRAMEBORDER_RIGHT, FRAMEBORDER_BLTR, FRAMEBORDER_NONE );
    maHor.SetKeyboardNeighbors(    FRAMEBORDER_LEFT, FRAMEBORDER_RIGHT, FRAMEBORDER_TLBR, FRAMEBORDER_BLTR );
    maVer.SetKeyboardNeighbors(    FRAMEBORDER_TLBR, FRAMEBORDER_BLTR,  FRAMEBORDER_TOP,  FRAMEBORDER_BOTTOM );
    maTLBR.SetKeyboardNeighbors(   FRAMEBORDER_LEFT, FRAMEBORDER_VER,   FRAMEBORDER_TOP,  FRAMEBORDER_HOR );
    maBLTR.SetKeyboardNeighbors(   FRAMEBORDER_VER,  FRAMEBORDER_RIGHT, FRAMEBORDER_HOR,  FRAMEBORDER_BOTTOM );
}

FrameSelectorImpl::~FrameSelectorImpl()
{
    if( mpAccess )
        mpAccess->Invalidate();
    for( AccessibleImplVec::iterator aIt = maChildVec.begin(), aEnd = maChildVec.end(); aIt != aEnd; ++aIt )
        if( *aIt )
            (*aIt)->Invalidate();
}

// initialization -------------------------------------------------------------

void FrameSelectorImpl::Initialize( FrameSelFlags nFlags )
{
    mnFlags = nFlags;

    maEnabBorders.clear();
    for( FrameBorderIter aIt( maAllBorders ); aIt.Is(); ++aIt )
    {
        (*aIt)->Enable( mnFlags );
        if( (*aIt)->IsEnabled() )
            maEnabBorders.push_back( *aIt );
    }
    mbHor = maHor.IsEnabled();
    mbVer = maVer.IsEnabled();
    mbTLBR = maTLBR.IsEnabled();
    mbBLTR = maBLTR.IsEnabled();

    InitVirtualDevice();
}

void FrameSelectorImpl::InitColors()
{
    const StyleSettings& rSett = mrFrameSel.GetSettings().GetStyleSettings();
    maBackCol = rSett.GetFieldColor();
    mbHCMode = maBackCol.IsDark();
    maArrowCol = rSett.GetFieldTextColor();
    maMarkCol.operator=( maBackCol ).Merge( maArrowCol, mbHCMode ? 0x80 : 0xC0 );
    maHCLineCol = rSett.GetLabelTextColor();
}

void FrameSelectorImpl::InitArrowImageList()
{
    /*  Build the arrow images bitmap with current colors. */
    Bitmap aBmp( maBmpArrows );
    aBmp.Replace( Color( 0, 0, 0 ), maArrowCol );       // black -> arrow color
    aBmp.Replace( Color( 0, 255, 0 ), maMarkCol );      // green -> marker color
    aBmp.Replace( Color( 255, 0, 255 ), maBackCol );    // magenta -> background

    /*  Create the new image list. */
    maILArrows = ImageList( aBmp, 16 );
    DBG_ASSERT( maILArrows.GetImageSize().Height() == maILArrows.GetImageSize().Width(),
        "svx::FrameSelectorImpl::InitArrowImageList - images are not squarish" );
    mnArrowSize = maILArrows.GetImageSize().Height();
}

void FrameSelectorImpl::InitGlobalGeometry()
{
    Size aCtrlSize( mrFrameSel.CalcOutputSize( mrFrameSel.GetSizePixel() ) );
    /*  nMinSize is the lower of width and height (control will always be squarish).
        FRAMESEL_GEOM_OUTER is the minimal distance between inner control border
        and any element. */
    long nMinSize = Min( aCtrlSize.Width(), aCtrlSize.Height() ) - 2 * FRAMESEL_GEOM_OUTER;
    /*  nFixedSize is the size all existing elements need in one direction:
        the diag. arrow, space betw. arrow and frame border, outer frame border,
        inner frame border, other outer frame border, space betw. frame border
        and arrow, the other arrow. */
    long nFixedSize = 2 * mnArrowSize + 2 * FRAMESEL_GEOM_INNER + 3 * FRAMESEL_GEOM_WIDTH;
    /*  nBetwBordersSize contains the size between an outer and inner frame border (made odd). */
    long nBetwBordersSize = (((nMinSize - nFixedSize) / 2) - 1) | 1;

    /*  The final size of the usable area. */
    mnCtrlSize = 2 * nBetwBordersSize + nFixedSize;
    maVirDev.SetOutputSizePixel( Size( mnCtrlSize, mnCtrlSize ) );

    /*  Center the virtual device in the control. */
    maVirDevPos = Point( (aCtrlSize.Width() - mnCtrlSize) / 2, (aCtrlSize.Height() - mnCtrlSize) / 2 );
}

void FrameSelectorImpl::InitBorderGeometry()
{
    size_t nCol, nCols, nRow, nRows;

    // Global border geometry values ------------------------------------------

    /*  mnLine* is the middle point inside a frame border (i.e. mnLine1 is mid X inside left border). */
    mnLine1 = mnArrowSize + FRAMESEL_GEOM_INNER + FRAMESEL_GEOM_WIDTH / 2;
    mnLine2 = mnCtrlSize / 2;
    mnLine3 = 2 * mnLine2 - mnLine1;

    // Frame helper array -----------------------------------------------------

    maArray.Initialize( mbVer ? 2 : 1, mbHor ? 2 : 1 );
    maArray.SetUseDiagDoubleClipping( true );

    maArray.SetXOffset( mnLine1 );
    maArray.SetAllColWidths( (mbVer ? mnLine2 : mnLine3) - mnLine1 );

    maArray.SetYOffset( mnLine1 );
    maArray.SetAllRowHeights( (mbHor ? mnLine2 : mnLine3) - mnLine1 );

    Rectangle aTLRect( maArray.GetCellRect( 0, 0 ) );

    // Focus polygons ---------------------------------------------------------

    /*  Width for focus rectangles from center of frame borders. */
    mnFocusOffs = FRAMESEL_GEOM_WIDTH / 2 + 1;

    maLeft.AddFocusPolygon(   Rectangle( mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine1 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
    maVer.AddFocusPolygon(    Rectangle( mnLine2 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine2 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
    maRight.AddFocusPolygon(  Rectangle( mnLine3 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );
    maTop.AddFocusPolygon(    Rectangle( mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine1 + mnFocusOffs ) );
    maHor.AddFocusPolygon(    Rectangle( mnLine1 - mnFocusOffs, mnLine2 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine2 + mnFocusOffs ) );
    maBottom.AddFocusPolygon( Rectangle( mnLine1 - mnFocusOffs, mnLine3 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );

    for( nCol = 0, nCols = maArray.GetColCount(); nCol < nCols; ++nCol )
    {
        for( nRow = 0, nRows = maArray.GetRowCount(); nRow < nRows; ++nRow )
        {
            Rectangle aRect( maArray.GetCellRect( nCol, nRow ) );
            long nDiagFocusOffsX = frame::GetTLDiagOffset( -mnFocusOffs, mnFocusOffs, maArray.GetHorDiagAngle( nCol, nRow ) );
            long nDiagFocusOffsY = frame::GetTLDiagOffset( -mnFocusOffs, mnFocusOffs, maArray.GetVerDiagAngle( nCol, nRow ) );

            std::vector< Point > aFocusVec;
            aFocusVec.push_back( Point( aRect.Left()  - mnFocusOffs,     aRect.Top()    + nDiagFocusOffsY ) );
            aFocusVec.push_back( Point( aRect.Left()  - mnFocusOffs,     aRect.Top()    - mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Left()  + nDiagFocusOffsX, aRect.Top()    - mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Right() + mnFocusOffs,     aRect.Bottom() - nDiagFocusOffsY ) );
            aFocusVec.push_back( Point( aRect.Right() + mnFocusOffs,     aRect.Bottom() + mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Right() - nDiagFocusOffsX, aRect.Bottom() + mnFocusOffs     ) );
            maTLBR.AddFocusPolygon( Polygon( static_cast< USHORT >( aFocusVec.size() ), &aFocusVec[ 0 ] ) );

            aFocusVec.clear();
            aFocusVec.push_back( Point( aRect.Right() + mnFocusOffs,     aRect.Top()    + nDiagFocusOffsY ) );
            aFocusVec.push_back( Point( aRect.Right() + mnFocusOffs,     aRect.Top()    - mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Right() - nDiagFocusOffsX, aRect.Top()    - mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Left()  - mnFocusOffs,     aRect.Bottom() - nDiagFocusOffsY ) );
            aFocusVec.push_back( Point( aRect.Left()  - mnFocusOffs,     aRect.Bottom() + mnFocusOffs     ) );
            aFocusVec.push_back( Point( aRect.Left()  + nDiagFocusOffsX, aRect.Bottom() + mnFocusOffs     ) );
            maBLTR.AddFocusPolygon( Polygon( static_cast< USHORT >( aFocusVec.size() ), &aFocusVec[ 0 ] ) );
        }
    }

    // Click areas ------------------------------------------------------------

    for( FrameBorderIter aIt( maAllBorders ); aIt.Is(); ++aIt )
        (*aIt)->ClearClickArea();

    /*  Additional space for click area: is added to the space available to draw
        the frame borders. For instance left frame border:
        - To left, top, and bottom always big additional space (outer area).
        - To right: Dependent on existence of inner vertical frame border
            (if enabled, use less space).
     */
    long nClO = FRAMESEL_GEOM_WIDTH / 2 + FRAMESEL_GEOM_ADD_CLICK_OUTER;
    long nClI = (mbTLBR && mbBLTR) ? (FRAMESEL_GEOM_WIDTH / 2 + FRAMESEL_GEOM_ADD_CLICK_INNER) : nClO;
    long nClH = mbHor ? nClI : nClO;            // additional space dependent of horizontal inner border
    long nClV = mbVer ? nClI : nClO;            // additional space dependent of vertical inner border

    maLeft.AddClickRect(   Rectangle( mnLine1 - nClO, mnLine1 - nClO, mnLine1 + nClV, mnLine3 + nClO ) );
    maVer.AddClickRect(    Rectangle( mnLine2 - nClI, mnLine1 - nClO, mnLine2 + nClI, mnLine3 + nClO ) );
    maRight.AddClickRect(  Rectangle( mnLine3 - nClV, mnLine1 - nClO, mnLine3 + nClO, mnLine3 + nClO ) );
    maTop.AddClickRect(    Rectangle( mnLine1 - nClO, mnLine1 - nClO, mnLine3 + nClO, mnLine1 + nClH ) );
    maHor.AddClickRect(    Rectangle( mnLine1 - nClO, mnLine2 - nClI, mnLine3 + nClO, mnLine2 + nClI ) );
    maBottom.AddClickRect( Rectangle( mnLine1 - nClO, mnLine3 - nClH, mnLine3 + nClO, mnLine3 + nClO ) );

    /*  Diagonal frame borders use the remaining space between outer and inner frame borders. */
    if( mbTLBR || mbBLTR )
    {
        for( nCol = 0, nCols = maArray.GetColCount(); nCol < nCols; ++nCol )
        {
            for( nRow = 0, nRows = maArray.GetRowCount(); nRow < nRows; ++nRow )
            {
                // the usable area between horizonal/vertical frame borders of current quadrant
                Rectangle aRect( maArray.GetCellRect( nCol, nRow ) );
                aRect.Left() += nClV + 1;
                aRect.Right() -= nClV + 1;
                aRect.Top() += nClH + 1;
                aRect.Bottom() -= nClH + 1;

                /*  Both diagonal frame borders enabled. */
                if( mbTLBR && mbBLTR )
                {
                    // single areas
                    Point aMid( aRect.Center() );
                    maTLBR.AddClickRect( Rectangle( aRect.TopLeft(), aMid ) );
                    maTLBR.AddClickRect( Rectangle( aMid + Point( 1, 1 ), aRect.BottomRight() ) );
                    maBLTR.AddClickRect( Rectangle( aRect.Left(), aMid.Y() + 1, aMid.X(), aRect.Bottom() ) );
                    maBLTR.AddClickRect( Rectangle( aMid.X() + 1, aRect.Top(), aRect.Right(), aMid.Y() ) );
                    // centered rectangle for both frame borders
                    Rectangle aMidRect( aRect.TopLeft(), Size( aRect.GetWidth() / 3, aRect.GetHeight() / 3 ) );
                    aMidRect.Move( (aRect.GetWidth() - aMidRect.GetWidth()) / 2, (aRect.GetHeight() - aMidRect.GetHeight()) / 2 );
                    maTLBR.AddClickRect( aMidRect );
                    maBLTR.AddClickRect( aMidRect );
                }
                /*  One of the diagonal frame borders enabled - use entire rectangle. */
                else if( mbTLBR && !mbBLTR )    // top-left to bottom-right only
                    maTLBR.AddClickRect( aRect );
                else if( !mbTLBR && mbBLTR )    // bottom-left to top-right only
                    maBLTR.AddClickRect( aRect );
            }
        }
    }
}

void FrameSelectorImpl::InitVirtualDevice()
{
    // initialize resources
    InitColors();
    InitArrowImageList();

    // initialize geometry
    InitGlobalGeometry();
    InitBorderGeometry();

    // correct background around the used area
    mrFrameSel.SetBackground( Wallpaper( maBackCol ) );
    DoInvalidate( true );
}

// frame border access --------------------------------------------------------

const FrameBorder& FrameSelectorImpl::GetBorder( FrameBorderType eBorder ) const
{
    size_t nIndex = GetIndexFromFrameBorderType( eBorder );
    if( nIndex < maAllBorders.size() )
        return *maAllBorders[ nIndex ];
    DBG_ERRORFILE( "svx::FrameSelectorImpl::GetBorder - unknown border type" )
    return maTop;
}

FrameBorder& FrameSelectorImpl::GetBorderAccess( FrameBorderType eBorder )
{
    return const_cast< FrameBorder& >( GetBorder( eBorder ) );
}

// drawing --------------------------------------------------------------------

void FrameSelectorImpl::DrawBackground()
{
    // clear the area
    maVirDev.SetLineColor();
    maVirDev.SetFillColor( maBackCol );
    maVirDev.DrawRect( Rectangle( Point( 0, 0 ), maVirDev.GetOutputSizePixel() ) );

    // draw the inner gray (or whatever color) rectangle
    maVirDev.SetLineColor();
    maVirDev.SetFillColor( maMarkCol );
    maVirDev.DrawRect( Rectangle(
        mnLine1 - mnFocusOffs, mnLine1 - mnFocusOffs, mnLine3 + mnFocusOffs, mnLine3 + mnFocusOffs ) );

    // draw the white space for enabled frame borders
    PolyPolygon aPPoly;
    for( FrameBorderCIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
        (*aIt)->MergeFocusToPolyPolygon( aPPoly );
    aPPoly.Optimize( POLY_OPTIMIZE_CLOSE );
    maVirDev.SetLineColor( maBackCol );
    maVirDev.SetFillColor( maBackCol );
    maVirDev.DrawPolyPolygon( aPPoly );
}

void FrameSelectorImpl::DrawArrows( const FrameBorder& rBorder )
{
    DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::DrawArrows - access to disabled border" );

    long nLinePos = 0;
    switch( rBorder.GetType() )
    {
        case FRAMEBORDER_LEFT:
        case FRAMEBORDER_TOP:       nLinePos = mnLine1; break;
        case FRAMEBORDER_VER:
        case FRAMEBORDER_HOR:       nLinePos = mnLine2; break;
        case FRAMEBORDER_RIGHT:
        case FRAMEBORDER_BOTTOM:    nLinePos = mnLine3; break;
    }
    nLinePos -= mnArrowSize / 2;

    long nTLPos = 0;
    long nBRPos = mnCtrlSize - mnArrowSize;
    Point aPos1, aPos2;
    USHORT nImgId1 = 0, nImgId2 = 0;
    switch( rBorder.GetType() )
    {
        case FRAMEBORDER_LEFT:
        case FRAMEBORDER_RIGHT:
        case FRAMEBORDER_VER:
            aPos1 = Point( nLinePos, nTLPos ); nImgId1 = 1;
            aPos2 = Point( nLinePos, nBRPos ); nImgId2 = 2;
        break;

        case FRAMEBORDER_TOP:
        case FRAMEBORDER_BOTTOM:
        case FRAMEBORDER_HOR:
            aPos1 = Point( nTLPos, nLinePos ); nImgId1 = 3;
            aPos2 = Point( nBRPos, nLinePos ); nImgId2 = 4;
        break;

        case FRAMEBORDER_TLBR:
            aPos1 = Point( nTLPos, nTLPos ); nImgId1 = 5;
            aPos2 = Point( nBRPos, nBRPos ); nImgId2 = 6;
        break;
        case FRAMEBORDER_BLTR:
            aPos1 = Point( nTLPos, nBRPos ); nImgId1 = 7;
            aPos2 = Point( nBRPos, nTLPos ); nImgId2 = 8;
        break;
    }

    // Arrow or marker? Do not draw arrows into disabled control.
    USHORT nSelectAdd = (mrFrameSel.IsEnabled() && rBorder.IsSelected()) ? 0 : 8;
    maVirDev.DrawImage( aPos1, maILArrows.GetImage( nImgId1 + nSelectAdd ) );
    maVirDev.DrawImage( aPos2, maILArrows.GetImage( nImgId2 + nSelectAdd ) );
}

void FrameSelectorImpl::DrawAllArrows()
{
    for( FrameBorderCIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
        DrawArrows( **aIt );
}

Color FrameSelectorImpl::GetDrawLineColor( const Color& rColor ) const
{
    Color aColor( mbHCMode ? maHCLineCol : rColor );
    if( aColor == maBackCol )
        aColor.Invert();
    return aColor;
}

void FrameSelectorImpl::DrawAllFrameBorders()
{
    // Translate core colors to current UI colors (regards current background and HC mode).
    for( FrameBorderIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
    {
        Color aCoreColor = ((*aIt)->GetState() == FRAMESTATE_DONTCARE) ? maMarkCol : (*aIt)->GetCoreStyle().GetColor();
        (*aIt)->SetUIColor( GetDrawLineColor( aCoreColor ) );
    }

    // Copy all frame border styles to the helper array
    maArray.SetColumnStyleLeft( 0, maLeft.GetUIStyle() );
    if( mbVer ) maArray.SetColumnStyleLeft( 1, maVer.GetUIStyle() );
    maArray.SetColumnStyleRight( mbVer ? 1 : 0, maRight.GetUIStyle() );

    maArray.SetRowStyleTop( 0, maTop.GetUIStyle() );
    if( mbHor ) maArray.SetRowStyleTop( 1, maHor.GetUIStyle() );
    maArray.SetRowStyleBottom( mbHor ? 1 : 0, maBottom.GetUIStyle() );

    for( size_t nCol = 0; nCol < maArray.GetColCount(); ++nCol )
        for( size_t nRow = 0; nRow < maArray.GetRowCount(); ++nRow )
            maArray.SetCellStyleDiag( nCol, nRow, maTLBR.GetUIStyle(), maBLTR.GetUIStyle() );

    // Let the helper array draw itself
    maArray.DrawArray( maVirDev );
}

void FrameSelectorImpl::DrawVirtualDevice()
{
    DrawBackground();
    DrawAllArrows();
    DrawAllFrameBorders();
    mbFullRepaint = false;
}

void FrameSelectorImpl::CopyVirDevToControl()
{
    if( mbFullRepaint )
        DrawVirtualDevice();
    mrFrameSel.DrawBitmap( maVirDevPos, maVirDev.GetBitmap( Point( 0, 0 ), maVirDev.GetOutputSizePixel() ) );
}

void FrameSelectorImpl::DrawAllTrackingRects()
{
    PolyPolygon aPPoly;
    if( mrFrameSel.IsAnyBorderSelected() )
    {
        for( SelFrameBorderCIter aIt( maEnabBorders ); aIt.Is(); ++aIt )
            (*aIt)->MergeFocusToPolyPolygon( aPPoly );
        aPPoly.Move( maVirDevPos.X(), maVirDevPos.Y() );
    }
    else
        // no frame border selected -> draw tracking rectangle around entire control
        aPPoly.Insert( Polygon( Rectangle( maVirDevPos, maVirDev.GetOutputSizePixel() ) ) );

    aPPoly.Optimize( POLY_OPTIMIZE_CLOSE );
    for( USHORT nIdx = 0, nCount = aPPoly.Count(); nIdx < nCount; ++nIdx )
        mrFrameSel.InvertTracking( aPPoly.GetObject( nIdx ), SHOWTRACK_SMALL | SHOWTRACK_WINDOW );
}

Point FrameSelectorImpl::GetDevPosFromMousePos( const Point& rMousePos ) const
{
    return rMousePos - maVirDevPos;
}

void FrameSelectorImpl::DoInvalidate( bool bFullRepaint )
{
    mbFullRepaint |= bFullRepaint;
    mrFrameSel.Invalidate( INVALIDATE_NOERASE );
}

// frame border state and style -----------------------------------------------

void FrameSelectorImpl::SetBorderState( FrameBorder& rBorder, FrameBorderState eState )
{
    DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SetBorderState - access to disabled border" );
    if( eState == FRAMESTATE_SHOW )
        SetBorderCoreStyle( rBorder, &maCurrStyle );
    else
        rBorder.SetState( eState );
    DoInvalidate( true );
}

void FrameSelectorImpl::SetBorderCoreStyle( FrameBorder& rBorder, const SvxBorderLine* pStyle )
{
    DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SetBorderCoreStyle - access to disabled border" );
    rBorder.SetCoreStyle( pStyle );
    DoInvalidate( true );
}

void FrameSelectorImpl::ToggleBorderState( FrameBorder& rBorder )
{
    bool bDontCare = mrFrameSel.SupportsDontCareState();
    switch( rBorder.GetState() )
    {
        // same order as tristate check box: visible -> don't care -> hidden
        case FRAMESTATE_SHOW:
            SetBorderState( rBorder, bDontCare ? FRAMESTATE_DONTCARE : FRAMESTATE_HIDE );
        break;
        case FRAMESTATE_HIDE:
            SetBorderState( rBorder, FRAMESTATE_SHOW );
        break;
        case FRAMESTATE_DONTCARE:
            SetBorderState( rBorder, FRAMESTATE_HIDE );
        break;
    }
}

// frame border selection -----------------------------------------------------

void FrameSelectorImpl::SelectBorder( FrameBorder& rBorder, bool bSelect )
{
    DBG_ASSERT( rBorder.IsEnabled(), "svx::FrameSelectorImpl::SelectBorder - access to disabled border" );
    rBorder.Select( bSelect );
    DrawArrows( rBorder );
    DoInvalidate( false );
    maSelectHdl.Call( this );
}

void FrameSelectorImpl::SilentGrabFocus()
{
    bool bOldAuto = mbAutoSelect;
    mbAutoSelect = false;
    mrFrameSel.GrabFocus();
    mbAutoSelect = bOldAuto;
}

bool FrameSelectorImpl::SelectedBordersEqual() const
{
    bool bEqual = true;
    SelFrameBorderCIter aIt( maEnabBorders );
    if( aIt.Is() )
    {
        const SvxBorderLine& rFirstStyle = (*aIt)->GetCoreStyle();
        for( ++aIt; bEqual && aIt.Is(); ++aIt )
            bEqual = ((*aIt)->GetCoreStyle() == rFirstStyle);
    }
    return bEqual;
}

// ============================================================================
// FrameSelector
// ============================================================================

FrameSelector::FrameSelector( Window* pParent, const ResId& rResId ) :
    Control( pParent, rResId )
{
    // not in c'tor init list (avoid warning about usage of *this)
    mxImpl.reset( new FrameSelectorImpl( *this ) );
    EnableRTL( false ); // #107808# don't mirror the mouse handling
}

FrameSelector::~FrameSelector()
{
}

void FrameSelector::Initialize( FrameSelFlags nFlags )
{
    mxImpl->Initialize( nFlags );
    Show();
}

// enabled frame borders ------------------------------------------------------

bool FrameSelector::IsBorderEnabled( FrameBorderType eBorder ) const
{
    return mxImpl->GetBorder( eBorder ).IsEnabled();
}

sal_Int32 FrameSelector::GetEnabledBorderCount() const
{
    return static_cast< sal_Int32 >( mxImpl->maEnabBorders.size() );
}

FrameBorderType FrameSelector::GetEnabledBorderType( sal_Int32 nIndex ) const
{
    FrameBorderType eBorder = FRAMEBORDER_NONE;
    if( nIndex >= 0 )
    {
        size_t nVecIdx = static_cast< size_t >( nIndex );
        if( nVecIdx < mxImpl->maEnabBorders.size() )
            eBorder = mxImpl->maEnabBorders[ nVecIdx ]->GetType();
    }
    return eBorder;
}

sal_Int32 FrameSelector::GetEnabledBorderIndex( FrameBorderType eBorder ) const
{
    sal_Int32 nIndex = 0;
    for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt, ++nIndex )
        if( (*aIt)->GetType() == eBorder )
            return nIndex;
    return -1;
}

// frame border state and style -----------------------------------------------

bool FrameSelector::SupportsDontCareState() const
{
    return (mxImpl->mnFlags & FRAMESEL_DONTCARE) != 0;
}

FrameBorderState FrameSelector::GetBorderState( FrameBorderType eBorder ) const
{
    return mxImpl->GetBorder( eBorder ).GetState();
}

const SvxBorderLine* FrameSelector::GetBorderStyle( FrameBorderType eBorder ) const
{
    const SvxBorderLine& rStyle = mxImpl->GetBorder( eBorder ).GetCoreStyle();
    // rest of the world uses null pointer for invisible frame border
    return rStyle.GetOutWidth() ? &rStyle : 0;
}

void FrameSelector::ShowBorder( FrameBorderType eBorder, const SvxBorderLine* pStyle )
{
    mxImpl->SetBorderCoreStyle( mxImpl->GetBorderAccess( eBorder ), pStyle );
}

void FrameSelector::SetBorderDontCare( FrameBorderType eBorder )
{
    mxImpl->SetBorderState( mxImpl->GetBorderAccess( eBorder ), FRAMESTATE_DONTCARE );
}

bool FrameSelector::IsAnyBorderVisible() const
{
    bool bIsSet = false;
    for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); !bIsSet && aIt.Is(); ++aIt )
        bIsSet = ((*aIt)->GetState() == FRAMESTATE_SHOW);
    return bIsSet;
}

void FrameSelector::HideAllBorders()
{
    for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        mxImpl->SetBorderState( **aIt, FRAMESTATE_HIDE );
}

bool FrameSelector::GetVisibleWidth( USHORT& rnPrim, USHORT& rnDist, USHORT& rnSecn ) const
{
    VisFrameBorderCIter aIt( mxImpl->maEnabBorders );
    if( !aIt.Is() )
        return false;

    const SvxBorderLine& rStyle = (*aIt)->GetCoreStyle();
    bool bFound = true;
    for( ++aIt; bFound && aIt.Is(); ++aIt )
        bFound =
            (rStyle.GetOutWidth() == (*aIt)->GetCoreStyle().GetOutWidth()) &&
            (rStyle.GetDistance() == (*aIt)->GetCoreStyle().GetDistance()) &&
            (rStyle.GetInWidth()  == (*aIt)->GetCoreStyle().GetInWidth());

    if( bFound )
    {
        rnPrim = rStyle.GetOutWidth();
        rnDist = rStyle.GetDistance();
        rnSecn = rStyle.GetInWidth();
    }
    return bFound;
}

bool FrameSelector::GetVisibleColor( Color& rColor ) const
{
    VisFrameBorderCIter aIt( mxImpl->maEnabBorders );
    if( !aIt.Is() )
        return false;

    const SvxBorderLine& rStyle = (*aIt)->GetCoreStyle();
    bool bFound = true;
    for( ++aIt; bFound && aIt.Is(); ++aIt )
        bFound = (rStyle.GetColor() == (*aIt)->GetCoreStyle().GetColor());

    if( bFound )
        rColor = rStyle.GetColor();
    return bFound;
}

// frame border selection -----------------------------------------------------

const Link& FrameSelector::GetSelectHdl() const
{
    return mxImpl->maSelectHdl;
}

void FrameSelector::SetSelectHdl( const Link& rHdl )
{
    mxImpl->maSelectHdl = rHdl;
}

bool FrameSelector::IsBorderSelected( FrameBorderType eBorder ) const
{
    return mxImpl->GetBorder( eBorder ).IsSelected();
}

void FrameSelector::SelectBorder( FrameBorderType eBorder, bool bSelect )
{
    mxImpl->SelectBorder( mxImpl->GetBorderAccess( eBorder ), bSelect );
}

bool FrameSelector::IsAnyBorderSelected() const
{
    // Construct an iterator for selected borders. If it is valid, there is a selected border.
    return SelFrameBorderCIter( mxImpl->maEnabBorders ).Is();
}

void FrameSelector::SelectAllBorders( bool bSelect )
{
    for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        mxImpl->SelectBorder( **aIt, bSelect );
}

void FrameSelector::SelectAllVisibleBorders( bool bSelect )
{
    for( VisFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        mxImpl->SelectBorder( **aIt, bSelect );
}

void FrameSelector::SetStyleToSelection( USHORT nPrim, USHORT nDist, USHORT nSecn )
{
    mxImpl->maCurrStyle.SetOutWidth( nPrim );
    mxImpl->maCurrStyle.SetDistance( nDist );
    mxImpl->maCurrStyle.SetInWidth( nSecn );
    for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        mxImpl->SetBorderState( **aIt, FRAMESTATE_SHOW );
}

void FrameSelector::SetColorToSelection( const Color& rColor )
{
    mxImpl->maCurrStyle.SetColor( rColor );
    for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        mxImpl->SetBorderState( **aIt, FRAMESTATE_SHOW );
}

// accessibility --------------------------------------------------------------

Reference< XAccessible > FrameSelector::CreateAccessible()
{
    if( !mxImpl->mxAccess.is() )
        mxImpl->mxAccess = mxImpl->mpAccess =
            new a11y::AccFrameSelector( *this, FRAMEBORDER_NONE );
    return mxImpl->mxAccess;
}

Reference< XAccessible > FrameSelector::GetChildAccessible( FrameBorderType eBorder )
{
    Reference< XAccessible > xRet;
    size_t nVecIdx = static_cast< size_t >( eBorder );
    if( IsBorderEnabled( eBorder ) && (1 <= nVecIdx) && (nVecIdx <= mxImpl->maChildVec.size()) )
    {
        --nVecIdx;
        if( !mxImpl->maChildVec[ nVecIdx ] )
            mxImpl->mxChildVec[ nVecIdx ] = mxImpl->maChildVec[ nVecIdx ] =
                new a11y::AccFrameSelector( *this, eBorder );
        xRet = mxImpl->mxChildVec[ nVecIdx ];
    }
    return xRet;
}

Reference< XAccessible > FrameSelector::GetChildAccessible( sal_Int32 nIndex )
{
    return GetChildAccessible( GetEnabledBorderType( nIndex ) );
}

Reference< XAccessible > FrameSelector::GetChildAccessible( const Point& rPos )
{
    Reference< XAccessible > xRet;
    for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); !xRet.is() && aIt.Is(); ++aIt )
        if( (*aIt)->ContainsClickPoint( rPos ) )
            xRet = GetChildAccessible( (*aIt)->GetType() );
    return xRet;
}

bool FrameSelector::ContainsClickPoint( const Point& rPos ) const
{
    bool bContains = false;
    for( FrameBorderCIter aIt( mxImpl->maEnabBorders ); !bContains && aIt.Is(); ++aIt )
        bContains = (*aIt)->ContainsClickPoint( rPos );
    return bContains;
}

Rectangle FrameSelector::GetClickBoundRect( FrameBorderType eBorder ) const
{
    Rectangle aRect;
    const FrameBorder& rBorder = mxImpl->GetBorder( eBorder );
    if( rBorder.IsEnabled() )
        aRect = rBorder.GetClickBoundRect();
    return aRect;
}

// virtual functions from base class ------------------------------------------

void FrameSelector::Paint( const Rectangle& )
{
    mxImpl->CopyVirDevToControl();
    if( HasFocus() )
        mxImpl->DrawAllTrackingRects();
}

void FrameSelector::MouseButtonDown( const MouseEvent& rMEvt )
{
    /*  Mouse handling:
        * Click on an unselected frame border:
            Set current style/color, make frame border visible, deselect all
            other frame borders.
        * Click on a selected frame border:
            Toggle state of the frame border (visible -> don't care -> hidden),
            deselect all other frame borders.
        * SHIFT+Click or CTRL+Click on an unselected frame border:
            Extend selection, set current style/color to all selected frame
            borders independent of the state/style/color of the borders.
        * SHIFT+Click or CTRL+Click on a selected frame border:
            If all frame borders have same style/color, toggle state of all
            borders (see above), otherwise set current style/color to all
            borders.
        * Click on unused area: Do not modify selection and selected frame
            borders.
     */

    // #107394# do not auto-select a frame border
    mxImpl->SilentGrabFocus();

    if( rMEvt.IsLeft() )
    {
        Point aPos( mxImpl->GetDevPosFromMousePos( rMEvt.GetPosPixel() ) );
        FrameBorderPtrVec aDeselectBorders;

        bool bAnyClicked = false;   // Any frame border clicked?
        bool bNewSelected = false;  // Any unselected frame border selected?

        /*  If frame borders are set to "don't care" and the control does not
            support this state, hide them on first mouse click.
            DR 2004-01-30: Why are the borders set to "don't care" then?!? */
        bool bHideDontCare = !mxImpl->mbClicked && !SupportsDontCareState();

        for( FrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
        {
            if( (*aIt)->ContainsClickPoint( aPos ) )
            {
                // frame border is clicked
                bAnyClicked = true;
                if( !(*aIt)->IsSelected() )
                {
                    bNewSelected = true;
                    mxImpl->SelectBorder( **aIt, true );
                }
            }
            else
            {
                // hide a "don't care" frame border only if it is not clicked
                if( bHideDontCare && ((*aIt)->GetState() == FRAMESTATE_DONTCARE) )
                    mxImpl->SetBorderState( **aIt, FRAMESTATE_HIDE );

                // deselect frame borders not clicked (if SHIFT or CTRL are not pressed)
                if( !rMEvt.IsShift() && !rMEvt.IsMod1() )
                    aDeselectBorders.push_back( *aIt );
            }
        }

        if( bAnyClicked )
        {
            // any valid frame border clicked? -> deselect other frame borders
            for( FrameBorderIter aIt( aDeselectBorders ); aIt.Is(); ++aIt )
                mxImpl->SelectBorder( **aIt, false );

            if( bNewSelected || !mxImpl->SelectedBordersEqual() )
            {
                // new frame border selected, selection extended, or selected borders different? -> show
                for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
                    // SetBorderState() sets current style and color to the frame border
                    mxImpl->SetBorderState( **aIt, FRAMESTATE_SHOW );
            }
            else
            {
                // all selected frame borders are equal -> toggle state
                for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
                    mxImpl->ToggleBorderState( **aIt );
            }
        }
    }
}

void FrameSelector::KeyInput( const KeyEvent& rKEvt )
{
    bool bHandled = false;
    KeyCode aKeyCode = rKEvt.GetKeyCode();
    if( !aKeyCode.GetModifier() )
    {
        USHORT nCode = aKeyCode.GetCode();
        switch( nCode )
        {
            case KEY_SPACE:
            {
                for( SelFrameBorderIter aIt( mxImpl->maEnabBorders ); aIt.Is(); ++aIt )
                    mxImpl->ToggleBorderState( **aIt );
                bHandled = true;
            }
            break;

            case KEY_UP:
            case KEY_DOWN:
            case KEY_LEFT:
            case KEY_RIGHT:
            {
                if( !mxImpl->maEnabBorders.empty() )
                {
                    // start from first selected frame border
                    SelFrameBorderCIter aIt( mxImpl->maEnabBorders );
                    FrameBorderType eBorder = aIt.Is() ? (*aIt)->GetType() : mxImpl->maEnabBorders.front()->GetType();

                    // search for next enabled frame border
                    do
                    {
                        eBorder = mxImpl->GetBorder( eBorder ).GetKeyboardNeighbor( nCode );
                    }
                    while( (eBorder != FRAMEBORDER_NONE) && !IsBorderEnabled( eBorder ) );

                    // select the frame border
                    if( eBorder != FRAMEBORDER_NONE )
                    {
                        DeselectAllBorders();
                        SelectBorder( eBorder );
                    }
                }
            }
            break;
        }
    }
    if( !bHandled )
        Window::KeyInput(rKEvt);
}

void FrameSelector::GetFocus()
{
    // auto-selection of a frame border, if focus reaches control, and nothing is selected
    if( mxImpl->mbAutoSelect && !IsAnyBorderSelected() && !mxImpl->maEnabBorders.empty() )
        mxImpl->SelectBorder( *mxImpl->maEnabBorders.front(), true );

    mxImpl->DoInvalidate( false );
    if( mxImpl->mxAccess.is() )
        mxImpl->mpAccess->NotifyFocusListeners( sal_True );
    Control::GetFocus();
}

void FrameSelector::LoseFocus()
{
    mxImpl->DoInvalidate( false );
    if( mxImpl->mxAccess.is() )
        mxImpl->mpAccess->NotifyFocusListeners( sal_False );
    Control::LoseFocus();
}

void FrameSelector::DataChanged( const DataChangedEvent& rDCEvt )
{
    Control::DataChanged( rDCEvt );
    if( (rDCEvt.GetType() == DATACHANGED_SETTINGS) && (rDCEvt.GetFlags() & SETTINGS_STYLE) )
        mxImpl->InitVirtualDevice();
}

// ============================================================================

template< typename Cont, typename Iter, typename Pred >
FrameBorderIterBase< Cont, Iter, Pred >::FrameBorderIterBase( container_type& rCont ) :
    maIt( rCont.begin() ),
    maEnd( rCont.end() )
{
    while( Is() && !maPred( *maIt ) ) ++maIt;
}

template< typename Cont, typename Iter, typename Pred >
FrameBorderIterBase< Cont, Iter, Pred >& FrameBorderIterBase< Cont, Iter, Pred >::operator++()
{
    do { ++maIt; } while( Is() && !maPred( *maIt ) );
    return *this;
}

// ============================================================================

} // namespace svx

