/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2007 Philip Falkner <philip.falkner@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

#include "boxswitch.h"

#include <kwinconfig.h>

#include <QMouseEvent>
#include <QSize>

#include <kapplication.h>
#include <kcolorscheme.h>
#include <kconfiggroup.h>

#ifdef KWIN_HAVE_OPENGL_COMPOSITING
#include <GL/gl.h>
#endif

namespace KWin
{

KWIN_EFFECT( boxswitch, BoxSwitchEffect )

BoxSwitchEffect::BoxSwitchEffect()
    : mActivated( 0 )
    , mMode( 0 )
    , thumbnailFrame( effects->effectFrame( EffectFrameStyled ) )
    , selected_window( 0 )
    , painting_desktop( 0 )
    , animation( false )
    , highlight_is_set( false )
    , primaryTabBox( true )
    , secondaryTabBox( false )
    , mProxy( this )
    , mProxyActivated( 0 )
    , mProxyAnimateSwitch( 0 )
    , mProxyShowText( 0 )
    , mPositioningFactor( 0.5f )
    {
    text_font.setBold( true );
    text_font.setPointSize( 12 );
    thumbnailFrame->setFont( text_font );
    thumbnailFrame->setAlignment( Qt::AlignBottom | Qt::AlignHCenter );

    highlight_margin = 10;
    reconfigure( ReconfigureAll );
    }

BoxSwitchEffect::~BoxSwitchEffect()
    {
    delete thumbnailFrame;
    }

void BoxSwitchEffect::reconfigure( ReconfigureFlags )
    {
    color_frame = KColorScheme( QPalette::Active, KColorScheme::Window ).background().color();
    color_frame.setAlphaF( 0.9 );
    color_highlight = KColorScheme( QPalette::Active, KColorScheme::Selection ).background().color();
    color_highlight.setAlphaF( 0.9 );
    activeTimeLine.setDuration( animationTime( 250 ));
    activeTimeLine.setCurveShape( TimeLine::EaseInOutCurve );
    timeLine.setDuration( animationTime( 150 ));
    timeLine.setCurveShape( TimeLine::EaseInOutCurve );
    KConfigGroup conf = effects->effectConfig( "BoxSwitch" );

    bg_opacity = conf.readEntry( "BackgroundOpacity", 25 ) / 100.0;
    elevate_window = conf.readEntry( "ElevateSelected", true );
    mAnimateSwitch = conf.readEntry( "AnimateSwitch", false );

    primaryTabBox = conf.readEntry( "TabBox", true );
    secondaryTabBox = conf.readEntry( "TabBoxAlternative", false );
    }

void BoxSwitchEffect::prePaintWindow( EffectWindow* w, WindowPrePaintData& data, int time )
    {
    if( activeTimeLine.value() != 0.0 && !mProxyActivated )
        {
        if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
            {
            if( windows.contains( w ))
                {
                if( w == selected_window )
                    w->enablePainting( EffectWindow::PAINT_DISABLED_BY_CLIENT_GROUP );
                else
                    data.setTranslucent();
                w->enablePainting( EffectWindow::PAINT_DISABLED_BY_MINIMIZE | EffectWindow::PAINT_DISABLED_BY_DESKTOP );
                }
            }
        else
            {
            if( painting_desktop )
                {
                if( w->isOnDesktop( painting_desktop ))
                    w->enablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP );
                else
                    w->disablePainting( EffectWindow::PAINT_DISABLED_BY_DESKTOP );
                }
            }
        }
    effects->prePaintWindow( w, data, time );
    }

void BoxSwitchEffect::prePaintScreen( ScreenPrePaintData& data, int time )
    {
    if( mActivated )
        activeTimeLine.addTime( time );
    else
        {
        activeTimeLine.removeTime( time );
        if( activeTimeLine.value() == 0.0 )
            { // No longer need the window data
            qDeleteAll( windows );
            windows.clear();
            }
        }
    if( mActivated && animation )
        {
        timeLine.addTime( time );
        calculateItemSizes();
        }
    effects->prePaintScreen( data, time );
    }

void BoxSwitchEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
    {
    effects->paintScreen( mask, region, data );
    if( mActivated && !mProxyActivated )
        {
        if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
            {
            paintWindowsBox(region);
            }
        else
            {
            if( !painting_desktop )
                {
                thumbnailFrame->setSelection( desktops[ selected_desktop ]->area );
                thumbnailFrame->render( region );

                QHash< int, ItemInfo* >::const_iterator i;
                for( i = desktops.constBegin(); i != desktops.constEnd(); ++i )
                    {
                    painting_desktop = i.key();
                    paintDesktopThumbnail( painting_desktop );
                    }
                painting_desktop = 0;
                }
            }
        }
    }

void BoxSwitchEffect::paintWindowsBox(const QRegion& region)
    {
    if( (mAnimateSwitch && !mProxyActivated) || (mProxyActivated && mProxyAnimateSwitch) )
        thumbnailFrame->setSelection( highlight_area );
    else
        {
        ItemInfo *info = windows.value( selected_window, 0 );
        thumbnailFrame->setSelection( info ? info->area : QRect() );
        }
        
    thumbnailFrame->render( region );

    if( (mAnimateSwitch && !mProxyActivated) || (mProxyActivated && mProxyAnimateSwitch) )
        {
        // HACK: PaintClipper is used because window split is somehow wrong if the height is greater than width
        PaintClipper::push( frame_area );
        QHash< EffectWindow*, ItemInfo* >::const_iterator i;
        for( i = windows.constBegin(); i != windows.constEnd(); ++i )
            {
            paintWindowThumbnail( i.key() );
            paintWindowIcon( i.key() );
            }
        PaintClipper::pop( frame_area );
        }
    else
        {
        QHash< EffectWindow*, ItemInfo* >::const_iterator i;
        for( i = windows.constBegin(); i != windows.constEnd(); ++i )
            {
            paintWindowThumbnail( i.key() );
            paintWindowIcon( i.key() );
            }
        }
    }

void BoxSwitchEffect::postPaintScreen()
    {
    if( mActivated && activeTimeLine.value() != 1.0 )
        effects->addRepaintFull();
    if( !mActivated && activeTimeLine.value() != 0.0 )
        effects->addRepaintFull();
    if( mActivated && animation )
        {
        if( timeLine.value() == 1.0 )
            {
            timeLine.setProgress( 0.0 );
            animation = false;
            if( !scheduled_directions.isEmpty() )
                {
                direction = scheduled_directions.dequeue();
                animation = true;
                }
            }
        QRect repaint = QRect( frame_area.x() - item_max_size.width()*0.5,
            frame_area.y(),
            frame_area.width() + item_max_size.width(),
            frame_area.height() );
        effects->addRepaint( repaint );
        }
    effects->postPaintScreen();
    }

void BoxSwitchEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
    {
    if((( mActivated && (mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode) )
        || ( !mActivated && activeTimeLine.value() != 0.0 )) && !mProxyActivated)
        {
        if( windows.contains( w ) && w != selected_window )
            {
            if( w->isMinimized() || !w->isOnCurrentDesktop() )
                // TODO: When deactivating minimized windows are not painted at all
                data.opacity *= activeTimeLine.value() * bg_opacity;
            else
                data.opacity *= 1.0 - activeTimeLine.value() * ( 1.0 - bg_opacity );
            }
        }
    effects->paintWindow( w, mask, region, data );
    }

void BoxSwitchEffect::windowInputMouseEvent( Window w, QEvent* e )
    {
    assert( w == mInput );
    Q_UNUSED( w );
    if( e->type() != QEvent::MouseButtonPress )
        return;
    QPoint pos = static_cast< QMouseEvent* >( e )->pos();
    pos += frame_area.topLeft();

    // determine which item was clicked
    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        QHash< EffectWindow*, ItemInfo* >::const_iterator i;
        for( i = windows.constBegin(); i != windows.constEnd(); ++i )
            {
            if( i.value()->clickable.contains( pos ))
                {
                effects->setTabBoxWindow( i.key() );
                break;
                }
            }
        // special handling for second half of window in case of animation and even number of windows
        if( mAnimateSwitch && ( windows.size() % 2 == 0 ) )
            {
            QRect additionalRect = QRect( frame_area.x(), frame_area.y(),
                item_max_size.width()*0.5, item_max_size.height());
            if( additionalRect.contains( pos ))
                {
                effects->setTabBoxWindow( right_window );
                }
            }
        }
    else
        {
        QHash< int, ItemInfo* >::const_iterator i;
        for( i = desktops.constBegin(); i != desktops.constEnd(); ++i )
            {
            if( i.value()->clickable.contains( pos ))
                effects->setTabBoxDesktop( i.key() );
            }
        }
    }

