/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org 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 version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
************************************************************************/

#include "precompiled_svtools.hxx"

#include "svtools/toolpanel/paneltabbar.hxx"
#include "svtools/toolpanel/toolpaneldeck.hxx"
#include "svtools/svtdata.hxx"
#include "svtools/svtools.hrc"

#include "tabitemdescriptor.hxx"
#include "paneltabbarpeer.hxx"
#include "tabbargeometry.hxx"

#include <vcl/button.hxx>
#include <vcl/help.hxx>
#include <vcl/virdev.hxx>
#include <tools/diagnose_ex.h>

#include <boost/optional.hpp>
#include <vector>

// space around an item
#define ITEM_OUTER_SPACE        2 * 3
// spacing before and after an item's text
#define ITEM_TEXT_FLOW_SPACE    5
// space between item icon and icon text
#define ITEM_ICON_TEXT_DISTANCE 4

//........................................................................
namespace svt
{
//........................................................................

    using ::com::sun::star::uno::Reference;
    using ::com::sun::star::awt::XWindowPeer;

    typedef sal_uInt16  ItemFlags;

    #define ITEM_STATE_NORMAL   0x00
    #define ITEM_STATE_ACTIVE   0x01
    #define ITEM_STATE_HOVERED  0x02
    #define ITEM_STATE_FOCUSED  0x04
    #define ITEM_POSITION_FIRST 0x08
    #define ITEM_POSITION_LAST  0x10

    //==================================================================================================================
    //= helper
    //==================================================================================================================
    namespace
    {
        ControlState lcl_ItemToControlState( const ItemFlags i_nItemFlags )
        {
            ControlState nState = CTRL_STATE_ENABLED;
            if ( i_nItemFlags & ITEM_STATE_FOCUSED )    nState |= CTRL_STATE_FOCUSED | CTRL_STATE_PRESSED;
            if ( i_nItemFlags & ITEM_STATE_HOVERED )    nState |= CTRL_STATE_ROLLOVER;
            if ( i_nItemFlags & ITEM_STATE_ACTIVE )     nState |= CTRL_STATE_SELECTED;
            return nState;
        }
    }

    //==================================================================================================================
    //= ITabBarRenderer
    //==================================================================================================================
    class SAL_NO_VTABLE ITabBarRenderer
    {
    public:
        /** fills the background of our target device
        */
        virtual void        renderBackground() const = 0;
        virtual Rectangle   calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const = 0;
        virtual void        preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const = 0;
        virtual void        postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const = 0;

        // TODO: postRenderItem takes the "real" window, i.e. effectively the tab bar. This is because
        // DrawSelectionBackground needs to be applied after everything else is painted, and is available at the Window
        // class, but not at the OutputDevice. This makes the API somewhat weird, as we're now mixing operations on the
        // target device, done in a normalized geometry, with operations on the window, done in a transformed geometry.
        // So, we should get rid of postRenderItem completely.
    };
    typedef ::boost::shared_ptr< ITabBarRenderer >  PTabBarRenderer;

    //==================================================================================================================
    //= VCLItemRenderer - declaration
    //==================================================================================================================
    class VCLItemRenderer : public ITabBarRenderer
    {
    public:
        VCLItemRenderer( OutputDevice& i_rTargetDevice )
            :m_rTargetDevice( i_rTargetDevice )
        {
        }

        // ITabBarRenderer
        virtual void        renderBackground() const;
        virtual Rectangle   calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const;
        virtual void        preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const;
        virtual void        postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const;

    protected:
        OutputDevice&   getTargetDevice() const { return m_rTargetDevice; }

    private:
        OutputDevice&   m_rTargetDevice;
    };

