//////////////////////////////////////////////////////////////////////////////
// oxygenclient.cpp
// -------------------
//
// Copyright (c) 2009 Hugo Pereira Da Costa <hugo.pereira@free.fr>
// Copyright (c) 2006, 2007 Casper Boemann <cbr@boemann.dk>
// Copyright (c) 2006, 2007 Riccardo Iaconelli <riccardo@kde.org>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////

#include "oxygenclient.h"
#include "oxygenclient.moc"

#include "oxygenbutton.h"
#include "oxygensizegrip.h"

#include <cassert>
#include <cmath>

#include <KLocalizedString>
#include <KColorUtils>
#include <KDebug>
#include <KStyle>

#include <QApplication>
#include <QLabel>
#include <QtGui/QPainter>
#include <QtGui/QBitmap>
#include <QtGui/QX11Info>
#include <QObjectList>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

namespace Oxygen
{

    //___________________________________________
    Client::Client(KDecorationBridge *b, Factory *f):
        KCommonDecorationUnstable(b, f),
        _factory( f ),
        _sizeGrip( 0 ),
        _glowAnimation( new Animation( 200, this ) ),
        _titleAnimationData( new TitleAnimationData( this ) ),
        _glowIntensity(0),
        _initialized( false ),
        _forceActive( false ),
        _mouseButton( Qt::NoButton ),
        _itemData( this ),
        _sourceItem( -1 ),
        _shadowAtom( 0 )
    {}

    //___________________________________________
    Client::~Client()
    {

        // delete sizegrip if any
        if( hasSizeGrip() ) deleteSizeGrip();

    }

    //___________________________________________
    QString Client::visibleName() const
    { return i18n("Oxygen"); }

    //___________________________________________
    void Client::init()
    {

        // make sure valid configuration is set
        if( !_configuration ) _configuration = _factory->configuration( *this );

        KCommonDecoration::init();

        widget()->setAttribute(Qt::WA_NoSystemBackground );
        widget()->setAutoFillBackground( false );
        widget()->setAcceptDrops( true );

        // setup glow animation
        _glowAnimation->setStartValue( glowBias() );
        _glowAnimation->setEndValue( 1.0 );
        _glowAnimation->setTargetObject( this );
        _glowAnimation->setPropertyName( "glowIntensity" );
        _glowAnimation->setEasingCurve( QEasingCurve::InOutQuad );
        connect( _glowAnimation, SIGNAL(finished()), this, SLOT(clearForceActive()) );

        // title animation data
        _titleAnimationData->initialize();
        connect( _titleAnimationData, SIGNAL(pixmapsChanged()), SLOT(updateTitleRect()) );

        // lists
        connect( _itemData.animation().data(), SIGNAL(finished()), this, SLOT(clearTargetItem()) );

        // in case of preview, one wants to make the label used
        // for the central widget transparent. This allows one to have
        // the correct background (with gradient) rendered
        // Remark: this is minor (and safe) a hack.
        // This should be moved upstream (into kwin/lib/kdecoration)
        if( isPreview() )
        {

            QList<QLabel*> children( widget()->findChildren<QLabel*>() );
            foreach( QLabel* widget, children )
            { widget->setAutoFillBackground( false ); }

            // also change shadow configuration size to something that fits in the preview list
            shadowCache().setShadowSize( QPalette::Active, 15 );
            shadowCache().setShadowSize( QPalette::Inactive, 15 );

        }

        setAlphaEnabled(!isMaximized());

        _initialized = true;

        // first reset is needed to store Oxygen configuration
        reset(0);

    }

    //___________________________________________
    void Client::reset( unsigned long changed )
    {
        KCommonDecorationUnstable::reset( changed );

        // update window mask when compositing is changed
        if( !_initialized ) return;
        if( changed & SettingCompositing )
        {
            updateWindowShape();
            widget()->update();
        }

        _configuration = _factory->configuration( *this );

        // glow animations
        _glowAnimation->setDuration( _configuration->shadowAnimationsDuration() );

        // title transitions
        _titleAnimationData->setDuration( _configuration->titleAnimationsDuration() );

        // tabs
        _itemData.setAnimationsEnabled( animationsEnabled() && _configuration->tabAnimationsEnabled() );
        _itemData.animation().data()->setDuration( _configuration->tabAnimationsDuration() );

        // reset title transitions
        _titleAnimationData->reset();

        // should also update animations for buttons
        resetButtons();

        // also reset tab buttons
        for( int index = 0; index < _itemData.count(); index++ )
        {
            ClientGroupItemData& item( _itemData[index] );
            if( item._closeButton ) { item._closeButton.data()->reset(0); }
        }

        // reset tab geometry
        _itemData.setDirty( true );

        // handle size grip
        if( _configuration->drawSizeGrip() && _configuration->frameBorder() == Configuration::BorderNone )
        {

            if( !hasSizeGrip() ) createSizeGrip();

        } else if( hasSizeGrip() ) deleteSizeGrip();

        // needs to remove shadow property on window since shadows are handled by the decoration
        removeShadowHint();

    }

    //___________________________________________
    bool Client::decorationBehaviour(DecorationBehaviour behaviour) const
    {
        switch (behaviour)
        {

            case DB_MenuClose:
            return _configuration->closeWindowFromMenuButton();

            case DB_WindowMask:
            return false;

            default:
            return KCommonDecoration::decorationBehaviour(behaviour);
        }
    }

    //_________________________________________________________
    KCommonDecorationButton *Client::createButton(::ButtonType type)
    {

        switch (type) {

            case MenuButton:
            return new Button(*this, i18n("Window Actions Menu"), ButtonMenu);

            case AppMenuButton:
            return new Button(*this, i18n("Application Menu"), ButtonApplicationMenu);

            case HelpButton:
            return new Button(*this, i18n("Help"), ButtonHelp);

            case MinButton:
            return new Button(*this, i18n("Minimize"), ButtonMin);

            case MaxButton:
            return new Button(*this, i18n("Maximize"), ButtonMax);

            case CloseButton:
            return new Button(*this, i18n("Close"), ButtonClose);

            case AboveButton:
            return new Button(*this, i18n("Keep Above Others"), ButtonAbove);

            case BelowButton:
            return new Button(*this, i18n("Keep Below Others"), ButtonBelow);

            case OnAllDesktopsButton:
            return new Button(*this, i18n("On All Desktops"), ButtonSticky);

            case ShadeButton:
            return new Button(*this, i18n("Shade Button"), ButtonShade);

            default: break;

        }

        return NULL;

    }