void BoxSwitchEffect::windowDamaged( EffectWindow* w, const QRect& damage )
    {
    Q_UNUSED( damage );
    if( mActivated )
        {
        if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
            {
            if( windows.contains( w ))
                {
                effects->addRepaint( frame_area );
                }
            }
        else
            {
            if( w->isOnAllDesktops())
                {
                foreach( ItemInfo* info, desktops )
                    effects->addRepaint( info->area );
                }
            else
                {
                effects->addRepaint( desktops[ w->desktop() ]->area );
                }
            }
        }
    }

void BoxSwitchEffect::windowGeometryShapeChanged( EffectWindow* w, const QRect& old )
    {
    if( mActivated )
        {
        if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
            {
            ItemInfo *info = windows.value( w, 0L );
            if( info && w->size() != old.size())
                {
                effects->addRepaint( info->area );
                }
            }
        else
            {
            if( w->isOnAllDesktops())
                {
                foreach( ItemInfo* info, desktops )
                    effects->addRepaint( info->area );
                }
            else
                {
                effects->addRepaint( desktops[ w->desktop() ]->area );
                }
            }
        }
    }

void BoxSwitchEffect::tabBoxAdded( int mode )
    {
    if( !mActivated )
        {
        if( ( mode == TabBoxWindowsMode && primaryTabBox ) ||
            ( mode == TabBoxWindowsAlternativeMode && secondaryTabBox) )
            {
            if( effects->currentTabBoxWindowList().count() > 0 )
                {
                mMode = mode;
                effects->refTabBox();
                highlight_is_set = false;
                animation = false;
                scheduled_directions.clear();
                right_window = 0;
                setActive();
                }
            }
        else if( mode == TabBoxDesktopListMode || mode == TabBoxDesktopMode )
            { // DesktopMode
            if( effects->currentTabBoxDesktopList().count() > 0 )
                {
                mMode = mode;
                painting_desktop = 0;
                effects->refTabBox();
                setActive();
                }
            }
        }
    }

void BoxSwitchEffect::tabBoxClosed()
    {
    if( mActivated )
        setInactive();
    }

void BoxSwitchEffect::tabBoxUpdated()
    {
    if( mActivated )
        {
        if( (mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode)
            && selected_window != effects->currentTabBoxWindow() )
            {
            if( selected_window != NULL )
                {
                if( (mAnimateSwitch && !mProxyActivated) || (mProxyActivated && mProxyAnimateSwitch) )
                    {
                    int old_index = effects->currentTabBoxWindowList().indexOf( selected_window );
                    int new_index = effects->currentTabBoxWindowList().indexOf( effects->currentTabBoxWindow() );
                    Direction new_direction;
                    int distance = new_index - old_index;
                    if( distance > 0 )
                        new_direction = Left;
                    if( distance < 0 )
                        new_direction = Right;
                    if( distance != 0 )
                        {
                        distance = abs( distance );
                        int tempDistance = effects->currentTabBoxWindowList().count() - distance;
                        if( tempDistance < abs( distance ) )
                            {
                            distance = tempDistance;
                            if( new_direction == Left )
                                new_direction = Right;
                            else
                                new_direction = Left;
                            }
                        if( !animation )
                            {
                            animation = true;
                            direction = new_direction;
                            distance--;
                            }
                        for( int i=0; i<distance; i++ )
                            {
                            if( !scheduled_directions.isEmpty() && scheduled_directions.last() != new_direction )
                                scheduled_directions.pop_back();
                            else
                                scheduled_directions.enqueue( new_direction );
                            if( scheduled_directions.count() == effects->currentTabBoxWindowList().count() )
                                scheduled_directions.clear();
                            }
                        }
                    }
                if ( ItemInfo *info = windows.value( selected_window, 0 ) )
                    effects->addRepaint( info->area );
                selected_window->addRepaintFull();
                }
            setSelectedWindow( effects->currentTabBoxWindow());
            if ( ItemInfo *info = windows.value( selected_window, 0 ) )
                effects->addRepaint( info->area );
            if ( selected_window ) // @Martin can effects->currentTabBoxWindow() be NULL?
                selected_window->addRepaintFull();
            effects->addRepaint( text_area );
            }
        else if( mMode != TabBoxWindowsMode && mMode != TabBoxWindowsAlternativeMode )
            { // DesktopMode
            if ( ItemInfo *info = desktops.value( selected_desktop, 0 ) )
                effects->addRepaint( info->area );
            selected_desktop = effects->currentTabBoxDesktop();
            if( !mProxyActivated || mProxyShowText )
                thumbnailFrame->setText( effects->desktopName( selected_desktop ));
            if ( ItemInfo *info = desktops.value( selected_desktop, 0 ) )
                effects->addRepaint( info->area );
            effects->addRepaint( text_area );
            if( effects->currentTabBoxDesktopList() == original_desktops )
                return;
            original_desktops = effects->currentTabBoxDesktopList();
            }
        // for safety copy list each time tabbox is updated
        original_windows = effects->currentTabBoxWindowList();
        effects->addRepaint( frame_area );
        calculateFrameSize();
        calculateItemSizes();
        moveResizeInputWindow( frame_area.x(), frame_area.y(), frame_area.width(), frame_area.height());
        effects->addRepaint( frame_area );
        }
    }