    //==================================================================================================================
    //= VCLItemRenderer - implementation
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    void VCLItemRenderer::renderBackground() const
    {
        getTargetDevice().DrawRect( Rectangle( Point(), getTargetDevice().GetOutputSizePixel() ) );
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle VCLItemRenderer::calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const
    {
        (void)i_nItemFlags;
        // no decorations at all
        return i_rContentArea;
    }

    //------------------------------------------------------------------------------------------------------------------
    void VCLItemRenderer::preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const
    {
        (void)i_rContentRect;
        (void)i_nItemFlags;
    }

    //------------------------------------------------------------------------------------------------------------------
    void VCLItemRenderer::postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const
    {
        const bool bActive = ( ( i_nItemFlags & ITEM_STATE_ACTIVE ) != 0 );
        const bool bHovered = ( ( i_nItemFlags & ITEM_STATE_HOVERED ) != 0 );
        const bool bFocused = ( ( i_nItemFlags & ITEM_STATE_FOCUSED ) != 0 );
        if ( bActive || bHovered || bFocused )
        {
            Rectangle aSelectionRect( i_rItemRect );
            aSelectionRect.Left() += ITEM_OUTER_SPACE / 2;
            aSelectionRect.Top() += ITEM_OUTER_SPACE / 2;
            aSelectionRect.Right() -= ITEM_OUTER_SPACE / 2;
            aSelectionRect.Bottom() -= ITEM_OUTER_SPACE / 2;
            i_rActualWindow.DrawSelectionBackground(
                aSelectionRect,
                ( bHovered || bFocused ) ? ( bActive ? 1 : 2 ) : 0 /* hilight */,
                bActive /* check */,
                sal_True /* border */,
                sal_False /* ext border only */,
                0 /* corner radius */,
                NULL,
                NULL
            );
        }
    }

    //==================================================================================================================
    //= NWFToolboxItemRenderer - declaration
    //==================================================================================================================
    class NWFToolboxItemRenderer : public ITabBarRenderer
    {
    public:
        NWFToolboxItemRenderer( OutputDevice& i_rTargetDevice )
            :m_rTargetDevice( i_rTargetDevice )
        {
        }

        // ITabBarRenderer
        virtual void        renderBackground() const;
        virtual Rectangle   calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const;
        virtual void        preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const;
        virtual void        postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const;

    protected:
        OutputDevice&   getTargetDevice() const { return m_rTargetDevice; }

    private:
        OutputDevice&   m_rTargetDevice;
    };

    //==================================================================================================================
    //= NWFToolboxItemRenderer - implementation
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    void NWFToolboxItemRenderer::renderBackground() const
    {
        getTargetDevice().DrawRect( Rectangle( Point(), getTargetDevice().GetOutputSizePixel() ) );
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle NWFToolboxItemRenderer::calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const
    {
        // don't ask GetNativeControlRegion, this will not deliver proper results in all cases.
        // Instead, simply assume that both the content and the bounding region are the same.
//        const ControlState nState( lcl_ItemToControlState( i_nItemFlags );
//        const ImplControlValue aControlValue;
//        bool bNativeOK = m_rTargetWindow.GetNativeControlRegion(
//            CTRL_TOOLBAR, PART_BUTTON,
//            i_rContentArea, nState,
//            aControlValue, ::rtl::OUString(),
//            aBoundingRegion, aContentRegion
//        );
        (void)i_nItemFlags;
        return Rectangle(
            Point( i_rContentArea.Left() - 1, i_rContentArea.Top() - 1 ),
            Size( i_rContentArea.GetWidth() + 2, i_rContentArea.GetHeight() + 2 )
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void NWFToolboxItemRenderer::preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const
    {
        const ControlState nState = lcl_ItemToControlState( i_nItemFlags );

        ImplControlValue aControlValue;
        aControlValue.setTristateVal( ( i_nItemFlags & ITEM_STATE_ACTIVE ) ? BUTTONVALUE_ON : BUTTONVALUE_OFF );

        bool bNativeOK = getTargetDevice().DrawNativeControl( CTRL_TOOLBAR, PART_BUTTON, i_rContentRect, nState, aControlValue, rtl::OUString() );
        (void)bNativeOK;
        OSL_ENSURE( bNativeOK, "NWFToolboxItemRenderer::preRenderItem: inconsistent NWF implementation!" );
            // IsNativeControlSupported returned true, previously, otherwise we would not be here ...
    }

    //------------------------------------------------------------------------------------------------------------------
    void NWFToolboxItemRenderer::postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const
    {
        (void)i_rActualWindow;
        (void)i_rItemRect;
        (void)i_nItemFlags;
    }

    //==================================================================================================================
    //= NWFTabItemRenderer - declaration
    //==================================================================================================================
    class NWFTabItemRenderer : public ITabBarRenderer
    {
    public:
        NWFTabItemRenderer( OutputDevice& i_rTargetDevice )
            :m_rTargetDevice( i_rTargetDevice )
        {
        }

        // ITabBarRenderer
        virtual void        renderBackground() const;
        virtual Rectangle   calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const;
        virtual void        preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const;
        virtual void        postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const;

    protected:
        OutputDevice&   getTargetDevice() const { return m_rTargetDevice; }

    private:
        OutputDevice&   m_rTargetDevice;
    };

    //==================================================================================================================
    //= NWFTabItemRenderer - implementation
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    void NWFTabItemRenderer::renderBackground() const
    {
        Rectangle aBackground( Point(), getTargetDevice().GetOutputSizePixel() );
        getTargetDevice().DrawRect( aBackground );

        aBackground.Top() = aBackground.Bottom();
        getTargetDevice().DrawNativeControl( CTRL_TAB_PANE, PART_ENTIRE_CONTROL, aBackground,
            CTRL_STATE_ENABLED, ImplControlValue(), ::rtl::OUString() );
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle NWFTabItemRenderer::calculateDecorations( const Rectangle& i_rContentArea, const ItemFlags i_nItemFlags ) const
    {
        const ControlState nState( lcl_ItemToControlState( i_nItemFlags ) );

        TabitemValue tiValue;

        Rectangle aBoundingRegion, aContentRegion;
        bool bNativeOK = getTargetDevice().GetNativeControlRegion(
            CTRL_TAB_ITEM, PART_ENTIRE_CONTROL,
            i_rContentArea, nState,
            tiValue, ::rtl::OUString(),
            aBoundingRegion, aContentRegion
        );
        (void)bNativeOK;
        OSL_ENSURE( bNativeOK, "NWFTabItemRenderer::calculateDecorations: GetNativeControlRegion not implemented for CTRL_TAB_ITEM?!" );

        return aBoundingRegion;
    }

    //------------------------------------------------------------------------------------------------------------------
    void NWFTabItemRenderer::preRenderItem( const Rectangle& i_rContentRect, const ItemFlags i_nItemFlags ) const
    {
        const ControlState nState = lcl_ItemToControlState( i_nItemFlags );

        TabitemValue tiValue;
        if ( i_nItemFlags & ITEM_POSITION_FIRST )
            tiValue.mnAlignment |= TABITEM_FIRST_IN_GROUP;
        if ( i_nItemFlags & ITEM_POSITION_LAST )
            tiValue.mnAlignment |= TABITEM_LAST_IN_GROUP;


        bool bNativeOK = getTargetDevice().DrawNativeControl( CTRL_TAB_ITEM, PART_ENTIRE_CONTROL, i_rContentRect, nState, tiValue, rtl::OUString() );
        (void)bNativeOK;
        OSL_ENSURE( bNativeOK, "NWFTabItemRenderer::preRenderItem: inconsistent NWF implementation!" );
            // IsNativeControlSupported returned true, previously, otherwise we would not be here ...
    }

    //------------------------------------------------------------------------------------------------------------------
    void NWFTabItemRenderer::postRenderItem( Window& i_rActualWindow, const Rectangle& i_rItemRect, const ItemFlags i_nItemFlags ) const
    {
        (void)i_rActualWindow;
        (void)i_rItemRect;
        (void)i_nItemFlags;
    }

    //==================================================================================================================
    //= PanelTabBar_Impl
    //==================================================================================================================
    class PanelTabBar_Impl : public IToolPanelDeckListener
    {
    public:
        PanelTabBar_Impl( PanelTabBar& i_rTabBar, IToolPanelDeck& i_rPanelDeck, const TabAlignment i_eAlignment, const TabItemContent i_eItemContent );

        ~PanelTabBar_Impl()
        {
            m_rPanelDeck.RemoveListener( *this );
        }

        // IToolPanelDeckListener
        virtual void PanelInserted( const PToolPanel& i_pPanel, const size_t i_nPosition )
        {
            (void)i_pPanel;
            (void)i_nPosition;
            m_bItemsDirty = true;
            m_rTabBar.Invalidate();

            Relayout();
        }

        virtual void PanelRemoved( const size_t i_nPosition )
        {
            m_bItemsDirty = true;
            m_rTabBar.Invalidate();

            if ( i_nPosition < m_nScrollPosition )
                --m_nScrollPosition;

            Relayout();
        }

        virtual void ActivePanelChanged( const ::boost::optional< size_t >& i_rOldActive, const ::boost::optional< size_t >& i_rNewActive );
        virtual void LayouterChanged( const PDeckLayouter& i_rNewLayouter );
        virtual void Dying();

        void    UpdateScrollButtons()
        {
            m_aScrollBack.Enable( m_nScrollPosition > 0 );
            m_aScrollForward.Enable( m_nScrollPosition < m_aItems.size() - 1 );
        }

        void                        Relayout();
        void                        EnsureItemsCache();
        ::boost::optional< size_t > FindItemForPoint( const Point& i_rPoint ) const;
        void                        DrawItem( const size_t i_nItemIndex, const Rectangle& i_rBoundaries ) const;
        void                        InvalidateItem( const size_t i_nItemIndex, const ItemFlags i_nAdditionalItemFlags = 0 ) const;
        void                        CopyFromRenderDevice( const Rectangle& i_rLogicalRect ) const;
        Rectangle                   GetActualLogicalItemRect( const Rectangle& i_rLogicalItemRect ) const;
        Rectangle                   GetItemScreenRect( const size_t i_nItemPos ) const;

        void                        FocusItem( const ::boost::optional< size_t >& i_rItemPos );

        inline bool                 IsVertical() const
        {
            return  (   ( m_eTabAlignment == TABS_LEFT )
                    ||  ( m_eTabAlignment == TABS_RIGHT )
                    );
        }

    protected:
        DECL_LINK( OnScroll, const PushButton* );

        void        impl_calcItemRects();
        Size        impl_calculateItemContentSize( const PToolPanel& i_pPanel, const TabItemContent i_eItemContent ) const;
        void        impl_renderItemContent( const PToolPanel& i_pPanel, const Rectangle& i_rContentArea, const TabItemContent i_eItemContent ) const;
        ItemFlags   impl_getItemFlags( const size_t i_nItemIndex ) const;

    public:
        PanelTabBar&                m_rTabBar;
        TabBarGeometry              m_aGeometry;
        NormalizedArea              m_aNormalizer;
        TabAlignment                m_eTabAlignment;
        IToolPanelDeck&             m_rPanelDeck;

        VirtualDevice               m_aRenderDevice;
        PTabBarRenderer             m_pRenderer;

        ::boost::optional< size_t > m_aHoveredItem;
        ::boost::optional< size_t > m_aFocusedItem;
        bool                        m_bMouseButtonDown;

        ItemDescriptors             m_aItems;
        bool                        m_bItemsDirty;

        PushButton                  m_aScrollBack;
        PushButton                  m_aScrollForward;

        size_t                      m_nScrollPosition;
    };

    //==================================================================================================================
    //= helper
    //==================================================================================================================
    namespace
    {
        //--------------------------------------------------------------------------------------------------------------
    #if OSL_DEBUG_LEVEL > 0
        static void lcl_checkConsistency( const PanelTabBar_Impl& i_rImpl )
        {
            if ( !i_rImpl.m_bItemsDirty )
            {
                if ( i_rImpl.m_rPanelDeck.GetPanelCount() != i_rImpl.m_aItems.size() )
                {
                    OSL_FAIL( "lcl_checkConsistency: inconsistent array sizes!" );
                    return;
                }
                for ( size_t i = 0; i < i_rImpl.m_rPanelDeck.GetPanelCount(); ++i )
                {
                    if ( i_rImpl.m_rPanelDeck.GetPanel( i ).get() != i_rImpl.m_aItems[i].pPanel.get() )
                    {
                        OSL_FAIL( "lcl_checkConsistency: array elements are inconsistent!" );
                        return;
                    }
                }
            }
        }

        #define DBG_CHECK( data ) \
            lcl_checkConsistency( data );
    #else
        #define DBG_CHECK( data ) \
            (void)data;
    #endif

        //--------------------------------------------------------------------------------------------------------------
        class ClipItemRegion
        {
        public:
            ClipItemRegion( const PanelTabBar_Impl& i_rImpl )
                :m_rDevice( i_rImpl.m_rTabBar )
            {
                m_rDevice.Push( PUSH_CLIPREGION );
                m_rDevice.SetClipRegion( i_rImpl.m_aNormalizer.getTransformed( i_rImpl.m_aGeometry.getItemsRect(), i_rImpl.m_eTabAlignment ) );
            }

            ~ClipItemRegion()
            {
                m_rDevice.Pop();
            }

        private:
            OutputDevice&   m_rDevice;
        };
    }

    //==================================================================================================================
    //= PanelTabBar_Impl - implementation
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    PanelTabBar_Impl::PanelTabBar_Impl( PanelTabBar& i_rTabBar, IToolPanelDeck& i_rPanelDeck, const TabAlignment i_eAlignment, const TabItemContent i_eItemContent )
        :m_rTabBar( i_rTabBar )
        ,m_aGeometry( i_eItemContent )
        ,m_aNormalizer()
        ,m_eTabAlignment( i_eAlignment )
        ,m_rPanelDeck( i_rPanelDeck )
        ,m_aRenderDevice( i_rTabBar )
        ,m_pRenderer()
        ,m_aHoveredItem()
        ,m_aFocusedItem()
        ,m_bMouseButtonDown( false )
        ,m_aItems()
        ,m_bItemsDirty( true )
        ,m_aScrollBack( &i_rTabBar, WB_BEVELBUTTON )
        ,m_aScrollForward( &i_rTabBar, WB_BEVELBUTTON )
        ,m_nScrollPosition( 0 )
    {
#ifdef WNT
        if ( m_aRenderDevice.IsNativeControlSupported( CTRL_TAB_ITEM, PART_ENTIRE_CONTROL ) )
            // this mode requires the NWF framework to be able to render those items onto a virtual
            // device. For some frameworks (some GTK themes, in particular), this is known to fail.
            // So, be on the safe side for the moment.
            m_pRenderer.reset( new NWFTabItemRenderer( m_aRenderDevice ) );
        else
#endif
        if ( m_aRenderDevice.IsNativeControlSupported( CTRL_TOOLBAR, PART_BUTTON ) )
            m_pRenderer.reset( new NWFToolboxItemRenderer( m_aRenderDevice ) );
        else
            m_pRenderer.reset( new VCLItemRenderer( m_aRenderDevice ) );

        m_aRenderDevice.SetLineColor();

        m_rPanelDeck.AddListener( *this );

        m_aScrollBack.SetSymbol( IsVertical() ? SYMBOL_ARROW_UP : SYMBOL_ARROW_LEFT );
        m_aScrollBack.Show();
        m_aScrollBack.SetClickHdl( LINK( this, PanelTabBar_Impl, OnScroll ) );
        m_aScrollBack.SetAccessibleDescription( String( SvtResId( STR_SVT_TOOL_PANEL_BUTTON_FWD ) ) );
        m_aScrollBack.SetAccessibleName( m_aScrollBack.GetAccessibleDescription() );

        m_aScrollForward.SetSymbol( IsVertical() ? SYMBOL_ARROW_DOWN : SYMBOL_ARROW_RIGHT );
        m_aScrollForward.Show();
        m_aScrollForward.SetClickHdl( LINK( this, PanelTabBar_Impl, OnScroll ) );
        m_aScrollForward.SetAccessibleDescription( String( SvtResId( STR_SVT_TOOL_PANEL_BUTTON_BACK ) ) );
        m_aScrollForward.SetAccessibleName( m_aScrollForward.GetAccessibleDescription() );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::impl_calcItemRects()
    {
        m_aItems.resize(0);

        Point aCompletePos( m_aGeometry.getFirstItemPosition() );
        Point aIconOnlyPos( aCompletePos );
        Point aTextOnlyPos( aCompletePos );

        for (   size_t i = 0;
                i < m_rPanelDeck.GetPanelCount();
                ++i
            )
        {
            PToolPanel pPanel( m_rPanelDeck.GetPanel( i ) );

            ItemDescriptor aItem;
            aItem.pPanel = pPanel;

            Rectangle aContentArea;

            const Size aCompleteSize( impl_calculateItemContentSize( pPanel, TABITEM_IMAGE_AND_TEXT ) );
            const Size aIconOnlySize( impl_calculateItemContentSize( pPanel, TABITEM_IMAGE_ONLY ) );
            const Size aTextOnlySize( impl_calculateItemContentSize( pPanel, TABITEM_TEXT_ONLY ) );

            // TODO: have one method calculating all sizes?

            // remember the three areas
            aItem.aCompleteArea = Rectangle( aCompletePos, aCompleteSize );
            aItem.aIconOnlyArea = Rectangle( aIconOnlyPos, aIconOnlySize );
            aItem.aTextOnlyArea = Rectangle( aTextOnlyPos, aTextOnlySize );

            m_aItems.push_back( aItem );

            aCompletePos = aItem.aCompleteArea.TopRight();
            aIconOnlyPos = aItem.aIconOnlyArea.TopRight();
            aTextOnlyPos = aItem.aTextOnlyArea.TopRight();
        }

        m_bItemsDirty = false;
    }

    //------------------------------------------------------------------------------------------------------------------
    Size PanelTabBar_Impl::impl_calculateItemContentSize( const PToolPanel& i_pPanel, const TabItemContent i_eItemContent ) const
    {
        // calculate the size needed for the content
        OSL_ENSURE( i_eItemContent != TABITEM_AUTO, "PanelTabBar_Impl::impl_calculateItemContentSize: illegal TabItemContent value!" );

        const Image aImage( i_pPanel->GetImage() );
        const bool bUseImage = !!aImage && ( i_eItemContent != TABITEM_TEXT_ONLY );

        const ::rtl::OUString sItemText( i_pPanel->GetDisplayName() );
        const bool bUseText = ( sItemText.getLength() != 0 ) && ( i_eItemContent != TABITEM_IMAGE_ONLY );

        Size aItemContentSize;
        if ( bUseImage )
        {
            aItemContentSize = aImage.GetSizePixel();
        }

        if ( bUseText )
        {
            if ( bUseImage )
                aItemContentSize.Width() += ITEM_ICON_TEXT_DISTANCE;

            // add space for text
            const Size aTextSize( m_rTabBar.GetCtrlTextWidth( sItemText ), m_rTabBar.GetTextHeight() );
            aItemContentSize.Width() += aTextSize.Width();
            aItemContentSize.Height() = ::std::max( aItemContentSize.Height(), aTextSize.Height() );

            aItemContentSize.Width() += 2 * ITEM_TEXT_FLOW_SPACE;
        }

        if ( !bUseImage && !bUseText )
        {
            // have a minimal size - this is pure heuristics, but if it doesn't suit your needs, then give your panels
            // a name and or image! :)
            aItemContentSize = Size( 16, 16 );
        }

        aItemContentSize.Width() += 2 * ITEM_OUTER_SPACE;
        aItemContentSize.Height() += 2 * ITEM_OUTER_SPACE;

        return aItemContentSize;
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::impl_renderItemContent( const PToolPanel& i_pPanel, const Rectangle& i_rContentArea, const TabItemContent i_eItemContent ) const
    {
        OSL_ENSURE( i_eItemContent != TABITEM_AUTO, "PanelTabBar_Impl::impl_renderItemContent: illegal TabItemContent value!" );

        Rectangle aRenderArea( i_rContentArea );
        if ( IsVertical() )
        {
            aRenderArea.Top() += ITEM_OUTER_SPACE;
        }
        else
        {
            aRenderArea.Left() += ITEM_OUTER_SPACE;
        }

        // draw the image
        const Image aItemImage( i_pPanel->GetImage() );
        const Size aImageSize( aItemImage.GetSizePixel() );
        const bool bUseImage = !!aItemImage && ( i_eItemContent != TABITEM_TEXT_ONLY );

        if ( bUseImage )
        {
            Point aImagePos;
            if ( IsVertical() )
            {
                aImagePos.X() = aRenderArea.Left() + ( aRenderArea.GetWidth() - aImageSize.Width() ) / 2;
                aImagePos.Y() = aRenderArea.Top();
            }
            else
            {
                aImagePos.X() = aRenderArea.Left();
                aImagePos.Y() = aRenderArea.Top() + ( aRenderArea.GetHeight() - aImageSize.Height() ) / 2;
            }
            m_rTabBar.DrawImage( aImagePos, aItemImage );
        }

        const ::rtl::OUString sItemText( i_pPanel->GetDisplayName() );
        const bool bUseText = ( sItemText.getLength() != 0 ) && ( i_eItemContent != TABITEM_IMAGE_ONLY );

        if ( bUseText )
        {
            if ( IsVertical() )
            {
                if ( bUseImage )
                    aRenderArea.Top() += aImageSize.Height() + ITEM_ICON_TEXT_DISTANCE;
                aRenderArea.Top() += ITEM_TEXT_FLOW_SPACE;
            }
            else
            {
                if ( bUseImage )
                    aRenderArea.Left() += aImageSize.Width() + ITEM_ICON_TEXT_DISTANCE;
                aRenderArea.Left() += ITEM_TEXT_FLOW_SPACE;
            }

            // draw the text
            const Size aTextSize( m_rTabBar.GetCtrlTextWidth( sItemText ), m_rTabBar.GetTextHeight() );
            Point aTextPos( aRenderArea.TopLeft() );
            if ( IsVertical() )
            {
                m_rTabBar.Push( PUSH_FONT );

                Font aFont( m_rTabBar.GetFont() );
                aFont.SetOrientation( 2700 );
                aFont.SetVertical( sal_True );
                m_rTabBar.SetFont( aFont );

                aTextPos.X() += aTextSize.Height();
                aTextPos.X() += ( aRenderArea.GetWidth() - aTextSize.Height() ) / 2;
            }
            else
            {
                aTextPos.Y() += ( aRenderArea.GetHeight() - aTextSize.Height() ) / 2;
            }

            m_rTabBar.DrawText( aTextPos, sItemText );

            if ( IsVertical() )
            {
                m_rTabBar.Pop();
            }
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::CopyFromRenderDevice( const Rectangle& i_rLogicalRect ) const
    {
        BitmapEx aBitmap( m_aRenderDevice.GetBitmapEx(
            i_rLogicalRect.TopLeft(),
            Size(
                i_rLogicalRect.GetSize().Width(),
                i_rLogicalRect.GetSize().Height()
            )
        ) );
        if ( IsVertical() )
        {
            aBitmap.Rotate( 2700, COL_BLACK );
            if ( m_eTabAlignment == TABS_LEFT )
                aBitmap.Mirror( BMP_MIRROR_HORZ );
        }
        else if ( m_eTabAlignment == TABS_BOTTOM )
        {
            aBitmap.Mirror( BMP_MIRROR_VERT );
        }

        const Rectangle aActualRect( m_aNormalizer.getTransformed( i_rLogicalRect, m_eTabAlignment ) );
        m_rTabBar.DrawBitmapEx( aActualRect.TopLeft(), aBitmap );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::InvalidateItem( const size_t i_nItemIndex, const ItemFlags i_nAdditionalItemFlags ) const
    {
        const ItemDescriptor& rItem( m_aItems[ i_nItemIndex ] );
        const ItemFlags nItemFlags( impl_getItemFlags( i_nItemIndex ) | i_nAdditionalItemFlags );

        const Rectangle aNormalizedContent( GetActualLogicalItemRect( rItem.GetCurrentRect() ) );
        const Rectangle aNormalizedBounds( m_pRenderer->calculateDecorations( aNormalizedContent, nItemFlags ) );

        const Rectangle aActualBounds = m_aNormalizer.getTransformed( aNormalizedBounds, m_eTabAlignment );
        m_rTabBar.Invalidate( aActualBounds );
    }

    //------------------------------------------------------------------------------------------------------------------
    ItemFlags PanelTabBar_Impl::impl_getItemFlags( const size_t i_nItemIndex ) const
    {
        ItemFlags nItemFlags( ITEM_STATE_NORMAL );
        if ( m_aHoveredItem == i_nItemIndex )
        {
            nItemFlags |= ITEM_STATE_HOVERED;
            if ( m_bMouseButtonDown )
                nItemFlags |= ITEM_STATE_ACTIVE;
        }

        if ( m_rPanelDeck.GetActivePanel() == i_nItemIndex )
            nItemFlags |= ITEM_STATE_ACTIVE;

        if ( m_aFocusedItem == i_nItemIndex )
            nItemFlags |= ITEM_STATE_FOCUSED;

        if ( 0 == i_nItemIndex )
            nItemFlags |= ITEM_POSITION_FIRST;

        if ( m_rPanelDeck.GetPanelCount() - 1 == i_nItemIndex )
            nItemFlags |= ITEM_POSITION_LAST;

        return nItemFlags;
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::DrawItem( const size_t i_nItemIndex, const Rectangle& i_rBoundaries ) const
    {
        const ItemDescriptor& rItem( m_aItems[ i_nItemIndex ] );
        const ItemFlags nItemFlags( impl_getItemFlags( i_nItemIndex ) );

        // the normalized bounding and content rect
        const Rectangle aNormalizedContent( GetActualLogicalItemRect( rItem.GetCurrentRect() ) );
        const Rectangle aNormalizedBounds( m_pRenderer->calculateDecorations( aNormalizedContent, nItemFlags ) );

        // check whether the item actually overlaps with the painting area
        if ( !i_rBoundaries.IsEmpty() )
        {
            const Rectangle aItemRect( GetActualLogicalItemRect( rItem.GetCurrentRect() ) );
            if ( !aItemRect.IsOver( i_rBoundaries ) )
                return;
        }

        m_rTabBar.SetUpdateMode( sal_False );

        // the aligned bounding and content rect
        const Rectangle aActualBounds = m_aNormalizer.getTransformed( aNormalizedBounds, m_eTabAlignment );
        const Rectangle aActualContent = m_aNormalizer.getTransformed( aNormalizedContent, m_eTabAlignment );

        // render item "background" layer
        m_pRenderer->preRenderItem( aNormalizedContent, nItemFlags );

        // copy from the virtual device to ourself
        CopyFromRenderDevice( aNormalizedBounds );

        // render the actual item content
        impl_renderItemContent( rItem.pPanel, aActualContent, rItem.eContent );

        // render item "foreground" layer
        m_pRenderer->postRenderItem( m_rTabBar, aActualBounds, nItemFlags );

        m_rTabBar.SetUpdateMode( sal_True );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::EnsureItemsCache()
    {
        if ( m_bItemsDirty == false )
        {
            DBG_CHECK( *this );
            return;
        }
        impl_calcItemRects();
        OSL_POSTCOND( m_bItemsDirty == false, "PanelTabBar_Impl::EnsureItemsCache: cache still dirty!" );
        DBG_CHECK( *this );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::Relayout()
    {
        EnsureItemsCache();

        const Size aOutputSize( m_rTabBar.GetOutputSizePixel() );
        m_aNormalizer = NormalizedArea( Rectangle( Point(), aOutputSize ), IsVertical() );
        const Size aLogicalOutputSize( m_aNormalizer.getReferenceSize() );

        // forward actual output size to our render device
        m_aRenderDevice.SetOutputSizePixel( aLogicalOutputSize );

        // re-calculate the size of the scroll buttons and of the items
        m_aGeometry.relayout( aLogicalOutputSize, m_aItems );

        if ( m_aGeometry.getButtonBackRect().IsEmpty() )
        {
            m_aScrollBack.Hide();
        }
        else
        {
            const Rectangle aButtonBack( m_aNormalizer.getTransformed( m_aGeometry.getButtonBackRect(), m_eTabAlignment ) );
            m_aScrollBack.SetPosSizePixel( aButtonBack.TopLeft(), aButtonBack.GetSize() );
            m_aScrollBack.Show();
        }

        if ( m_aGeometry.getButtonForwardRect().IsEmpty() )
        {
            m_aScrollForward.Hide();
        }
        else
        {
            const Rectangle aButtonForward( m_aNormalizer.getTransformed( m_aGeometry.getButtonForwardRect(), m_eTabAlignment ) );
            m_aScrollForward.SetPosSizePixel( aButtonForward.TopLeft(), aButtonForward.GetSize() );
            m_aScrollForward.Show();
        }

        UpdateScrollButtons();
    }

    //------------------------------------------------------------------------------------------------------------------
    ::boost::optional< size_t > PanelTabBar_Impl::FindItemForPoint( const Point& i_rPoint ) const
    {
        Point aPoint( IsVertical() ? i_rPoint.Y() : i_rPoint.X(), IsVertical() ? i_rPoint.X() : i_rPoint.Y() );

        if ( !m_aGeometry.getItemsRect().IsInside( aPoint ) )
            return ::boost::optional< size_t >();

        size_t i=0;
        for (   ItemDescriptors::const_iterator item = m_aItems.begin();
                item != m_aItems.end();
                ++item, ++i
            )
        {
            Rectangle aItemRect( GetActualLogicalItemRect( item->GetCurrentRect() ) );
            if ( aItemRect.IsInside( aPoint ) )
            {
                return ::boost::optional< size_t >( i );
            }
        }
        return ::boost::optional< size_t >();
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle PanelTabBar_Impl::GetItemScreenRect( const size_t i_nItemPos ) const
    {
        ENSURE_OR_RETURN( i_nItemPos < m_aItems.size(), "PanelTabBar_Impl::GetItemScreenRect: invalid item pos!", Rectangle() );
        const ItemDescriptor& rItem( m_aItems[ i_nItemPos ] );
        const Rectangle aItemRect( m_aNormalizer.getTransformed(
            GetActualLogicalItemRect( rItem.GetCurrentRect() ),
            m_eTabAlignment ) );

        const Rectangle aTabBarRect( m_rTabBar.GetWindowExtentsRelative( NULL ) );
        return Rectangle(
            Point( aTabBarRect.Left() + aItemRect.Left(), aTabBarRect.Top() + aItemRect.Top() ),
            aItemRect.GetSize()
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::FocusItem( const ::boost::optional< size_t >& i_rItemPos )
    {
        // reset old focus item
        if ( !!m_aFocusedItem )
            InvalidateItem( *m_aFocusedItem );
        m_aFocusedItem.reset();

        // mark the active icon as focused
        if ( !!i_rItemPos )
        {
            m_aFocusedItem = i_rItemPos;
            InvalidateItem( *m_aFocusedItem );
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    IMPL_LINK( PanelTabBar_Impl, OnScroll, const PushButton*, i_pButton )
    {
        if ( i_pButton == &m_aScrollBack )
        {
            OSL_ENSURE( m_nScrollPosition > 0, "PanelTabBar_Impl::OnScroll: inconsistency!" );
            --m_nScrollPosition;
            m_rTabBar.Invalidate();
        }
        else if ( i_pButton == &m_aScrollForward )
        {
            OSL_ENSURE( m_nScrollPosition < m_aItems.size() - 1, "PanelTabBar_Impl::OnScroll: inconsistency!" );
            ++m_nScrollPosition;
            m_rTabBar.Invalidate();
        }

        UpdateScrollButtons();

        return 0L;
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle PanelTabBar_Impl::GetActualLogicalItemRect( const Rectangle& i_rLogicalItemRect ) const
    {
        // care for the offset imposed by our geometry, i.e. whether or not we have scroll buttons
        Rectangle aItemRect( i_rLogicalItemRect );
        aItemRect.Move( m_aGeometry.getItemsRect().Left() - m_aGeometry.getButtonBackRect().Left(), 0 );

        // care for the current scroll position
        OSL_ENSURE( m_nScrollPosition < m_aItems.size(), "GetActualLogicalItemRect: invalid scroll position!" );
        if ( ( m_nScrollPosition > 0 ) && ( m_nScrollPosition < m_aItems.size() ) )
        {
            long nOffsetX = m_aItems[ m_nScrollPosition ].GetCurrentRect().Left() - m_aItems[ 0 ].GetCurrentRect().Left();
            long nOffsetY = m_aItems[ m_nScrollPosition ].GetCurrentRect().Top() - m_aItems[ 0 ].GetCurrentRect().Top();
            aItemRect.Move( -nOffsetX, -nOffsetY );
        }

        return aItemRect;
    }

    //==================================================================================================================
    //= PanelTabBar_Impl
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::ActivePanelChanged( const ::boost::optional< size_t >& i_rOldActive, const ::boost::optional< size_t >& i_rNewActive )
    {
        EnsureItemsCache();

        if ( !!i_rOldActive )
            InvalidateItem( *i_rOldActive, ITEM_STATE_ACTIVE );
        if ( !!i_rNewActive )
            InvalidateItem( *i_rNewActive );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::LayouterChanged( const PDeckLayouter& i_rNewLayouter )
    {
        // not interested in
        (void)i_rNewLayouter;
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar_Impl::Dying()
    {
        // not interested in - the notifier is a member of this instance here, so we're dying ourself at the moment
    }

    //==================================================================================================================
    //= PanelTabBar
    //==================================================================================================================
    //------------------------------------------------------------------------------------------------------------------
    PanelTabBar::PanelTabBar( Window& i_rParentWindow, IToolPanelDeck& i_rPanelDeck, const TabAlignment i_eAlignment, const TabItemContent i_eItemContent )
        :Control( &i_rParentWindow, 0 )
        ,m_pImpl( new PanelTabBar_Impl( *this, i_rPanelDeck, i_eAlignment, i_eItemContent ) )
    {
        DBG_CHECK( *m_pImpl );
    }

    //------------------------------------------------------------------------------------------------------------------
    PanelTabBar::~PanelTabBar()
    {
    }

    //------------------------------------------------------------------------------------------------------------------
    TabItemContent PanelTabBar::GetTabItemContent() const
    {
        return m_pImpl->m_aGeometry.getItemContent();
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::SetTabItemContent( const TabItemContent& i_eItemContent )
    {
        m_pImpl->m_aGeometry.setItemContent( i_eItemContent );
        m_pImpl->Relayout();
        Invalidate();
    }

    //------------------------------------------------------------------------------------------------------------------
    IToolPanelDeck& PanelTabBar::GetPanelDeck() const
    {
        DBG_CHECK( *m_pImpl );
        return m_pImpl->m_rPanelDeck;
    }

    //------------------------------------------------------------------------------------------------------------------
    Size PanelTabBar::GetOptimalSize( WindowSizeType i_eType ) const
    {
        m_pImpl->EnsureItemsCache();
        Size aOptimalSize( m_pImpl->m_aGeometry.getOptimalSize( m_pImpl->m_aItems, i_eType == WINDOWSIZE_MINIMUM ) );
        if ( m_pImpl->IsVertical() )
            ::std::swap( aOptimalSize.Width(), aOptimalSize.Height() );
        return aOptimalSize;
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::Resize()
    {
        Control::Resize();
        m_pImpl->Relayout();
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::Paint( const Rectangle& i_rRect )
    {
        m_pImpl->EnsureItemsCache();

        // background
        const Rectangle aNormalizedPaintArea( m_pImpl->m_aNormalizer.getNormalized( i_rRect, m_pImpl->m_eTabAlignment ) );
        m_pImpl->m_aRenderDevice.Push( PUSH_CLIPREGION );
        m_pImpl->m_aRenderDevice.SetClipRegion( aNormalizedPaintArea );
        m_pImpl->m_pRenderer->renderBackground();
        m_pImpl->m_aRenderDevice.Pop();
        m_pImpl->CopyFromRenderDevice( aNormalizedPaintArea );

        // ensure the items really paint into their own playground only
        ClipItemRegion aClipItems( *m_pImpl );

        const Rectangle aLogicalPaintRect( m_pImpl->m_aNormalizer.getNormalized( i_rRect, m_pImpl->m_eTabAlignment ) );

        const ::boost::optional< size_t > aActivePanel( m_pImpl->m_rPanelDeck.GetActivePanel() );
        const ::boost::optional< size_t > aHoveredPanel( m_pImpl->m_aHoveredItem );

        // items:
        // 1. paint all non-active, non-hovered items
        size_t i=0;
        for (   ItemDescriptors::const_iterator item = m_pImpl->m_aItems.begin();
                item != m_pImpl->m_aItems.end();
                ++item, ++i
            )
        {
            if ( i == aActivePanel )
                continue;

            if ( aHoveredPanel == i )
                continue;

            m_pImpl->DrawItem( i, aLogicalPaintRect );
        }

        // 2. paint the item which is hovered, /without/ the mouse button pressed down
        if ( !!aHoveredPanel && !m_pImpl->m_bMouseButtonDown )
            m_pImpl->DrawItem( *aHoveredPanel, aLogicalPaintRect );

        // 3. paint the active item
        if ( !!aActivePanel )
            m_pImpl->DrawItem( *aActivePanel, aLogicalPaintRect );

        // 4. paint the item which is hovered, /with/ the mouse button pressed down
        if ( !!aHoveredPanel && m_pImpl->m_bMouseButtonDown )
            m_pImpl->DrawItem( *aHoveredPanel, aLogicalPaintRect );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::MouseMove( const MouseEvent& i_rMouseEvent )
    {
        m_pImpl->EnsureItemsCache();

        ::boost::optional< size_t > aOldItem( m_pImpl->m_aHoveredItem );
        ::boost::optional< size_t > aNewItem( m_pImpl->FindItemForPoint( i_rMouseEvent.GetPosPixel() ) );

        if  ( i_rMouseEvent.IsLeaveWindow() )
            aNewItem = ::boost::optional< size_t >();

        bool const bChanged(
                ( !aOldItem && aNewItem )
                || ( aOldItem && !aNewItem )
                || ( aOldItem && aNewItem && aOldItem != aNewItem ) )
            ;
        if ( bChanged )
        {
            if ( aOldItem )
                m_pImpl->InvalidateItem( *aOldItem );

            m_pImpl->m_aHoveredItem = aNewItem;

            if ( aNewItem )
                m_pImpl->InvalidateItem( *aNewItem );
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::MouseButtonDown( const MouseEvent& i_rMouseEvent )
    {
        Control::MouseButtonDown( i_rMouseEvent );

        if ( !i_rMouseEvent.IsLeft() )
            return;

        m_pImpl->EnsureItemsCache();

        ::boost::optional< size_t > aHitItem( m_pImpl->FindItemForPoint( i_rMouseEvent.GetPosPixel() ) );
        if ( !aHitItem )
            return;

        CaptureMouse();
        m_pImpl->m_bMouseButtonDown = true;

        m_pImpl->InvalidateItem( *aHitItem );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::MouseButtonUp( const MouseEvent& i_rMouseEvent )
    {
        Control::MouseButtonUp( i_rMouseEvent );

        if ( m_pImpl->m_bMouseButtonDown )
        {
            ::boost::optional< size_t > aHitItem( m_pImpl->FindItemForPoint( i_rMouseEvent.GetPosPixel() ) );
            if ( !!aHitItem )
            {
                // re-draw that item now that we're not in mouse-down mode anymore
                m_pImpl->InvalidateItem( *aHitItem );
                // activate the respective panel
                m_pImpl->m_rPanelDeck.ActivatePanel( *aHitItem );
            }

            OSL_ENSURE( IsMouseCaptured(), "PanelTabBar::MouseButtonUp: inconsistency!" );
            if ( IsMouseCaptured() )
                ReleaseMouse();
            m_pImpl->m_bMouseButtonDown = false;
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::RequestHelp( const HelpEvent& i_rHelpEvent )
    {
        m_pImpl->EnsureItemsCache();

        ::boost::optional< size_t > aHelpItem( m_pImpl->FindItemForPoint( ScreenToOutputPixel( i_rHelpEvent.GetMousePosPixel() ) ) );
        if ( !aHelpItem )
            return;

        const ItemDescriptor& rItem( m_pImpl->m_aItems[ *aHelpItem ] );
        if ( rItem.eContent != TABITEM_IMAGE_ONLY )
            // if the text is displayed for the item, we do not need to show it as tooltip
            return;

        const ::rtl::OUString sItemText( rItem.pPanel->GetDisplayName() );
        if ( i_rHelpEvent.GetMode() == HELPMODE_BALLOON )
            Help::ShowBalloon( this, OutputToScreenPixel( rItem.GetCurrentRect().Center() ), rItem.GetCurrentRect(), sItemText );
        else
            Help::ShowQuickHelp( this, rItem.GetCurrentRect(), sItemText );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::GetFocus()
    {
        Control::GetFocus();
        if ( !m_pImpl->m_aFocusedItem )
            m_pImpl->FocusItem( m_pImpl->m_rPanelDeck.GetActivePanel() );
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::LoseFocus()
    {
        Control::LoseFocus();

        if ( !!m_pImpl->m_aFocusedItem )
        {
            m_pImpl->InvalidateItem( *m_pImpl->m_aFocusedItem );
        }

        m_pImpl->m_aFocusedItem.reset();
    }

    //------------------------------------------------------------------------------------------------------------------
    class KeyInputHandler
    {
    public:
        KeyInputHandler( Control& i_rControl, const KeyEvent& i_rKeyEvent )
            :m_rControl( i_rControl )
            ,m_rKeyEvent( i_rKeyEvent )
            ,m_bHandled( false )
        {
        }

        ~KeyInputHandler()
        {
            if ( !m_bHandled )
                m_rControl.Control::KeyInput( m_rKeyEvent );
        }

        void   setHandled()
        {
            m_bHandled = true;
        }

    private:
        Control&        m_rControl;
        const KeyEvent& m_rKeyEvent;
        bool            m_bHandled;
    };

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::KeyInput( const KeyEvent& i_rKeyEvent )
    {
        KeyInputHandler aKeyInputHandler( *this, i_rKeyEvent );

        const KeyCode& rKeyCode( i_rKeyEvent.GetKeyCode() );
        if ( rKeyCode.GetModifier() != 0 )
            // only interested in mere key presses
            return;

        // if there are less than 2 panels, we cannot travel them ...
        const size_t nPanelCount( m_pImpl->m_rPanelDeck.GetPanelCount() );
        if ( nPanelCount < 2 )
            return;

        OSL_PRECOND( !!m_pImpl->m_aFocusedItem, "PanelTabBar::KeyInput: we should have a focused item here!" );
            // if we get KeyInput events, we should have the focus. In this case, m_aFocusedItem should not be empty,
            // except if there are no panels, but then we bail out of this method here earlier ...

        bool bFocusNext = false;
        bool bFocusPrev = false;

        switch ( rKeyCode.GetCode() )
        {
        case KEY_UP:    bFocusPrev = true; break;
        case KEY_DOWN:  bFocusNext = true; break;
        case KEY_LEFT:
            if ( IsRTLEnabled() )
                bFocusNext = true;
            else
                bFocusPrev = true;
            break;
        case KEY_RIGHT:
            if ( IsRTLEnabled() )
                bFocusPrev = true;
            else
                bFocusNext = true;
            break;
        case KEY_RETURN:
            m_pImpl->m_rPanelDeck.ActivatePanel( *m_pImpl->m_aFocusedItem );
            break;
        }

        if ( !bFocusNext && !bFocusPrev )
            return;

        m_pImpl->InvalidateItem( *m_pImpl->m_aFocusedItem );
        if ( bFocusNext )
        {
            m_pImpl->m_aFocusedItem.reset( ( *m_pImpl->m_aFocusedItem + 1 ) % nPanelCount );
        }
        else
        {
            m_pImpl->m_aFocusedItem.reset( ( *m_pImpl->m_aFocusedItem + nPanelCount - 1 ) % nPanelCount );
        }
        m_pImpl->InvalidateItem( *m_pImpl->m_aFocusedItem );

        // don't delegate to base class
        aKeyInputHandler.setHandled();
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::DataChanged( const DataChangedEvent& i_rDataChanedEvent )
    {
        Control::DataChanged( i_rDataChanedEvent );

        if  (   ( i_rDataChanedEvent.GetType() == DATACHANGED_SETTINGS )
            &&  ( ( i_rDataChanedEvent.GetFlags() & SETTINGS_STYLE ) != 0 )
            )
        {
            Invalidate();
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    bool PanelTabBar::IsVertical() const
    {
        return m_pImpl->IsVertical();
    }

    //------------------------------------------------------------------------------------------------------------------
    PushButton& PanelTabBar::GetScrollButton( const bool i_bForward )
    {
        return i_bForward ? m_pImpl->m_aScrollForward : m_pImpl->m_aScrollBack;
    }

    //------------------------------------------------------------------------------------------------------------------
    ::boost::optional< size_t > PanelTabBar::GetFocusedPanelItem() const
    {
        return m_pImpl->m_aFocusedItem;
    }

    //------------------------------------------------------------------------------------------------------------------
    void PanelTabBar::FocusPanelItem( const size_t i_nItemPos )
    {
        ENSURE_OR_RETURN_VOID( i_nItemPos < m_pImpl->m_rPanelDeck.GetPanelCount(), "PanelTabBar::FocusPanelItem: illegal item pos!" );

        if ( !HasChildPathFocus() )
            GrabFocus();

        m_pImpl->FocusItem( i_nItemPos );
        OSL_POSTCOND( !!m_pImpl->m_aFocusedItem, "PanelTabBar::FocusPanelItem: have the focus, but no focused item?" );
        if ( !!m_pImpl->m_aFocusedItem )
            m_pImpl->InvalidateItem( *m_pImpl->m_aFocusedItem );
        m_pImpl->m_aFocusedItem.reset( i_nItemPos );
    }

    //------------------------------------------------------------------------------------------------------------------
    Rectangle PanelTabBar::GetItemScreenRect( const size_t i_nItemPos ) const
    {
        return m_pImpl->GetItemScreenRect( i_nItemPos );
    }

    //------------------------------------------------------------------------------------------------------------------
    Reference< XWindowPeer > PanelTabBar::GetComponentInterface( sal_Bool i_bCreate )
    {
        Reference< XWindowPeer > xWindowPeer( Control::GetComponentInterface( sal_False ) );
        if ( !xWindowPeer.is() && i_bCreate )
        {
            xWindowPeer.set( new PanelTabBarPeer( *this ) );
            SetComponentInterface( xWindowPeer );
        }
        return xWindowPeer;
    }

//........................................................................
} // namespace svt
//........................................................................

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