    //_________________________________________________________
    QRegion Client::calcMask( void ) const
    {

        if( isMaximized() ) { return widget()->rect(); }
        const QRect frame( widget()->rect().adjusted(
            layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ),
            -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) ) );

        QRegion mask;
        if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() )
        {

            if( hideTitleBar() ) mask = QRegion();
            else if( compositingActive() ) mask = QRegion();
            else mask = helper().roundedMask( frame, 1, 1, 1, 0 );

        } else {

            if( compositingActive() ) mask = QRegion();
            else mask = helper().roundedMask( frame );

        }

        return mask;

    }

    //___________________________________________
    int Client::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *btn) const
    {

        const bool maximized( isMaximized() );
        const bool shaded( isShade() );
        const bool narrowSpacing( _configuration->useNarrowButtonSpacing() );
        const int frameBorder( this->frameBorder() );
        const int buttonSize( hideTitleBar() ? 0 : this->buttonSize() );

        switch (lm)
        {
            case LM_BorderLeft:
            case LM_BorderRight:
            {
                int border( frameBorder );
                if( respectWindowState && maximized )
                {

                    border = 0;

                } else if( _configuration->frameBorder() < Configuration::BorderTiny ) {

                    border = 0;

                } else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) {

                    border = qMax( frameBorder, 3 );

                }

                return border;
            }

            case LM_BorderBottom:
            {
                int border( frameBorder );
                if( (respectWindowState && maximized) || shaded )
                {

                    border = 0;

                } else if( _configuration->frameBorder() >= Configuration::BorderNoSide ) {

                    // for tiny border, the convention is to have a larger bottom area in order to
                    // make resizing easier
                    border = qMax(frameBorder, 4);

                } else if( _configuration->frameBorder() < Configuration::BorderTiny ) {

                    border = 0;

                } else if( !compositingActive() && _configuration->frameBorder() == Configuration::BorderTiny ) {

                    border = qMax( frameBorder, 3 );

                }

                return border;
            }

            case LM_TitleEdgeTop:
            {
                int border = 0;
                if( _configuration->frameBorder() == Configuration::BorderNone && hideTitleBar() )
                {

                    border = 0;

                } else if( !( respectWindowState && maximized )) {

                    border = TFRAMESIZE;

                }

                return border;

            }

            case LM_TitleEdgeBottom:
            {
                return 0;
            }

            case LM_TitleEdgeLeft:
            case LM_TitleEdgeRight:
            {
                int border = 0;
                if( !(respectWindowState && maximized) )
                { border = 4; }

                return border;

            }

            case LM_TitleBorderLeft:
            case LM_TitleBorderRight:
            {
                int border = 5;

                // if title outline is to be drawn, one adds the space needed to
                // separate title and tab border. namely the same value
                if( _configuration->drawTitleOutline() ) border += border;

                return border;
            }

            case LM_ButtonWidth:
            case LM_ButtonHeight:
            {
                return buttonSize;
            }

            case LM_TitleHeight:
            {
                if( hideTitleBar() ) return 0;
                else {
                    const int titleHeight = QFontMetrics(options()->font(true)).height();
                    return qMax(buttonSize, titleHeight);
                }
            }

            case LM_ButtonSpacing:
            return narrowSpacing ? 1:3;

            case LM_ButtonMarginTop:
            return 0;

            // outer margin for shadow/glow
            case LM_OuterPaddingLeft:
            case LM_OuterPaddingRight:
            case LM_OuterPaddingTop:
            case LM_OuterPaddingBottom:
            if( maximized ) return 0;
            else return shadowCache().shadowSize();

            default:
            return KCommonDecoration::layoutMetric(lm, respectWindowState, btn);
        }

    }

    //_________________________________________________________
    QRect Client::defaultTitleRect( bool active ) const
    {

        QRect titleRect( this->titleRect().adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ) );

        // when drawing title outline, shrink the rect so that it matches the actual caption size
        if( active && _configuration->drawTitleOutline() && isActive() )
        {


            if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth )
            {
                titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) );
                titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) );
            }

            const QRect textRect( titleBoundingRect( options()->font( true, false),  titleRect, caption() ) );
            titleRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) );
            titleRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) );

        } else {

            // buttons are properly accounted for in titleBoundingRect method
            titleRect.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) );
            titleRect.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) );

        }

        return titleRect;

    }

    //_________________________________________________________
    QRect Client::titleBoundingRect( const QFont& font, QRect rect, const QString& caption ) const
    {

        // get title bounding rect
        QRect boundingRect( QFontMetrics( font ).boundingRect( rect, titleAlignment() | Qt::AlignVCenter, caption ) );

        // adjust to make sure bounding rect
        // 1/ has same vertical alignment as original titleRect
        // 2/ does not exceeds available horizontal space
        boundingRect.setTop( rect.top() );
        boundingRect.setBottom( rect.bottom() );

        // check bounding rect against input rect
        boundRectTo( boundingRect, rect );

        if( _configuration->titleAlignment() == Configuration::AlignCenterFullWidth )
        {

            /*
            check bounding rect against max available space, for buttons
            this is not needed if centerTitleOnFullWidth flag is set to false,
            because it was already done before calling titleBoundingRect
            */
            boundRectTo( boundingRect, titleRect() );

        }

        return boundingRect;

    }

    //_________________________________________________________
    void Client::boundRectTo( QRect& rect, const QRect& bound ) const
    {

        if( bound.left() > rect.left() )
        {
            rect.moveLeft( bound.left() );
            if( bound.right() < rect.right() )
            { rect.setRight( bound.right() ); }

        } else if( bound.right() < rect.right() ) {

            rect.moveRight( bound.right() );
            if( bound.left() > rect.left() )
            { rect.setLeft( bound.left() ); }

        }

        return;
    }

    //_________________________________________________________
    void Client::clearTargetItem( void )
    {

        if( _itemData.animationType() == AnimationLeave )
        { _itemData.setDirty( true ); }

    }

    //_________________________________________________________
    void Client::updateItemBoundingRects( bool alsoUpdate )
    {

        // make sure items are not animated
        _itemData.animate( AnimationNone );

        // maximum available space
        const QRect titleRect( this->titleRect() );

        // get tabs
        const int items( tabCount() );

        // make sure item data have the correct number of items
        while( _itemData.count() < items ) _itemData.push_back( ClientGroupItemData() );
        while( _itemData.count() > items )
        {
            if( _itemData.back()._closeButton ) delete _itemData.back()._closeButton.data();
            _itemData.pop_back();
        }

        assert( !_itemData.isEmpty() );

        // create buttons
        if( _itemData.count() == 1 )
        {

            // remove button
            if( _itemData.front()._closeButton )
            { delete _itemData.front()._closeButton.data(); }

            // set active rect
            _itemData.front()._activeRect = titleRect.adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 );

        } else {

            int left( titleRect.left() );
            const int width( titleRect.width()/items );
            for( int index = 0; index < _itemData.count(); index++ )
            {

                ClientGroupItemData& item(_itemData[index]);

                // make sure button exists
                if( !item._closeButton )
                {
                    item._closeButton = ClientGroupItemData::ButtonPointer( new Button( *this, "Close this tab", ButtonItemClose ) );
                    item._closeButton.data()->show();
                    item._closeButton.data()->installEventFilter( this );
                }

                // set active rect
                QRect local(  QPoint( left, titleRect.top() ), QSize( width, titleRect.height() ) );
                local.adjust( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 );
                item._activeRect = local;
                left += width;

            }

        }

        if( _itemData.count() == 1 )
        {

            _itemData.front().reset( defaultTitleRect() );

        } else {

            for( int index = 0; index < _itemData.count(); index++ )
            { _itemData[index].reset( _itemData[index]._activeRect ); }

        }

        // button activity
        _itemData.updateButtonActivity( currentTabId() );

        // reset buttons location
        _itemData.updateButtons( alsoUpdate );
        _itemData.setDirty( false );

        return;

    }

    //_________________________________________________________
    QColor Client::titlebarTextColor(const QPalette &palette) const
    {
        if( glowIsAnimated() ) return KColorUtils::mix(
            titlebarTextColor( palette, false ),
            titlebarTextColor( palette, true ),
            glowIntensity() );
        else return titlebarTextColor( palette, isActive() );
    }

    //_________________________________________________________
    void Client::renderWindowBackground( QPainter* painter, const QRect& rect, const QWidget* widget, const QPalette& palette ) const
    {

        // window background
        if( helper().hasBackgroundGradient( windowId() ) )
        {

            int offset = layoutMetric( LM_OuterPaddingTop );

            // radial gradient positionning
            const int height = hideTitleBar() ? 0:layoutMetric(LM_TitleHeight);
            if( isMaximized() ) offset -= 3;

            const QWidget* window( isPreview() ? this->widget() : widget->window() );
            helper().renderWindowBackground(painter, rect, widget, window, palette, offset, height );

        } else {

            painter->fillRect( rect, palette.color( QPalette::Window ) );

        }

        // background pixmap
        if( isPreview() || helper().hasBackgroundPixmap( windowId() ) )
        {
            int offset = layoutMetric( LM_OuterPaddingTop );

            // radial gradient positionning
            const int height = hideTitleBar() ? 0:layoutMetric(LM_TitleHeight);
            if( isMaximized() ) offset -= 3;

            // background pixmap
            const QPoint backgroundPixmapOffset( layoutMetric( LM_OuterPaddingLeft ) + layoutMetric( LM_BorderLeft ), 0 );
            helper().setBackgroundPixmapOffset( backgroundPixmapOffset );

            const QWidget* window( isPreview() ? this->widget() : widget->window() );
            helper().renderBackgroundPixmap(painter, rect, widget, window, offset, height );

        }

    }

    //_________________________________________________________
    void Client::renderWindowBorder( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QPalette& palette ) const
    {

        // get coordinates relative to the client area
        // this is annoying. One could use mapTo if this was taking const QWidget* and not
        // const QWidget* as argument.
        const QWidget* window = (isPreview()) ? this->widget() : widget->window();
        const QWidget* w = widget;
        QPoint position( 0, 0 );
        while (  w != window && !w->isWindow() && w != w->parentWidget() )
        {
            position += w->geometry().topLeft();
            w = w->parentWidget();
        }

        // save painter
        if( clipRect.isValid() )
        {
            painter->save();
            painter->setClipRegion(clipRect,Qt::IntersectClip);
        }

        QRect r = (isPreview()) ? this->widget()->rect():window->rect();
        r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) );
        r.adjust(0,0, 1, 1);

        // base color
        QColor color( palette.window().color() );

        // add alpha channel
        if( _itemData.count() == 1 && glowIsAnimated() )
        { color = helper().alphaColor( color, glowIntensity() ); }

        // title height
        const int titleHeight( layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleEdgeBottom ) + layoutMetric( LM_TitleHeight ) );

        // make titlebar background darker for tabbed, non-outline window
        if( ( tabCount() >= 2 || _itemData.isAnimated() ) && !(_configuration->drawTitleOutline() && isActive() ) )
        {

            const QPoint topLeft( r.topLeft()-position );
            const QRect rect( topLeft, QSize( r.width(), titleHeight ) );

            QLinearGradient lg( rect.topLeft(), rect.bottomLeft() );
            lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) );
            lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) );
            painter->setBrush( lg );
            painter->setPen( Qt::NoPen );
            painter->drawRect( rect );

        }

        // horizontal line
        {
            const int shadowSize = 7;
            const int height = shadowSize-3;

            const QPoint topLeft( r.topLeft()+QPoint(0,titleHeight-height)-position );
            QRect rect( topLeft, QSize( r.width(), height ) );

            // adjustements to cope with shadow size and outline border.
            rect.adjust( -shadowSize, 0, shadowSize-1, 0 );
            if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) && !isMaximized() )
            {
                if( _configuration->frameBorder() == Configuration::BorderTiny ) rect.adjust( 1, 0, -1, 0 );
                else if( _configuration->frameBorder() > Configuration::BorderTiny ) rect.adjust( HFRAMESIZE-1, 0, -HFRAMESIZE+1, 0 );
            }

            if( rect.isValid() )
            { helper().slab( color, 0, shadowSize )->render( rect, painter, TileSet::Top ); }

        }

        if( _configuration->drawTitleOutline() && ( isActive() || glowIsAnimated() ) )
        {

            // save old hints and turn off anti-aliasing
            const QPainter::RenderHints hints( painter->renderHints() );
            painter->setRenderHint( QPainter::Antialiasing, false );

            // save mask and frame to where
            // grey window background is to be rendered
            QRegion mask;
            QRect frame;

            // bottom line
            const int leftOffset = qMin( layoutMetric( LM_BorderLeft ), int(HFRAMESIZE) );
            const int rightOffset = qMin( layoutMetric( LM_BorderRight ), int(HFRAMESIZE) );
            if( _configuration->frameBorder() > Configuration::BorderNone )
            {

                const int height = qMax( 0, layoutMetric( LM_BorderBottom ) - HFRAMESIZE );
                const int width = r.width() - leftOffset - rightOffset - 1;

                const QRect rect( r.bottomLeft()-position + QPoint( leftOffset, -layoutMetric( LM_BorderBottom ) ), QSize( width, height ) );
                if( height > 0 ) { mask += rect; frame |= rect; }

                const QColor shadow( helper().calcDarkColor( color ) );
                painter->setPen( shadow );
                painter->drawLine( rect.bottomLeft()+QPoint(-1,1), rect.bottomRight()+QPoint(1,1) );

            }

            // left and right
            const int topOffset = titleHeight;
            const int bottomOffset = qMin( layoutMetric( LM_BorderBottom ), int(HFRAMESIZE) );
            const int height = r.height() - topOffset - bottomOffset - 1;

            if( _configuration->frameBorder() >= Configuration::BorderTiny )
            {

                const QColor shadow( helper().calcLightColor( color ) );
                painter->setPen( shadow );

                // left
                int width = qMax( 0, layoutMetric( LM_BorderLeft ) - HFRAMESIZE );
                QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ) - width, topOffset ), QSize( width, height ) );
                if( width > 0 ) { mask += rect; frame |= rect; }

                painter->drawLine( rect.topLeft()-QPoint(1,0), rect.bottomLeft()-QPoint(1, 0) );

                // right
                width = qMax( 0, layoutMetric( LM_BorderRight ) - HFRAMESIZE );
                rect = QRect(r.topRight()-position + QPoint( -layoutMetric( LM_BorderRight ), topOffset ), QSize( width, height ));
                if( width > 0 ) { mask += rect; frame |= rect; }

                painter->drawLine( rect.topRight()+QPoint(1,0), rect.bottomRight()+QPoint(1, 0) );
            }

            // restore old hints
            painter->setRenderHints( hints );

            // in preview mode also adds center square
            if( isPreview() )
            {
                const QRect rect( r.topLeft()-position + QPoint( layoutMetric( LM_BorderLeft ), topOffset ), QSize(r.width()-layoutMetric( LM_BorderLeft )-layoutMetric( LM_BorderRight ),height) );
                mask += rect; frame |= rect;
            }

            // paint
            if( !mask.isEmpty() )
            {
                painter->setClipRegion( mask, Qt::IntersectClip);
                renderWindowBackground(painter, frame, widget, palette );
            }

        }

        // restore painter
        if( clipRect.isValid() )
        { painter->restore(); }

    }

    //_________________________________________________________
    void Client::renderSeparator( QPainter* painter, const QRect& clipRect, const QWidget* widget, const QColor& color ) const
    {

        const QWidget* window = (isPreview()) ? this->widget() : widget->window();

        // get coordinates relative to the client area
        // this is annoying. One could use mapTo if this was taking const QWidget* and not
        // const QWidget* as argument.
        QPoint position( 0, 0 );
        {
            const QWidget* w = widget;
            while (  w != window && !w->isWindow() && w != w->parentWidget() ) {
                position += w->geometry().topLeft();
                w = w->parentWidget();
            }
        }

        // setup painter
        if (clipRect.isValid())
        {
            painter->save();
            painter->setClipRegion(clipRect,Qt::IntersectClip);
        }

        QRect r = (isPreview()) ? this->widget()->rect():window->rect();
        r.adjust( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ), -layoutMetric( LM_OuterPaddingRight ), -layoutMetric( LM_OuterPaddingBottom ) );

        // dimensions
        const int titleHeight = layoutMetric(LM_TitleHeight);
        const int titleTop = layoutMetric(LM_TitleEdgeTop) + r.top();

        // set color
        QColor local( color );
        if( glowIsAnimated() && _configuration->separatorMode() != Configuration::SeparatorAlways )
        { local = helper().alphaColor( color, glowIntensity() ); }

        // render
        helper().drawSeparator( painter, QRect(r.top(), titleTop+titleHeight-1.5, r.width(), 2).translated( -position ), local, Qt::Horizontal);

        if (clipRect.isValid()) { painter->restore(); }

    }

    //_________________________________________________________
    void Client::renderTitleOutline(  QPainter* painter, const QRect& rect, const QPalette& palette ) const
    {

        // center (for active windows only)
        {
            painter->save();
            QRect adjustedRect( rect.adjusted( 1, 1, -1, 1 ) );

            // prepare painter mask
            QRegion mask( adjustedRect.adjusted( 1, 0, -1, 0 ) );
            mask += adjustedRect.adjusted( 0, 1, 0, 0 );
            painter->setClipRegion( mask, Qt::IntersectClip );

            // draw window background
            renderWindowBackground(painter, adjustedRect, widget(), palette );
            painter->restore();
        }

        // shadow
        const int shadowSize( 7 );
        const int offset( -3 );
        const int voffset( 5-shadowSize );
        const QRect adjustedRect( rect.adjusted(offset, voffset, -offset, shadowSize) );
        QColor color( palette.color( widget()->backgroundRole() ) );

        // add alpha channel
        if( _itemData.count() == 1 && glowIsAnimated() )
        { color = helper().alphaColor( color, glowIntensity() ); }

        // render slab
        helper().slab( color, 0, shadowSize )->render( adjustedRect, painter, TileSet::Tiles(TileSet::Top|TileSet::Left|TileSet::Right) );

    }

    //_________________________________________________________
    void Client::renderTitleText( QPainter* painter, const QRect& rect, const QColor& color, const QColor& contrast ) const
    {

        if( !_titleAnimationData->isValid() )
        {
            // contrast pixmap
            _titleAnimationData->reset(
                rect,
                renderTitleText( rect, caption(), color ),
                renderTitleText( rect, caption(), contrast ) );
        }

        if( _titleAnimationData->isDirty() )
        {

            // clear dirty flags
            _titleAnimationData->setDirty( false );

            // finish current animation if running
            if( _titleAnimationData->isAnimated() )
            { _titleAnimationData->finishAnimation(); }

            if( !_titleAnimationData->isLocked() )
            {

                // set pixmaps
                _titleAnimationData->setPixmaps(
                    rect,
                    renderTitleText( rect, caption(), color ),
                    renderTitleText( rect, caption(), contrast ) );

                _titleAnimationData->startAnimation();
                renderTitleText( painter, rect, color, contrast );

            } else if( !caption().isEmpty() ) {

                renderTitleText( painter, rect, caption(), color, contrast );

            }

            // lock animations (this must be done whether or not
            // animation was actually started, in order to extend locking
            // every time title get changed too rapidly
            _titleAnimationData->lockAnimations();

        } else if( _titleAnimationData->isAnimated() ) {

            if( isMaximized() ) painter->translate( 0, 2 );
            if( !_titleAnimationData->contrastPixmap().isNull() )
            {
                painter->translate( 0, 1 );
                painter->drawPixmap( rect.topLeft(), _titleAnimationData->contrastPixmap() );
                painter->translate( 0, -1 );
            }

            painter->drawPixmap( rect.topLeft(), _titleAnimationData->pixmap() );

            if( isMaximized() ) painter->translate( 0, -2 );

        } else if( !caption().isEmpty() ) {
            renderTitleText( painter, rect, caption(), color, contrast );

        }

    }

    //_______________________________________________________________________
    void Client::renderTitleText( QPainter* painter, const QRect& rect, const QString& caption, const QColor& color, const QColor& contrast, bool elide ) const
    {

        const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter );
        const QString local( elide ? QFontMetrics( painter->font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption );

        // translate title down in case of maximized window
        if( isMaximized() ) painter->translate( 0, 2 );

        if( contrast.isValid() )
        {
            painter->setPen( contrast );
            painter->translate( 0, 1 );
            painter->drawText( rect, alignment, local );
            painter->translate( 0, -1 );
        }

        painter->setPen( color );
        painter->drawText( rect, alignment, local );

        // translate back
        if( isMaximized() ) painter->translate( 0, -2 );

    }

    //_______________________________________________________________________
    QPixmap Client::renderTitleText( const QRect& rect, const QString& caption, const QColor& color, bool elide ) const
    {

        if( !rect.isValid() ) return QPixmap();

        QPixmap out( rect.size() );
        out.fill( Qt::transparent );
        if( caption.isEmpty() || !color.isValid() ) return out;

        QPainter painter( &out );
        painter.setFont( options()->font(isActive(), false) );
        const Qt::Alignment alignment( titleAlignment() | Qt::AlignVCenter );
        const QString local( elide ? QFontMetrics( painter.font() ).elidedText( caption, Qt::ElideRight, rect.width() ):caption );

        painter.setPen( color );
        painter.drawText( out.rect(), alignment, local );
        painter.end();
        return out;

    }

    //_______________________________________________________________________
    void Client::renderItem( QPainter* painter, int index, const QPalette& palette )
    {

        const ClientGroupItemData& item( _itemData[index] );

        // see if tag is active
        const int itemCount( _itemData.count() );

        // check item bounding rect
        if( !item._boundingRect.isValid() ) return;

        // create rect in which text is to be drawn
        QRect textRect( item._boundingRect.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) );

        // add extra space needed for title outline
        if( itemCount > 1 || _itemData.isAnimated() )
        { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); }

        // add extra space for the button
        if( itemCount > 1 && item._closeButton && item._closeButton.data()->isVisible() )
        { textRect.adjust( 0, 0, - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); }

        // check if current item is active
        const bool active( tabId(index) == currentTabId() );

        // get current item caption and update text rect
        const QString caption( itemCount == 1 ? this->caption() : this->caption(index) );

        if( _configuration->titleAlignment() != Configuration::AlignCenterFullWidth )
        { boundRectTo( textRect, titleRect() ); }

        // adjust textRect
        textRect = titleBoundingRect( painter->font(), textRect, caption );

        // title outline
        if( itemCount == 1 )
        {

            // no title outline if the window caption is empty
            if( !caption.trimmed().isEmpty() )
            {
                if( _itemData.isAnimated() ) {

                    renderTitleOutline( painter, item._boundingRect, palette );

                } else if( (isActive()||glowIsAnimated()) && _configuration->drawTitleOutline() ) {

                    // adjusts boundingRect accordingly
                    QRect boundingRect( item._boundingRect );
                    boundingRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) );
                    boundingRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) );

                    // render bounding rect around it with extra margins
                    renderTitleOutline( painter, boundingRect, palette );

                }

            }

        } else if( active ) {

            // in multiple tabs render title outline in all cases
            renderTitleOutline( painter, item._boundingRect, palette );

        }

        // render text
        if( active || itemCount == 1 )
        {

            // for active tab, current caption is "merged" with old caption, if any
            renderTitleText(
                painter, textRect,
                titlebarTextColor( palette ),
                titlebarContrastColor( palette ) );


        } else {

            QColor background( backgroundPalette( widget(), palette ).color( widget()->window()->backgroundRole() ) );

            // add extra shade (as used in renderWindowBorder
            if( !( isActive() && _configuration->drawTitleOutline() ) )
            { background = KColorUtils::mix( background, Qt::black, 0.10 ); }

            // otherwise current caption is rendered directly
            renderTitleText(
                painter, textRect, caption,
                titlebarTextColor( backgroundPalette( widget(), palette ), false ),
                titlebarContrastColor( background ) );

        }

        // render separators between inactive tabs
        if( !( active || itemCount == 1 ) && item._closeButton && item._closeButton.data()->isVisible() )
        {

            // separators
            // draw Left separator
            const QColor color( backgroundPalette( widget(), palette ).window().color() );
            const bool isFirstItem(  index == 0 || (index == 1 && !_itemData[0]._boundingRect.isValid() ) );
            if( !active && ( ( isFirstItem && buttonsLeftWidth() > 0 ) || _itemData.isTarget( index ) ) )
            {

                const QRect local( item._boundingRect.topLeft()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) );
                helper().drawSeparator( painter, local, color, Qt::Vertical);

            }

            // draw right separator
            if(
                ( index == itemCount-1 && buttonsRightWidth() > 0 ) ||
                ( index+1 < itemCount && ( _itemData.isTarget( index+1 ) ||
                !( tabId(index+1) == currentTabId() && _itemData[index+1]._boundingRect.isValid() ) ) ) )
            {

                const QRect local( item._boundingRect.topRight()+QPoint(0,2), QSize( 2, item._boundingRect.height()-3 ) );
                helper().drawSeparator( painter, local, color, Qt::Vertical);

            }

        }

    }

    //_______________________________________________________________________
    void Client::renderTargetRect( QPainter* p, const QPalette& palette )
    {
        if( _itemData.targetRect().isNull() || _itemData.isAnimationRunning() ) return;

        const QColor color = palette.color(QPalette::Highlight);
        p->setPen(KColorUtils::mix(color, palette.color(QPalette::Active, QPalette::WindowText)));
        p->setBrush( helper().alphaColor( color, 0.5 ) );
        p->drawRect( QRectF(_itemData.targetRect()).adjusted( 4.5, 2.5, -4.5, -2.5 ) );

    }

    //_______________________________________________________________________
    void Client::renderCorners( QPainter* painter, const QRect& frame, const QPalette& palette ) const
    {

        const QColor color( backgroundColor( widget(), palette ) );

        QLinearGradient lg = QLinearGradient(0, -0.5, 0, qreal( frame.height() )+0.5);
        lg.setColorAt(0.0, helper().calcLightColor( helper().backgroundTopColor(color) ));
        lg.setColorAt(0.51, helper().backgroundBottomColor(color) );
        lg.setColorAt(1.0, helper().backgroundBottomColor(color) );

        painter->setPen( QPen( lg, 1 ) );
        painter->setBrush( Qt::NoBrush );
        painter->drawRoundedRect( QRectF( frame ).adjusted( 0.5, 0.5, -0.5, -0.5 ), 3.5,  3.5 );

    }

    //_______________________________________________________________________
    void Client::renderFloatFrame( QPainter* painter, const QRect& frame, const QPalette& palette ) const
    {

        // shadow and resize handles
        if( !isMaximized() )
        {

            if( _configuration->frameBorder() >= Configuration::BorderTiny )
            {

                helper().drawFloatFrame(
                    painter, frame, backgroundColor( widget(), palette ),
                    !compositingActive(), isActive() && shadowCache().isEnabled( QPalette::Active ),
                    KDecoration::options()->color(ColorTitleBar)
                    );

            } else {

                // for small borders, use a frame that matches the titlebar only
                const QRect local( frame.topLeft(), QSize( frame.width(), layoutMetric(LM_TitleHeight) + layoutMetric(LM_TitleEdgeTop) ) );
                helper().drawFloatFrame(
                    painter, local, backgroundColor( widget(), palette ),
                    false, isActive() && shadowCache().isEnabled( QPalette::Active ),
                    KDecoration::options()->color(ColorTitleBar)
                    );
            }

        } else if( isShade() ) {

            // for shaded maximized windows adjust frame and draw the bottom part of it
            helper().drawFloatFrame(
                painter, frame, backgroundColor( widget(), palette ),
                !( compositingActive() || _configuration->frameBorder() == Configuration::BorderNone ), isActive(),
                KDecoration::options()->color(ColorTitleBar),
                TileSet::Bottom
                );

        }

    }

    //____________________________________________________________________________
    void Client::renderDots( QPainter* painter, const QRect& frame, const QColor& color ) const
    {

        if( _configuration->frameBorder() >= Configuration::BorderTiny )
        {

            // dimensions
            int x,y,w,h;
            frame.getRect(&x, &y, &w, &h);

            if( isResizable() && !isShade() && !isMaximized() )
            {

                // Draw right side 3-dots resize handles
                const int cenY = (h / 2 + y) ;
                const int posX = (w + x - 3);

                helper().renderDot( painter, QPoint(posX, cenY - 3), color);
                helper().renderDot( painter, QPoint(posX, cenY), color);
                helper().renderDot( painter, QPoint(posX, cenY + 3), color);

            }

            // Draw bottom-right cornet 3-dots resize handles
            if( isResizable() && !isShade() && !_configuration->drawSizeGrip() )
            {

                painter->save();
                painter->translate(x + w-9, y + h-9);
                helper().renderDot( painter, QPoint(2, 6), color);
                helper().renderDot( painter, QPoint(5, 5), color);
                helper().renderDot( painter, QPoint(6, 2), color);
                painter->restore();
            }

        }

    }

    //_________________________________________________________
    void Client::activeChange( void )
    {

        KCommonDecorationUnstable::activeChange();
        _itemData.setDirty( true );

        // reset animation
        if( shadowAnimationsEnabled() )
        {
            _glowAnimation->setDirection( isActive() ? Animation::Forward : Animation::Backward );
            if(!glowIsAnimated()) { _glowAnimation->start(); }
        }

        // update size grip so that it gets the right color
        // also make sure it is remaped to from z stack,
        // unless hidden
        if( hasSizeGrip() && !(isShade() || isMaximized() ))
        {
            sizeGrip().activeChange();
            sizeGrip().update();
        }

    }

    //_________________________________________________________
    void Client::maximizeChange( void  )
    {
        if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) );
        setAlphaEnabled(!isMaximized());
        KCommonDecorationUnstable::maximizeChange();
    }

    //_________________________________________________________
    void Client::shadeChange( void  )
    {
        if( hasSizeGrip() ) sizeGrip().setVisible( !( isShade() || isMaximized() ) );
        KCommonDecorationUnstable::shadeChange();
    }

    //_________________________________________________________
    void Client::captionChange( void  )
    {

        KCommonDecorationUnstable::captionChange();
        _itemData.setDirty( true );
        if( titleAnimationsEnabled() )
        { _titleAnimationData->setDirty( true ); }

    }

    //_________________________________________________________
    QPalette Client::backgroundPalette( const QWidget* widget, QPalette palette ) const
    {

        if( _configuration->drawTitleOutline() )
        {
            if( glowIsAnimated() && !isForcedActive() )
            {

                const QColor inactiveColor( backgroundColor( widget, palette, false ) );
                const QColor activeColor( backgroundColor( widget, palette, true ) );
                const QColor mixed( KColorUtils::mix( inactiveColor, activeColor, glowIntensity() ) );
                palette.setColor( QPalette::Window, mixed );
                palette.setColor( QPalette::Button, mixed );

            } else if( isActive() || isForcedActive() ) {

                const QColor color =  options()->color( KDecorationDefines::ColorTitleBar, true );
                palette.setColor( QPalette::Window, color );
                palette.setColor( QPalette::Button, color );

            }

        }

        return palette;

    }

    //_________________________________________________________
    QColor Client::backgroundColor( const QWidget*, QPalette palette, bool active ) const
    {

        return ( _configuration->drawTitleOutline() && active ) ?
            options()->color( KDecorationDefines::ColorTitleBar, true ):
            palette.color( QPalette::Window );

    }

    //_________________________________________________________
    QString Client::defaultButtonsLeft() const
    { return KCommonDecoration::defaultButtonsLeft(); }

    //_________________________________________________________
    QString Client::defaultButtonsRight() const
    { return "HIAX"; }

    //________________________________________________________________
    void Client::updateWindowShape()
    {

        if(isMaximized()) clearMask();
        else setMask( calcMask() );

    }

    //______________________________________________________________________________
    bool Client::eventFilter( QObject* object, QEvent* event )
    {

        // all dedicated event filtering is here to handle multiple tabs.

        bool state = false;
        switch( event->type() )
        {

            case QEvent::Show:
            if( widget() == object )
            { _itemData.setDirty( true ); }
            break;

            case QEvent::MouseButtonPress:
            if( widget() == object )
            { state = mousePressEvent( static_cast< QMouseEvent* >( event ) ); }
            break;

            case QEvent::MouseButtonRelease:
            if( widget() == object ) state = mouseReleaseEvent( static_cast< QMouseEvent* >( event ) );
            else if( Button *btn = qobject_cast< Button* >( object ) )
            {
                QMouseEvent* mouseEvent( static_cast< QMouseEvent* >( event ) );
                if( mouseEvent->button() == Qt::LeftButton && btn->rect().contains( mouseEvent->pos() ) )
                { state = closeItem( btn ); }
            }

            break;

            case QEvent::MouseMove:
            state = mouseMoveEvent( static_cast< QMouseEvent* >( event ) );
            break;

            case QEvent::DragEnter:
            if(  widget() == object )
            { state = dragEnterEvent( static_cast< QDragEnterEvent* >( event ) ); }
            break;

            case QEvent::DragMove:
            if( widget() == object )
            { state = dragMoveEvent( static_cast< QDragMoveEvent* >( event ) ); }
            break;

            case QEvent::DragLeave:
            if( widget() == object )
            { state = dragLeaveEvent( static_cast< QDragLeaveEvent* >( event ) ); }
            break;

            case QEvent::Drop:
            if( widget() == object )
            { state = dropEvent( static_cast< QDropEvent* >( event ) ); }
            break;

            default: break;

        }
        return state || KCommonDecorationUnstable::eventFilter( object, event );

    }

    //_________________________________________________________
    void Client::resizeEvent( QResizeEvent* event )
    {

        // prepare item data updates
        _itemData.setDirty( true );

        // mark title animation as dirty
        if( event->oldSize().width() != event->size().width() )
        { _titleAnimationData->setDirty( true ); }

        // resize backing store pixmap
        if( !compositingActive() )
        { _pixmap = QPixmap( event->size() ); }

        // base class implementation
        KCommonDecorationUnstable::resizeEvent( event );
    }

    //_________________________________________________________
    void Client::paintBackground( QPainter& painter ) const
    {
        if( !compositingActive() )
        { painter.drawPixmap( QPoint(), _pixmap ); }
    }

    //_________________________________________________________
    QRegion Client::region( KDecorationDefines::Region r )
    {

        // return empty region for anything but extended borders, when enabled
        if( !( r == KDecorationDefines::ExtendedBorderRegion && configuration()->useExtendedWindowBorders() ) )
        { return QRegion(); }

        // return empty region for maximized windows
        if( isMaximized() ) return QRegion();

        // return 3 pixels extended borders for sides that have no visible borders
        // also add the invisible pixels at the masked rounded corners, in non compositing mode
        if( configuration()->frameBorder() <= Configuration::BorderNoSide || !compositingActive() )
        {

            QRect rect = widget()->rect().adjusted(
                layoutMetric( LM_OuterPaddingLeft ),
                layoutMetric( LM_OuterPaddingTop ),
                - layoutMetric( LM_OuterPaddingRight ),
                - layoutMetric( LM_OuterPaddingBottom ) );

            rect.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) );

            // mask
            QRegion mask( calcMask() );
            if( mask.isEmpty() ) mask = rect;
            else mask.translate( -layoutMetric( LM_OuterPaddingLeft ), -layoutMetric( LM_OuterPaddingTop ) );

            // only return non-empty region on the sides for which there is no border
            if( configuration()->frameBorder() == Configuration::BorderNone ) return QRegion( rect.adjusted( -3, 0, 3, 3 ) ) - mask;
            else if( configuration()->frameBorder() == Configuration::BorderNoSide ) return QRegion( rect.adjusted( -3, 0, 3, 0 ) ) - mask;
            else if( !compositingActive() ) return QRegion( rect ) - mask;

        }

        // fall back
        return QRegion();

    }


    //_________________________________________________________
    void Client::paintEvent( QPaintEvent* event )
    {

        // factory
        if(!( _initialized && _factory->initialized() ) ) return;

        if( compositingActive() )
        {

            QPainter painter(widget());
            painter.setRenderHint(QPainter::Antialiasing);
            painter.setClipRegion( event->region() );
            paint( painter );

            // update buttons
            QList<Button*> buttons( widget()->findChildren<Button*>() );
            foreach( Button* button, buttons )
            {
                if( ( button->isVisible() || isPreview() ) && event->rect().intersects( button->geometry() ) )
                {

                    painter.save();
                    painter.setViewport( button->geometry() );
                    painter.setWindow( button->rect() );
                    button->paint( painter );
                    painter.restore();

                }
            }


        } else {

            {
                // update backing store pixmap
                QPainter painter( &_pixmap );
                painter.setRenderHint(QPainter::Antialiasing);
                painter.setClipRegion( event->region() );
                paint( painter );
            }

            QPainter painter( widget() );
            painter.setClipRegion( event->region() );
            painter.drawPixmap( QPoint(), _pixmap );

            // update buttons
            QList<Button*> buttons( widget()->findChildren<Button*>() );
            foreach( Button* button, buttons )
            {
                if( event->rect().intersects( button->geometry() ) )
                { button->update(); }
            }

        }



    }

    //_________________________________________________________
    void Client::paint( QPainter& painter )
    {

        // palette
        QPalette palette = widget()->palette();
        palette.setCurrentColorGroup( (isActive() ) ? QPalette::Active : QPalette::Inactive );

        // define frame
        QRect frame = widget()->rect();

        // base color
        QColor color = palette.window().color();

        // draw shadows
        if( compositingActive() && shadowCache().shadowSize() > 0 && !isMaximized() )
        {

            TileSet *tileSet( 0 );
            const ShadowCache::Key key( this->key() );
            if( shadowCache().isEnabled( QPalette::Active ) && glowIsAnimated() && !isForcedActive() )
            {

                tileSet = shadowCache().tileSet( key, glowIntensity() );

            } else {

                tileSet = shadowCache().tileSet( key );

            }

            tileSet->render( frame, &painter, TileSet::Ring);

        }

        // adjust frame
        frame.adjust(
            layoutMetric(LM_OuterPaddingLeft),
            layoutMetric(LM_OuterPaddingTop),
            -layoutMetric(LM_OuterPaddingRight),
            -layoutMetric(LM_OuterPaddingBottom) );

        //  adjust mask
        if( compositingActive() || isPreview() )
        {

            if( isMaximized() ) {

                painter.setClipRect( frame, Qt::IntersectClip );

            } else {

                // multipliers
                const int left = 1;
                const int right = 1;
                const int top = 1;
                int bottom = 1;

                // disable bottom corners when border frame is too small and window is not shaded
                if( _configuration->frameBorder() == Configuration::BorderNone && !isShade() ) bottom = 0;
                QRegion mask( helper().roundedMask( frame, left, right, top, bottom ) );

                renderCorners( &painter, frame, palette );
                painter.setClipRegion( mask, Qt::IntersectClip );

            }

        }

        // make sure ItemData and tabList are synchronized
        /*
        this needs to be done before calling RenderWindowBorder
        since some painting in there depend on the clientGroups state
        */
        if(  _itemData.isDirty() || _itemData.count() != tabCount() )
        { updateItemBoundingRects( false ); }

        // window background
        renderWindowBackground( &painter, frame, widget(), backgroundPalette( widget(), palette ) );

        // window border (for title outline)
        if( hasTitleOutline() ) renderWindowBorder( &painter, frame, widget(), palette );

        // clipping
        if( compositingActive() )
        {
            painter.setClipping(false);
            frame.adjust(-1,-1, 1, 1);
        }

        // float frame
        renderFloatFrame( &painter, frame, palette );

        // resize handles
        renderDots( &painter, frame, backgroundColor( widget(), palette ) );

        if( !hideTitleBar() )
        {

            // title bounding rect
            painter.setFont( options()->font(isActive(), false) );

            // draw ClientGroupItems
            const int itemCount( _itemData.count() );
            for( int i = 0; i < itemCount; i++ ) renderItem( &painter, i, palette );

            // draw target rect
            renderTargetRect( &painter, widget()->palette() );

            // separator
            if( itemCount == 1 && !_itemData.isAnimated() && drawSeparator() )
            { renderSeparator(&painter, frame, widget(), color ); }

        }

    }

    //_____________________________________________________________
    bool Client::mousePressEvent( QMouseEvent* event )
    {

        const QPoint point = event->pos();
        if( tabIndexAt( point ) < 0 ) return false;
        _dragPoint = point;

        _mouseButton = event->button();
        bool accepted( false );
        if( buttonToWindowOperation( _mouseButton ) == TabDragOp )
        {

            accepted = true;

        } else if( buttonToWindowOperation( _mouseButton ) == OperationsOp ) {

            QPoint point = event->pos();
            const int clickedIndex( tabIndexAt( point ) );
            _mouseButton = Qt::NoButton;
            if ( tabIndexAt( point ) > -1)
            { showWindowMenu( widget()->mapToGlobal( event->pos() ), tabId(clickedIndex) ); }

            accepted = true; // displayClientMenu can possibly destroy the deco...

        }
        return accepted;
    }

    //_____________________________________________________________
    bool Client::mouseReleaseEvent( QMouseEvent* event )
    {

        bool accepted( false );
        if( _mouseButton == event->button() && buttonToWindowOperation( _mouseButton ) != OperationsOp )
        {

            const QPoint point = event->pos();

            const long visibleItem = currentTabId();
            const int clickedIndex( tabIndexAt( point ) );
            if( clickedIndex >= 0 && visibleItem != tabId(clickedIndex) )
            {
                setCurrentTab( tabId(clickedIndex) );
                setForceActive( true );
                accepted = true;
            }

        }

        _mouseButton = Qt::NoButton;
        return accepted;

    }

    //_____________________________________________________________
    bool Client::mouseMoveEvent( QMouseEvent* event )
    {

        // check button and distance to drag point
        if( hideTitleBar() || _mouseButton == Qt::NoButton  || ( event->pos() - _dragPoint ).manhattanLength() <= QApplication::startDragDistance() )
        { return false; }

        bool accepted( false );
        if( buttonToWindowOperation( _mouseButton ) == TabDragOp )
        {

            const QPoint point = event->pos();
            const int clickedIndex( tabIndexAt( point ) );
            if( clickedIndex < 0 ) return false;

            _titleAnimationData->reset();

            QDrag *drag = new QDrag( widget() );
            QMimeData *groupData = new QMimeData();
            groupData->setData( tabDragMimeType(), QString().setNum( tabId(clickedIndex) ).toAscii() );
            drag->setMimeData( groupData );
            _sourceItem = tabIndexAt( _dragPoint );

            // get tab geometry
            QRect geometry( _itemData[clickedIndex]._boundingRect );

            // remove space used for buttons
            if( _itemData.count() > 1  )
            {

                geometry.adjust( 0, 0,  - buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 );

            } else if( !( isActive() && _configuration->drawTitleOutline() ) ) {

                geometry.adjust(
                    buttonsLeftWidth() + layoutMetric( LM_TitleEdgeLeft ) , 0,
                    -( buttonsRightWidth() + layoutMetric( LM_TitleEdgeRight )), 0 );

            }

            // adjust geometry to include shadow size
            const int shadowSize( shadowCache().shadowSize() );
            const bool drawShadow(
                compositingActive() &&
                KStyle::customStyleHint( "SH_ArgbDndWindow", widget() ) &&
                shadowSize > 0 );

            if( drawShadow )
            { geometry.adjust( -shadowSize, -shadowSize, shadowSize, shadowSize ); }

            // compute pixmap and assign
            drag->setPixmap( itemDragPixmap( clickedIndex, geometry, drawShadow ) );

            // note: the pixmap is moved just above the pointer on purpose
            // because overlapping pixmap and pointer slows down the pixmap a lot.
            QPoint hotSpot( QPoint( event->pos().x() - geometry.left(), -1 ) );
            if( drawShadow ) hotSpot += QPoint( 0, shadowSize );

            // make sure the horizontal hotspot position is not too far away (more than 1px)
            // from the pixmap
            if( hotSpot.x() < -1 ) hotSpot.setX(-1);
            if( hotSpot.x() > geometry.width() ) hotSpot.setX( geometry.width() );

            drag->setHotSpot( hotSpot );

            _dragStartTimer.start( 50, this );
            drag->exec( Qt::MoveAction );

            // detach tab from window
            if( drag->target() == 0 && _itemData.count() > 1 )
            {
                _itemData.setDirty( true );
                untab( tabId(_sourceItem),
                    widget()->frameGeometry().adjusted(
                    layoutMetric( LM_OuterPaddingLeft ),
                    layoutMetric( LM_OuterPaddingTop ),
                    -layoutMetric( LM_OuterPaddingRight ),
                    -layoutMetric( LM_OuterPaddingBottom )
                    ).translated( QCursor::pos() - event->pos() +
                    QPoint( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop )))
                    );
            }

            // reset button
            _mouseButton = Qt::NoButton;
            accepted = true;

        }

        return accepted;

    }

    //_____________________________________________________________
    bool Client::dragEnterEvent( QDragEnterEvent* event )
    {

        // check if drag enter is allowed
        if( !event->mimeData()->hasFormat( tabDragMimeType() ) || hideTitleBar() ) return false;

        // accept event
        event->acceptProposedAction();

        // animate
        if( event->source() != widget() )
        {

            _itemData.animate( AnimationEnter, tabIndexAt( event->pos(), true ) );

        } else if( _itemData.count() > 1 )  {

            _itemData.animate( AnimationEnter|AnimationSameTarget, tabIndexAt( event->pos(), true ) );

        }

        return true;

    }

    //_____________________________________________________________
    bool Client::dragLeaveEvent( QDragLeaveEvent* )
    {

        if( _itemData.animationType() & AnimationSameTarget )
        {

            if( _dragStartTimer.isActive() ) _dragStartTimer.stop();
            _itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem );

        } else if( _itemData.isAnimated() ) {

            _itemData.animate( AnimationLeave );

        }

        return true;

    }

    //_____________________________________________________________
    bool Client::dragMoveEvent( QDragMoveEvent* event )
    {

        // check format
        if( !event->mimeData()->hasFormat( tabDragMimeType() ) ) return false;

        // animate
        if( event->source() != widget() )
        {

            _itemData.animate( AnimationMove, tabIndexAt( event->pos(), true ) );

        } else if( _itemData.count() > 1 )  {

            if( _dragStartTimer.isActive() ) _dragStartTimer.stop();
            _itemData.animate( AnimationMove|AnimationSameTarget, tabIndexAt( event->pos(), true ) );

        }

        return false;

    }

    //_____________________________________________________________
    bool Client::dropEvent( QDropEvent* event )
    {

        const QPoint point = event->pos();
        _itemData.animate( AnimationNone );

        const QMimeData *groupData = event->mimeData();
        if( !groupData->hasFormat( tabDragMimeType() ) ) return false;

        _itemData.setDirty( true );

        if( widget() != event->source() ) setForceActive( true );

        const long source = QString( groupData->data( tabDragMimeType() ) ).toLong();
        const int clickedIndex( tabIndexAt( point, true ) );
        if( clickedIndex < 0 ) tab_A_behind_B( source, tabId(_itemData.count()-1) );
        else tab_A_before_B( source, tabId(clickedIndex) );

        // update title
        if( widget() == event->source() ) updateTitleRect();

        _titleAnimationData->reset();
        return true;

    }

    //_____________________________________________________________
    void Client::timerEvent( QTimerEvent* event )
    {

        if( event->timerId() != _dragStartTimer.timerId() )
        { return KCommonDecorationUnstable::timerEvent( event ); }

        _dragStartTimer.stop();

        // do nothing if there is only one tab
        if( _itemData.count() > 1 )
        {
            _itemData.animate( AnimationMove|AnimationSameTarget, _sourceItem );
            _itemData.animate( AnimationLeave|AnimationSameTarget, _sourceItem );
        }

    }

    //_____________________________________________________________
    bool Client::closeItem( const Button* button )
    {

        for( int i=0; i <  _itemData.count(); i++ )
        {
            if( button == _itemData[i]._closeButton.data() )
            {
                _itemData.setDirty( true );
                closeTab( tabId(i) );
                return true;
            }
        }
        return false;

    }

    //________________________________________________________________
    QPixmap Client::itemDragPixmap( int index, QRect geometry, bool drawShadow )
    {
        const bool itemValid( index >= 0 && index < tabCount() );

        QPixmap pixmap( geometry.size() );
        pixmap.fill( Qt::transparent );
        QPainter painter( &pixmap );
        painter.setRenderHints(QPainter::SmoothPixmapTransform|QPainter::Antialiasing);

        painter.translate( -geometry.topLeft() );

        // draw shadows
        if( drawShadow )
        {

            // shadow
            const int shadowSize( shadowCache().shadowSize() );
            TileSet *tileSet( shadowCache().tileSet( ShadowCache::Key() ) );
            tileSet->render( geometry, &painter, TileSet::Ring);
            geometry.adjust( shadowSize, shadowSize, -shadowSize, -shadowSize );

            renderCorners( &painter, geometry, widget()->palette() );

        }

        // mask
        painter.setClipRegion( helper().roundedMask( geometry ), Qt::IntersectClip );

        // render window background
        renderWindowBackground( &painter, geometry, widget(), widget()->palette() );

        // darken background if item is inactive
        const bool itemActive = (tabCount() <= 1) || !( itemValid && tabId(index) != currentTabId() );
        if( !itemActive )
        {

            QLinearGradient lg( geometry.topLeft(), geometry.bottomLeft() );
            lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) );
            lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) );
            painter.setBrush( lg );
            painter.setPen( Qt::NoPen );
            painter.drawRect( geometry );

        }

        // render title text
        painter.setFont( options()->font(isActive(), false) );
        QRect textRect( geometry.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) );

        if( itemValid )
        { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); }

        const QString caption( itemValid ? this->caption(index) : this->caption() );

        renderTitleText(
            &painter, textRect, caption,
            titlebarTextColor( widget()->palette(), isActive() && itemActive ),
            titlebarContrastColor( widget()->palette() ) );

        // adjust geometry for floatFrame when compositing is on.
        if( drawShadow )
        { geometry.adjust(-1, -1, 1, 1 ); }

        // floating frame
        helper().drawFloatFrame(
            &painter, geometry, widget()->palette().window().color(),
            !drawShadow, false,
            KDecoration::options()->color(ColorTitleBar)
            );

        painter.end();
        return pixmap;

    }

    //_________________________________________________________________
    void Client::createSizeGrip( void )
    {

        assert( !hasSizeGrip() );
        if( ( isResizable() && windowId() != 0 ) || isPreview() )
        {
            _sizeGrip = new SizeGrip( this );
            sizeGrip().setVisible( !( isMaximized() || isShade() ) );
        }

    }

    //_________________________________________________________________
    void Client::deleteSizeGrip( void )
    {
        assert( hasSizeGrip() );
        _sizeGrip->deleteLater();
        _sizeGrip = 0;
    }

    //_________________________________________________________________
    void Client::removeShadowHint( void )
    {

        // do nothing if no window id
        if( !windowId() ) return;

        // create atom
        if( !_shadowAtom )
        { _shadowAtom = XInternAtom( QX11Info::display(), "_KDE_NET_WM_SHADOW", False); }

        XDeleteProperty(QX11Info::display(), windowId(), _shadowAtom);
    }

}