void BoxSwitchEffect::setActive()
    {
    mActivated = true;

    // Just in case we are activated again before the deactivation animation finished
    qDeleteAll( windows );
    windows.clear();

    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        original_windows = effects->currentTabBoxWindowList();
        setSelectedWindow( effects->currentTabBoxWindow());
        }
    else
        {
        original_desktops = effects->currentTabBoxDesktopList();
        selected_desktop = effects->currentTabBoxDesktop();
        if( !mProxyActivated || mProxyShowText )
            thumbnailFrame->setText( effects->desktopName( selected_desktop ));
        }
    calculateFrameSize();
    calculateItemSizes();
    if( !mProxyActivated )
        {
        // only create the input window when effect is not activated via the proxy
        mInput = effects->createInputWindow( this, frame_area.x(), frame_area.y(),
                                             frame_area.width(), frame_area.height(), Qt::ArrowCursor );
        }
    effects->addRepaint( frame_area );
    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        QHash< EffectWindow*, ItemInfo* >::const_iterator i;
        for( i = windows.constBegin(); i != windows.constEnd(); ++i )
            {
            i.key()->addRepaintFull();
            }
        }
    }

void BoxSwitchEffect::setInactive()
    {
    mActivated = false;
    effects->unrefTabBox();
    if( !mProxyActivated && mInput != None )
        {
        effects->destroyInputWindow( mInput );
        mInput = None;
        }
    mProxyActivated = false;
    mPositioningFactor = 0.5f;
    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        QHash< EffectWindow*, ItemInfo* >::const_iterator i;
        for( i = windows.constBegin(); i != windows.constEnd(); ++i )
            {
            if( i.key() != selected_window )
                i.key()->addRepaintFull();
            }
        // We don't unset the selected window so we have correct fading
        // But we do need to remove elevation status
        if( elevate_window && selected_window )
            effects->setElevatedWindow( selected_window, false );

        foreach( EffectWindow* w, referrencedWindows )
            {
            w->unrefWindow();
            }
        referrencedWindows.clear();
        }
    else
        { // DesktopMode
        qDeleteAll( windows );
        desktops.clear();
        }
    thumbnailFrame->free();
    effects->addRepaint( frame_area );
    frame_area = QRect();
    }

void BoxSwitchEffect::setSelectedWindow( EffectWindow* w )
    {
    if( elevate_window && selected_window )
        {
        effects->setElevatedWindow( selected_window, false );
        }
    selected_window = w;
    if( selected_window && ( !mProxyActivated || mProxyShowText ) )
        thumbnailFrame->setText( selected_window->caption() );
    if( elevate_window && w )
        {
        effects->setElevatedWindow( selected_window, true );
        }
    }

void BoxSwitchEffect::windowClosed( EffectWindow* w )
    {
    if( w == selected_window )
        {
        setSelectedWindow( 0 );
        }
    QHash< EffectWindow*, ItemInfo* >::iterator it = windows.find( w );
    if( it != windows.end() )
        {
        w->refWindow();
        referrencedWindows.append( w );
        original_windows.removeAll( w );
        delete *it; *it = 0;
        windows.erase( it );
        effects->addRepaintFull();
        }
    }

void BoxSwitchEffect::moveResizeInputWindow( int x, int y, int width, int height )
    {
    XMoveWindow( display(), mInput, x, y );
    XResizeWindow( display(), mInput, width, height );
    }

void BoxSwitchEffect::calculateFrameSize()
    {
    int itemcount;

    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        itemcount = original_windows.count();
        item_max_size.setWidth( 200 );
        item_max_size.setHeight( 200 );
        }
    else
        {
        itemcount = original_desktops.count();
        item_max_size.setWidth( 200 );
        item_max_size.setHeight( 200 );
        }
    // How much height to reserve for a one-line text label
    text_area.setHeight( QFontMetrics( text_font ).height() * 1.2 );
    // Separator space between items and text
    const int separator_height = 6;
    // Shrink the size until all windows/desktops can fit onscreen
    frame_area.setWidth( itemcount * item_max_size.width());
    QRect screenr = effects->clientArea( PlacementArea, effects->activeScreen(), effects->currentDesktop());
    while( frame_area.width() > screenr.width())
        {
        item_max_size /= 2;
        frame_area.setWidth( itemcount * item_max_size.width());
        }
    frame_area.setHeight( item_max_size.height() +
            separator_height + text_area.height());
    if( mProxyActivated && !mProxyShowText )
        frame_area.setHeight( item_max_size.height() );
    text_area.setWidth( frame_area.width() );

    frame_area.moveTo( screenr.x() + ( screenr.width() - frame_area.width()) / 2,
        screenr.y() + ( screenr.height() - frame_area.height()) / 2 * mPositioningFactor * 2 );
    text_area.moveTo( frame_area.x(),
                      frame_area.y() + item_max_size.height() + separator_height);

    thumbnailFrame->setGeometry( frame_area );
    }

void BoxSwitchEffect::calculateItemSizes()
    {
    if( mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode )
        {
        qDeleteAll( windows );
        windows.clear();
        if( (mAnimateSwitch && !mProxyActivated) || (mProxyActivated && mProxyAnimateSwitch) )
            {
            int index = original_windows.indexOf( effects->currentTabBoxWindow() );
            int leftIndex = index;
            int rightIndex = index + 1;
            if( rightIndex == original_windows.count() )
                rightIndex = 0;
            EffectWindowList ordered_windows;

            int leftWindowCount = original_windows.count()/2;
            int rightWindowCount = leftWindowCount;
            if( original_windows.count()%2 == 1 )
                leftWindowCount++;
            for( int i=0; i < leftWindowCount; i++ )
                {
                int tempIndex = ( leftIndex - i );
                if( tempIndex < 0 )
                    tempIndex = original_windows.count() + tempIndex;
                ordered_windows.prepend( original_windows[ tempIndex ] );
                }
            for( int i=0; i < rightWindowCount; i++ )
                {
                int tempIndex = ( rightIndex + i ) % original_windows.count();
                ordered_windows.append( original_windows[ tempIndex ] );
                }
            // move items cause of schedule
            for( int i=0; i < scheduled_directions.count(); i++ )
                {
                Direction actual = scheduled_directions[ i ];
                if( actual == Left )
                    {
                    EffectWindow* w = ordered_windows.takeLast();
                    ordered_windows.prepend( w );
                    }
                else
                    {
                    EffectWindow* w = ordered_windows.takeFirst();
                    ordered_windows.append( w );
                    }
                }
            if( animation && timeLine.value() < 0.5 )
                {
                if( direction == Left )
                    {
                    EffectWindow* w = ordered_windows.takeLast();
                    edge_window = w;
                    ordered_windows.prepend( w );
                    }
                else
                    {
                    EffectWindow* w = ordered_windows.takeFirst();
                    edge_window = w;
                    ordered_windows.append( w );
                    }
                }
            else if( animation && timeLine.value() >= 0.5 )
                {
                if( animation && direction == Left )
                    edge_window = ordered_windows.last();
                if( animation && direction == Right )
                    edge_window = ordered_windows.first();
                }
            int offset = 0;
            if( animation )
                {
                if( direction == Left )
                    offset = (float)item_max_size.width()*(1.0-timeLine.value());
                else
                    offset = -(float)item_max_size.width()*(1.0-timeLine.value());
                }
            for( int i = 0; i < ordered_windows.count(); i++ )
                {
                if( !ordered_windows.at( i ) )
                    continue;
                EffectWindow* w = ordered_windows.at( i );
                ItemInfo *info = windows.value( w, 0 );
                if ( !info )
                    windows[ w ] = info = new ItemInfo();

                info->iconFrame = effects->effectFrame( EffectFrameUnstyled, false );
                info->iconFrame->setAlignment( Qt::AlignTop | Qt::AlignLeft );
                info->iconFrame->setIcon( w->icon() );

                float moveIndex = i;
                if( animation && timeLine.value() < 0.5 )
                    {
                    if( direction == Left )
                        moveIndex--;
                    else
                        moveIndex++;
                    }
                if( ordered_windows.count()%2 == 0 )
                    moveIndex += 0.5;
                info->area = QRect( frame_area.x() + moveIndex * item_max_size.width() + offset,
                                    frame_area.y(),
                                    item_max_size.width(), item_max_size.height());
                info->clickable = info->area;
                }
            if( ordered_windows.count()%2 == 0 )
                {
                right_window = ordered_windows.last();
                }
            if( !highlight_is_set )
                {
                ItemInfo *info = windows.value( selected_window, 0 );
                highlight_area = info ? info->area : QRect();
                highlight_is_set = true;
                }
            }
        else
            {
            for( int i = 0; i < original_windows.count(); i++ )
                {
                if( !original_windows.at( i ) )
                    continue;
                EffectWindow* w = original_windows.at( i );
                ItemInfo *info = windows.value( w, 0 );
                if ( !info )
                    windows[ w ] = info = new ItemInfo();

                info->iconFrame = effects->effectFrame( EffectFrameUnstyled, false );
                info->iconFrame->setAlignment( Qt::AlignTop | Qt::AlignLeft );
                info->iconFrame->setIcon( w->icon() );

                info->area = QRect( frame_area.x() + i * item_max_size.width(),
                                    frame_area.y(),
                                    item_max_size.width(), item_max_size.height());
                info->clickable = info->area;
                }
            }
        }
    else
        {
        desktops.clear();
        for( int i = 0; i < original_desktops.count(); i++ )
            {
            int it = original_desktops.at( i );
            ItemInfo *info = desktops.value( it, 0 );
            if ( !info )
                desktops[ it ] = info = new ItemInfo();

            info->area = QRect( frame_area.x() + i * item_max_size.width(),
                                frame_area.y(),
                                item_max_size.width(), item_max_size.height());
            info->clickable = info->area;
            }
        }
    }

void BoxSwitchEffect::paintWindowThumbnail( EffectWindow* w )
    {
    ItemInfo *info = windows.value( w, 0 );
    if ( !info )
        return;
    WindowPaintData data( w );

    setPositionTransformations( data,
        info->thumbnail, w,
        info->area.adjusted( highlight_margin, highlight_margin, -highlight_margin, -highlight_margin ),
        Qt::KeepAspectRatio );

    if( animation && ( w == edge_window ) && ( windows.size() % 2 == 1 ) )
        {
        // in case of animation:
        // the window which has to change the side will be split and painted on both sides of the edge
        double splitPoint = 0.0;
        if( direction == Left )
            {
            splitPoint = w->geometry().width()*timeLine.value();
            }
        else
            {
            splitPoint = w->geometry().width() - w->geometry().width()*timeLine.value();
            }
        data.quads = data.quads.splitAtX( splitPoint );
        WindowQuadList left_quads;
        WindowQuadList right_quads;
        foreach( const WindowQuad &quad, data.quads )
            {
            if( quad.left() >= splitPoint )
                left_quads << quad;
            if( quad.right() <= splitPoint )
                right_quads << quad;
            }
        // the base position of the window is changed after half of the animation
        if( timeLine.value() < 0.5 )
            {
            if( direction == Left )
                data.quads = left_quads;
            else
                data.quads = right_quads;
            }
        else
            {
            if( direction == Left )
                data.quads = right_quads;
            else
                data.quads = left_quads;
            }

        // paint one part of the thumbnail
        effects->paintWindow( w,
            PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS,
            info->thumbnail, data );

        QRect secondThumbnail;
        
        // paint the second part of the thumbnail:
        // the other window quads are needed and a new rect for transformation has to be calculated
        if( direction == Left )
            {
            if( timeLine.value() < 0.5 )
                {
                data.quads = right_quads;
                secondThumbnail = QRect( frame_area.x() + frame_area.width() -
                    (float)item_max_size.width()*timeLine.value(),
                    frame_area.y(), item_max_size.width(), item_max_size.height());
                }
            else
                {
                data.quads = left_quads;
                secondThumbnail = QRect( frame_area.x() - (float)item_max_size.width()*timeLine.value(),
                    frame_area.y(), item_max_size.width(), item_max_size.height());
                if( right_window )
                    secondThumbnail = QRect( frame_area.x() -
                        (float)item_max_size.width()*(timeLine.value()-0.5),
                        frame_area.y(), item_max_size.width(), item_max_size.height());
                }
            }
        else
            {
            if( timeLine.value() < 0.5 )
                {
                data.quads = left_quads;
                secondThumbnail = QRect( frame_area.x() -
                    (float)item_max_size.width()*(1.0 - timeLine.value()),
                    frame_area.y(), item_max_size.width(), item_max_size.height());
                }
            else
                {
                data.quads = right_quads;
                secondThumbnail = QRect( frame_area.x() + frame_area.width() - 
                    (float)item_max_size.width()*(1.0 - timeLine.value()),
                    frame_area.y(), item_max_size.width(), item_max_size.height());
                }
            }
        setPositionTransformations( data,
            info->thumbnail, w,
            secondThumbnail.adjusted( highlight_margin, highlight_margin, -highlight_margin, -highlight_margin ),
            Qt::KeepAspectRatio );
        effects->paintWindow( w,
            PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS,
            info->thumbnail, data );
        }
    else if( ( windows.size() % 2 == 0 ) && ( w == right_window ) )
        {
        // in case of even number of thumbnails:
        // the window on the right is painted one half on left and on half on the right side
        float animationOffset = 0.0f;
        double splitPoint = w->geometry().width()*0.5;
        if( animation && timeLine.value() <= 0.5 )
            {
            // in case of animation the right window has only to be split during first half of animation
            if( direction == Left )
                {
                splitPoint += w->geometry().width()*timeLine.value();
                animationOffset = -(float)item_max_size.width()*timeLine.value();
                }
            else
                {
                splitPoint -= w->geometry().width()*timeLine.value();
                animationOffset = (float)item_max_size.width()*timeLine.value();
                }
            }
        if( animation && timeLine.value() > 0.5 )
            {
            // at half of animation a different window has become the right window
            // we have to adjust the splitting again
            if( direction == Left )
                {
                splitPoint -= w->geometry().width()*(1.0-timeLine.value());
                animationOffset = (float)item_max_size.width()*(1.0-timeLine.value());
                }
            else
                {
                splitPoint += w->geometry().width()*(1.0-timeLine.value());
                animationOffset = -(float)item_max_size.width()*(1.0-timeLine.value());
                }
            }
        data.quads = data.quads.splitAtX( splitPoint );
        WindowQuadList rightQuads;
        WindowQuadList leftQuads;
        foreach( const WindowQuad &quad, data.quads )
            {
            if( quad.right() <= splitPoint )
                leftQuads << quad;
            else
                rightQuads << quad;
            }

        // left quads are painted on right side of frame
        data.quads = leftQuads;
        effects->drawWindow( w,
            PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS,
            info->thumbnail, data );

        // right quads are painted on left side of frame
        data.quads = rightQuads;
        QRect secondThumbnail;
        secondThumbnail = QRect( frame_area.x() -
            (float)item_max_size.width()*0.5 + animationOffset,
            frame_area.y(), item_max_size.width(), item_max_size.height());
        setPositionTransformations( data,
            info->thumbnail, w,
            secondThumbnail.adjusted( highlight_margin, highlight_margin, -highlight_margin, -highlight_margin ),
            Qt::KeepAspectRatio );
        effects->drawWindow( w,
            PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS,
            info->thumbnail, data );        
        }
    else
        {
        effects->drawWindow( w,
                 PAINT_WINDOW_OPAQUE | PAINT_WINDOW_TRANSFORMED | PAINT_WINDOW_LANCZOS,
                info->thumbnail, data );
        }
    }

void BoxSwitchEffect::paintDesktopThumbnail( int iDesktop )
    {
    ItemInfo *info = desktops.value( iDesktop, 0 );
    if( !info )
        return;

    ScreenPaintData data;
    QRect region;
    QRect r = info->area.adjusted( highlight_margin, highlight_margin, -highlight_margin, -highlight_margin );
    QSize size = QSize( displayWidth(), displayHeight());

    size.scale( r.size(), Qt::KeepAspectRatio );
    data.xScale = size.width() / double( displayWidth());
    data.yScale = size.height() / double( displayHeight());
    int width = int( displayWidth() * data.xScale );
    int height = int( displayHeight() * data.yScale );
    int x = r.x() + ( r.width() - width ) / 2;
    int y = r.y() + ( r.height() - height ) / 2;
    region = QRect( x, y, width, height );
    data.xTranslate = x;
    data.yTranslate = y;

    effects->paintScreen( PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_BACKGROUND_FIRST,
        region, data );
    }

void BoxSwitchEffect::paintWindowIcon( EffectWindow* w )
    {
    ItemInfo *info = windows.value( w, 0 );
    if( !info )
        return;
    // Don't render null icons
    if( w->icon().isNull() )
        {
        return;
        }

    int width = w->icon().width();
    int height = w->icon().height();
    int x = info->area.x() + info->area.width() - width - highlight_margin;
    int y = info->area.y() + info->area.height() - height - highlight_margin;
    if( ( windows.size() % 2 == 0 ) )
        {
        if( w == right_window )
            {
            // in case of right window the icon has to be painted on the left side of the frame
            x = frame_area.x() + info->area.width()*0.5 - width - highlight_margin;
            if( animation )
                {
                if( timeLine.value() <= 0.5 )
                    {
                    if( direction == Left )
                        {
                        x -= info->area.width()*timeLine.value();
                        x = qMax( x, frame_area.x() );
                        }
                    else
                        x += info->area.width()*timeLine.value();
                    }
                else
                    {
                    if( direction == Left )
                        x += info->area.width()*(1.0-timeLine.value());
                    else
                        {
                        x -= windows[ w ]->area.width()*(1.0-timeLine.value());
                        x = qMax( x, frame_area.x() );
                        }
                    }
                }
            }
        }
    else
        {
        // during animation the icon of the edge window has to change position
        if( animation && w == edge_window )
            {
            if( timeLine.value() < 0.5 )
                {
                if( direction == Left )
                    x += info->area.width()*timeLine.value();
                else
                    x -= info->area.width()*timeLine.value();
                }
            else
                {
                if( direction == Left )
                    x -= info->area.width()*(1.0 - timeLine.value());
                else
                    x += info->area.width()*(1.0 - timeLine.value());
                }
            }
        }

    info->iconFrame->setPosition( QPoint( x, y ));
    info->iconFrame->render( infiniteRegion(), 1.0, 0.75 );
    }

void* BoxSwitchEffect::proxy()
    {
    return &mProxy;
    }

void BoxSwitchEffect::activateFromProxy( int mode, bool animate, bool showText, float positioningFactor )
    {
    if( !mActivated )
        {
        mProxyActivated = true;
        mProxyAnimateSwitch = animate;
        mProxyShowText = showText;
        mPositioningFactor = positioningFactor;
        thumbnailFrame->setText(" ");
        if( ( mode == TabBoxWindowsMode ) || ( mode == TabBoxWindowsAlternativeMode ) )
            {
            if( effects->currentTabBoxWindowList().count() > 0 )
                {
                mMode = mode;
                effects->refTabBox();
                highlight_is_set = false;
                animation = false;
                scheduled_directions.clear();
                right_window = 0;
                setActive();
                }
            }
        else if( mode == TabBoxDesktopListMode || mode == TabBoxDesktopMode )
            { // DesktopMode
            if( effects->currentTabBoxDesktopList().count() > 0 )
                {
                mMode = mode;
                painting_desktop = 0;
                effects->refTabBox();
                setActive();
                }
            }
        if( !mActivated )
            mProxyActivated = false;
        }
    }

BoxSwitchEffect::ItemInfo::ItemInfo()
    : iconFrame( NULL )
    {
    }

BoxSwitchEffect::ItemInfo::~ItemInfo()
    {
    delete iconFrame;
    }

} // namespace
