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

 Copyright (C) 2008 Martin Gräßlin <ubuntu@martin-graesslin.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 "coverswitch.h"

#include <kwinconfig.h>
#include <QFont>
#include <klocale.h>
#include <kapplication.h>
#include <kcolorscheme.h>
#include <kconfiggroup.h>

#include <kwinglutils.h>

#include <math.h>

#include <kdebug.h>

#include <GL/gl.h>
#include "../boxswitch/boxswitch_proxy.h"

namespace KWin
{

KWIN_EFFECT( coverswitch, CoverSwitchEffect )
KWIN_EFFECT_SUPPORTED( coverswitch, CoverSwitchEffect::supported() )

CoverSwitchEffect::CoverSwitchEffect()
    : mActivated( 0 )
    , angle( 60.0 )
    , animation( false )
    , start( false )
    , stop( false )
    , stopRequested( false )
    , startRequested( false )
    , zPosition( 900.0 )
    , scaleFactor( 0.0 )
    , direction( Left )
    , selected_window( 0 )
    , captionFrame( EffectFrame::Styled )
    , primaryTabBox( false )
    , secondaryTabBox( false )
    {
    reconfigure( ReconfigureAll );

    // Caption frame
    captionFont.setBold( true );
    captionFont.setPointSize( captionFont.pointSize() * 2 );
    captionFrame.setFont( captionFont );
    }

CoverSwitchEffect::~CoverSwitchEffect()
    {
    }

bool CoverSwitchEffect::supported()
    {
    return effects->compositingType() == OpenGLCompositing;
    }

void CoverSwitchEffect::reconfigure( ReconfigureFlags )
    {
    KConfigGroup conf = effects->effectConfig( "CoverSwitch" );
    animationDuration = animationTime( conf, "Duration", 200 );
    animateSwitch     = conf.readEntry( "AnimateSwitch", true );
    animateStart      = conf.readEntry( "AnimateStart", true );
    animateStop       = conf.readEntry( "AnimateStop", true );
    reflection        = conf.readEntry( "Reflection", true );
    windowTitle       = conf.readEntry( "WindowTitle", true );
    zPosition         = conf.readEntry( "ZPosition", 900.0 );
    thumbnails        = conf.readEntry( "Thumbnails", true );
    dynamicThumbnails = conf.readEntry( "DynamicThumbnails", true );
    thumbnailWindows  = conf.readEntry( "ThumbnailWindows", 8 );
    timeLine.setCurveShape( TimeLine::EaseInOutCurve );
    timeLine.setDuration( animationDuration );
    primaryTabBox = conf.readEntry( "TabBox", false );
    secondaryTabBox = conf.readEntry( "TabBoxAlternative", false );
    QColor tmp        = conf.readEntry( "MirrorFrontColor", QColor(0,0,0) );
    mirrorColor[0][0] = tmp.redF();
    mirrorColor[0][1] = tmp.greenF();
    mirrorColor[0][2] = tmp.blueF();
    mirrorColor[0][3] = 1.0;
    tmp               = conf.readEntry( "MirrorRearColor", QColor(0,0,0) );
    mirrorColor[1][0] = tmp.redF();
    mirrorColor[1][1] = tmp.greenF();
    mirrorColor[1][2] = tmp.blueF();
    mirrorColor[1][3] = -1.0;
    
    }

void CoverSwitchEffect::prePaintScreen( ScreenPrePaintData& data, int time )
    {
    if( mActivated || stop || stopRequested )
        {
        data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS;
        if( animation || start || stop )
            {
            timeLine.addTime( (double)time );
            }
        if( selected_window == NULL )
            abort();
        }
    effects->prePaintScreen(data, time);
    }

void CoverSwitchEffect::paintScreen( int mask, QRegion region, ScreenPaintData& data )
    {
    effects->paintScreen( mask, region, data );

    if( mActivated || stop || stopRequested )
        {
        if( effects->numScreens() > 1 )
            {
            // unfortunatelly we have to change the projection matrix in dual screen mode
            QRect fullRect = effects->clientArea( FullArea, activeScreen, effects->currentDesktop() );
            glMatrixMode( GL_PROJECTION );
            glPushMatrix();
            glLoadIdentity();
            float fovy = 60.0f;
            float aspect = 1.0f;
            float zNear = 0.1f;
            float zFar = 100.0f;
            float ymax = zNear * tan( fovy  * M_PI / 360.0f );
            float ymin = -ymax;
            float xmin =  ymin * aspect;
            float xmax = ymax * aspect;
            float xTranslate = 0.0;
            float yTranslate = 0.0;
            float xminFactor = 1.0;
            float xmaxFactor = 1.0;
            float yminFactor = 1.0;
            float ymaxFactor = 1.0;
            if( area.x() == 0 && area.width() != fullRect.width() )
                {
                // horizontal layout: left screen
                xminFactor = (float)area.width()/(float)fullRect.width();
                xmaxFactor = ((float)fullRect.width()-(float)area.width()*0.5f)/((float)fullRect.width()*0.5f);
                xTranslate = (float)fullRect.width()*0.5f-(float)area.width()*0.5f;
                }
            if( area.x() != 0 && area.width() != fullRect.width() )
                {
                // horizontal layout: right screen
                xminFactor = ((float)fullRect.width()-(float)area.width()*0.5f)/((float)fullRect.width()*0.5f);
                xmaxFactor = (float)area.width()/(float)fullRect.width();
                xTranslate = (float)fullRect.width()*0.5f-(float)area.width()*0.5f;
                }
            if( area.y() == 0 && area.height() != fullRect.height() )
                {
                // vertical layout: top screen
                yminFactor = ((float)fullRect.height()-(float)area.height()*0.5f)/((float)fullRect.height()*0.5f);
                ymaxFactor = (float)area.height()/(float)fullRect.height();
                yTranslate = (float)fullRect.height()*0.5f-(float)area.height()*0.5f;
                }
            if( area.y() != 0 && area.height() != fullRect.height() )
                {
                // vertical layout: bottom screen
                yminFactor = (float)area.height()/(float)fullRect.height();
                ymaxFactor = ((float)fullRect.height()-(float)area.height()*0.5f)/((float)fullRect.height()*0.5f);
                yTranslate = (float)fullRect.height()*0.5f-(float)area.height()*0.5f;
                }
            glFrustum( xmin*xminFactor, xmax*xmaxFactor, ymin*yminFactor, ymax*ymaxFactor, zNear, zFar );
            glMatrixMode( GL_MODELVIEW );
            glPushMatrix();
            glTranslatef( xTranslate, yTranslate, 0.0 );
            }
    
        QList< EffectWindow* > tempList = currentWindowList;
        int index = tempList.indexOf( selected_window );
        if( animation || start || stop )
            {
            if( !start && !stop )
                {
                if( direction == Right )
                    index++;
                else
                    index--;
                if( index < 0 )
                    index = tempList.count() + index;
                if( index >= tempList.count() )
                    index = index % tempList.count();
                }
            foreach( Direction direction, scheduled_directions )
                {
                if( direction == Right )
                    index++;
                else
                    index--;
                if( index < 0 )
                    index = tempList.count() + index;
                if( index >= tempList.count() )
                    index = index % tempList.count();
                }
            }
        int leftIndex = index -1;
        if( leftIndex < 0 )
            leftIndex = tempList.count() -1;
        int rightIndex = index +1;
        if( rightIndex == tempList.count() )
            rightIndex = 0;

        EffectWindow* frontWindow = tempList[ index ];
        leftWindows.clear();
        rightWindows.clear();

        bool evenWindows = ( tempList.count() % 2 == 0 ) ? true : false;
        int leftWindowCount = 0;
        if( evenWindows )
            leftWindowCount = tempList.count()/2 - 1;
        else
            leftWindowCount = ( tempList.count() - 1 )/2;
        for( int i=0; i < leftWindowCount; i++ )
            {
            int tempIndex = ( leftIndex - i );
            if( tempIndex < 0 )
                tempIndex = tempList.count() + tempIndex;
            leftWindows.prepend( tempList[ tempIndex ] );
            }
        int rightWindowCount = 0;
        if( evenWindows )
            rightWindowCount = tempList.count()/2;
        else
            rightWindowCount = ( tempList.count() - 1 )/2;
        for( int i=0; i < rightWindowCount; i++ )
            {
            int tempIndex = ( rightIndex + i ) % tempList.count();
            rightWindows.prepend( tempList[ tempIndex ] );
            }

        if( reflection )
            {
            // restrict painting the reflections to the current screen
            QRegion clip = QRegion( area );
            PaintClipper::push( clip );
            // no reflections during start and stop animation
            if( !start && !stop )
                paintScene( frontWindow, leftWindows, rightWindows, true );
            PaintClipper::pop( clip );
            glEnable( GL_BLEND );
            glBlendFunc( GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA );
            glPolygonMode( GL_FRONT, GL_FILL );
            glPushMatrix();
            QRect fullRect = effects->clientArea( FullArea, activeScreen, effects->currentDesktop() );
            // we can use a huge scale factor (needed to calculate the rearground vertices)
            // as we restrict with a PaintClipper painting on the current screen
            float reflectionScaleFactor = 100000 * tan( 60.0 * M_PI / 360.0f )/area.width();
            if( effects->numScreens() > 1 && area.x() != fullRect.x() )
                {
                // have to change the reflection area in horizontal layout and right screen
                glTranslatef( -area.x(), 0.0, 0.0 );
                }
            glTranslatef( area.x() + area.width()*0.5f, 0.0, 0.0 );
            float vertices[] = {
                -area.width()*0.5f, area.height(), 0.0,
                area.width()*0.5f, area.height(), 0.0,
                (float)area.width()*reflectionScaleFactor, area.height(), -5000,
                -(float)area.width()*reflectionScaleFactor, area.height(), -5000 };
            // foreground
            if( start )
                mirrorColor[0][3] = timeLine.value();
            else if( stop )
                mirrorColor[0][3] = 1.0 - timeLine.value();
            glColor4fv( mirrorColor[0] );
            mirrorColor[0][3] = 1.0;

            int y = 0;
            // have to adjust the y values to fit OpenGL
            // in OpenGL y==0 is at bottom, in Qt at top
            if( effects->numScreens() > 1 )
                {
                QRect fullArea = effects->clientArea( FullArea, 0, 1 );
                if( fullArea.height() != area.height() )
                    {
                    if( area.y() == 0 )
                        y = fullArea.height() - area.height();
                    else
                        y = fullArea.height() - area.y() - area.height();
                    }
                }
            // use scissor to restrict painting of the reflection plane to current screen
            glScissor( area.x(), y, area.width(), area.height() );
            glEnable( GL_SCISSOR_TEST );
            glBegin( GL_POLYGON );
            glVertex3f( vertices[0], vertices[1], vertices[2] );
            glVertex3f( vertices[3], vertices[4], vertices[5] );
            // rearground
            glColor4fv( mirrorColor[1] );
            glVertex3f( vertices[6], vertices[7], vertices[8] );
            glVertex3f( vertices[9], vertices[10], vertices[11] );
            glEnd();
            glDisable( GL_SCISSOR_TEST );

            glPopMatrix();
            glDisable( GL_BLEND );
            }
        paintScene( frontWindow, leftWindows, rightWindows );

        if( effects->numScreens() > 1 )
            {
            glPopMatrix();
            // revert change of projection matrix
            glMatrixMode( GL_PROJECTION );
            glPopMatrix();
            glMatrixMode( GL_MODELVIEW );
            }

        // Render the caption frame
        if (windowTitle)
        {
            double opacity = 1.0;
            if( start )
                opacity = timeLine.value();
            else if( stop )
                opacity = 1.0 - timeLine.value();
            captionFrame.render( region, opacity );
        }

        if( ( thumbnails && (!dynamicThumbnails || 
            (dynamicThumbnails && currentWindowList.size() >= thumbnailWindows)) )
            && !( start || stop ) )
            {
            BoxSwitchEffectProxy *proxy =
                static_cast<BoxSwitchEffectProxy*>( effects->getProxy( "boxswitch" ) );
            if( proxy )
                proxy->paintWindowsBox( region );
            }
        }
    }

void CoverSwitchEffect::postPaintScreen()
    {
    if( ( mActivated && ( animation || start ) ) || stop || stopRequested )
        {
        if( timeLine.value() == 1.0 )
            {
            timeLine.setProgress(0.0);
            if( stop )
                {
                stop = false;
                effects->setActiveFullScreenEffect( 0 );
                foreach( EffectWindow* window, referrencedWindows )
                    {
                    window->unrefWindow();
                    }
                referrencedWindows.clear();
                currentWindowList.clear();
                if( startRequested )
                    {
                    startRequested = false;
                    mActivated = true;
                    effects->refTabBox();
                    currentWindowList = effects->currentTabBoxWindowList();
                    if( animateStart )
                        {
                        start = true;
                        }
                    }
                }
            else if( !scheduled_directions.isEmpty() )
                {
                direction = scheduled_directions.dequeue();
                if( start )
                    {
                    animation = true;
                    start = false;
                    }
                }
            else
                {
                animation = false;
                start = false;
                if( stopRequested )
                    {
                    stopRequested = false;
                    stop = true;
                    }
                }
            }
        effects->addRepaintFull();
        }
    effects->postPaintScreen();
    }

void CoverSwitchEffect::paintScene( EffectWindow* frontWindow, const EffectWindowList& leftWindows,
    const EffectWindowList& rightWindows, bool reflectedWindows )
    {
    // LAYOUT
    // one window in the front. Other windows left and right rotated
    // for odd number of windows: left: (n-1)/2; front: 1; right: (n-1)/2
    // for even number of windows: left: n/2; front: 1; right: n/2 -1
    //
    // ANIMATION
    // forward (alt+tab)
    // all left windows are moved to next position
    // top most left window is rotated and moved to front window position
    // front window is rotated and moved to next right window position
    // right windows are moved to next position
    // last right window becomes totally transparent in half the time
    // appears transparent on left side and becomes totally opaque again
    // backward (alt+shift+tab) same as forward but opposite direction
    int width = area.width();
    int leftWindowCount = leftWindows.count();
    int rightWindowCount = rightWindows.count();
    RotationData rot;
    rot.axis = RotationData::YAxis;


    // Problem during animation: a window which is painted after another window
    // appears in front of the other
    // so during animation the painting order has to be rearreanged
    // paint sequence no animation: left, right, front
    // paint sequence forward animation: right, front, left

    if( !animation )
        {
        paintWindows( leftWindows, true, reflectedWindows );
        paintWindows( rightWindows, false, reflectedWindows );
        paintFrontWindow( frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows );
        }
    else
        {
        if( direction == Right )
            {
            if( timeLine.value() < 0.5 )
                {
                // paint in normal way
                paintWindows( leftWindows, true, reflectedWindows );
                paintWindows( rightWindows, false, reflectedWindows );
                paintFrontWindow( frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows );
                }
            else
                {
                paintWindows( rightWindows, false, reflectedWindows );
                paintFrontWindow( frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows );
                paintWindows( leftWindows, true, reflectedWindows, rightWindows.at( 0 ) );
                }
            }
        else
            {
            paintWindows( leftWindows, true, reflectedWindows );
            if( timeLine.value() < 0.5 )
                {
                paintWindows( rightWindows, false, reflectedWindows );
                paintFrontWindow( frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows );
                }
            else
                {
                EffectWindow* leftWindow;
                if( leftWindowCount > 0)
                    {
                    leftWindow = leftWindows.at( 0 );
                    paintFrontWindow( frontWindow, width, leftWindowCount, rightWindowCount, reflectedWindows );
                    }
                else
                    leftWindow = frontWindow;
                paintWindows( rightWindows, false, reflectedWindows, leftWindow );
                }
            }
        }
    }

void CoverSwitchEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data )
    {
    if( mActivated || stop || stopRequested )
        {
        if( !( mask & PAINT_WINDOW_TRANSFORMED ) && !w->isDesktop() )
            {
            if( ( start || stop ) && w->isDock() )
                {
                data.opacity = 1.0 - timeLine.value();
                if( stop )
                    data.opacity = timeLine.value();
                }
            else
                return;
            }
        }
    if ( ( start || stop ) && (!w->isOnCurrentDesktop() || w->isMinimized() ) )
        {
        if (stop) // Fade out windows not on the current desktop
            data.opacity = (1.0 - timeLine.value());
        else // Fade in Windows from other desktops when animation is started
            data.opacity = timeLine.value();
        }
    effects->paintWindow( w, mask, region, data );
    }

void CoverSwitchEffect::tabBoxAdded( int mode )
    {
    if( effects->activeFullScreenEffect() && effects->activeFullScreenEffect() != this )
        return;
    if( !mActivated )
        {
        // only for windows mode
        if( (( mode == TabBoxWindowsMode && primaryTabBox ) ||
            ( mode == TabBoxWindowsAlternativeMode && secondaryTabBox ))
            && effects->currentTabBoxWindowList().count() > 0 )
            {
            input = effects->createFullScreenInputWindow( this, Qt::ArrowCursor );
            activeScreen = effects->activeScreen();
            BoxSwitchEffectProxy *proxy =
                static_cast<BoxSwitchEffectProxy*>( effects->getProxy( "boxswitch" ) );
            if( proxy )
                proxy->activate( mode, true, false, 0.05f );
            if( !stop && !stopRequested )
                {
                effects->refTabBox();
                effects->setActiveFullScreenEffect( this );
                scheduled_directions.clear();
                selected_window = effects->currentTabBoxWindow();
                currentWindowList = effects->currentTabBoxWindowList();
                direction = Left;
                mActivated = true;
                if( animateStart )
                    {
                    start = true;
                    }

                // Calculation of correct area
                area = effects->clientArea( FullScreenArea, activeScreen, effects->currentDesktop());
                scaleFactor = (zPosition+1100) * 2.0 * tan( 60.0 * M_PI / 360.0f )/displayWidth();
                if( displayWidth()-area.width() != 0 )
                    {
                    // one of the screens is smaller than the other (horizontal)
                    if( area.width() < displayWidth() - area.width() )
                        scaleFactor *= (float)area.width()/(float)(displayWidth()-area.width());
                    else if( area.width() != displayWidth() - area.width() )
                        {
                        // vertical layout with different width
                        // but we don't want to catch screens with same width and different height
                        if( displayHeight() != area.height() )
                            scaleFactor *= (float)area.width()/(float)(displayWidth());
                        }
                    }

                // Setup caption frame geometry
                if (windowTitle)
                {
                    QRect frameRect = QRect( area.width() * 0.25f + area.x(),
                        area.height() * 0.9f + area.y(),
                        area.width() * 0.5f,
                        QFontMetrics( captionFont ).height() );
                    captionFrame.setGeometry( frameRect );
                    captionFrame.setIconSize( QSize( frameRect.height(), frameRect.height() ));
                    // And initial contents
                    captionFrame.setText( selected_window->caption() );
                    captionFrame.setIcon( selected_window->icon() );
                }

                effects->addRepaintFull();
                }
            else
                {
                startRequested = true;
                }
            }
        }
    }

void CoverSwitchEffect::tabBoxClosed()
    {
    if( mActivated )
        {
        if( animateStop )
            {
            if( !animation && !start )
                {
                stop = true;
                }
            else if( start && scheduled_directions.isEmpty() )
                {
                start = false;
                stop = true;
                timeLine.setProgress( 1.0 - timeLine.value() );
                }
            else
                {
                stopRequested = true;
                }
            }
        else
            effects->setActiveFullScreenEffect( 0 );
        mActivated = false;
        effects->unrefTabBox();
        effects->destroyInputWindow( input );
        effects->addRepaintFull();
        }
    }

void CoverSwitchEffect::tabBoxUpdated()
    {
    if( mActivated )
        {
        if( animateSwitch && currentWindowList.count() > 1)
            {
            // determine the switch direction
            if( selected_window != effects->currentTabBoxWindow() )
                {
                if( selected_window != NULL )
                    {
                    int old_index = currentWindowList.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( effects->currentTabBoxWindowList().count() == 2 )
                        {
                        new_direction = Left;
                        distance = 1;
                        }
                    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 && !start )
                            {
                            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();
                            }
                        }
                    }
                selected_window = effects->currentTabBoxWindow();
                currentWindowList = effects->currentTabBoxWindowList();
                captionFrame.setText( selected_window->caption() );
                captionFrame.setIcon( selected_window->icon() );
                }
            }
        effects->addRepaintFull();
        }
    }

void CoverSwitchEffect::paintWindowCover( EffectWindow* w, bool reflectedWindow, WindowPaintData& data )
    {
    QRect windowRect = w->geometry();
    data.yTranslate = area.height() - windowRect.y() - windowRect.height();
    data.zTranslate = -zPosition;
    if( start )
        {
        if( w->isMinimized() )
            {
            data.opacity *= timeLine.value();
            }
        else
            {
            data.xTranslate *= timeLine.value();
            data.yTranslate *= timeLine.value();
            if( effects->numScreens() > 1)
                {
                QRect clientRect = effects->clientArea( FullScreenArea, w->screen(), effects->currentDesktop() );
                QRect fullRect = effects->clientArea( FullArea, activeScreen, effects->currentDesktop() );
                if( w->screen() == activeScreen )
                    {
                    if( clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x() )
                        {
                        data.xTranslate -= clientRect.x()*(1.0f-timeLine.value());
                        }
                    if( clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y() )
                        {
                        data.yTranslate -= clientRect.y()*(1.0f-timeLine.value());
                        }
                    }
                else
                    {
                    if( clientRect.width() != fullRect.width() && clientRect.x() < area.x())
                        {
                        data.xTranslate -= clientRect.width()*(1.0f-timeLine.value());
                        }
                    if( clientRect.height() != fullRect.height() && clientRect.y() < area.y() )
                        {
                        data.yTranslate -= clientRect.height()*(1.0f-timeLine.value());
                        }
                    }
                }
            data.zTranslate *= timeLine.value();
            if( data.rotation )
                data.rotation->angle *= timeLine.value();
            }
        }
    if( stop )
        {
        if( w->isMinimized() && w != effects->activeWindow() )
            {
            data.opacity *= (1.0 - timeLine.value());
            }
        else
            {
            data.xTranslate *= (1.0 - timeLine.value());
            data.yTranslate *= (1.0 - timeLine.value());
            if( effects->numScreens() > 1)
                {
                QRect clientRect = effects->clientArea( FullScreenArea, w->screen(), effects->currentDesktop() );
                QRect rect = effects->clientArea( FullScreenArea, activeScreen, effects->currentDesktop() );
                QRect fullRect = effects->clientArea( FullArea, activeScreen, effects->currentDesktop() );
                if( w->screen() == activeScreen )
                    {
                    if( clientRect.width() != fullRect.width() && clientRect.x() != fullRect.x() )
                        {
                        data.xTranslate -= clientRect.x()*timeLine.value();
                        }
                    if( clientRect.height() != fullRect.height() && clientRect.y() != fullRect.y() )
                        {
                        data.yTranslate -= clientRect.y()*timeLine.value();
                        }
                    }
                else
                    {
                    if( clientRect.width() != fullRect.width() && clientRect.x() < rect.x())
                        {
                        data.xTranslate -= clientRect.width()*timeLine.value();
                        }
                    if( clientRect.height() != fullRect.height() && clientRect.y() < area.y() )
                        {
                        data.yTranslate -= clientRect.height()*timeLine.value();
                        }
                    }
                }
            data.zTranslate *= (1.0 - timeLine.value());
            if( data.rotation )
                data.rotation->angle *= (1.0 - timeLine.value());
            }
        }

    QRect thumbnail = infiniteRegion();

    if( reflectedWindow )
        {
        glPushMatrix();
        glScalef( 1.0, -1.0, 1.0 );
        data.yTranslate = - area.height() - windowRect.y() - windowRect.height();
        effects->paintWindow( w,
            PAINT_WINDOW_TRANSFORMED,
            infiniteRegion(), data );
        glPopMatrix();
        }
    else
        {
        effects->paintWindow( w,
            PAINT_WINDOW_TRANSFORMED,
            infiniteRegion(), data );
        }
    }

void CoverSwitchEffect::paintFrontWindow( EffectWindow* frontWindow, int width, int leftWindows, int rightWindows, bool reflectedWindow )
    {
    if( frontWindow == NULL )
        return;
    float distance = 0.0;
    bool specialHandlingForward = false;
    WindowPaintData data( frontWindow );
    data.xTranslate = area.width()*0.5 - frontWindow->geometry().x() - frontWindow->geometry().width()*0.5;
    if( leftWindows == 0 )
        {
        leftWindows = 1;
        if( !start && !stop )
            specialHandlingForward = true;
        }
    if( rightWindows == 0 )
        {
        rightWindows = 1;
        }
    if( animation )
        {
        if( direction == Right )
            {
            // move to right
            distance = -frontWindow->geometry().width()*0.5f + area.width()*0.5f +
                (((float)displayWidth()*0.5*scaleFactor)-(float)area.width()*0.5f)/rightWindows;
            data.xTranslate += distance * timeLine.value();
            RotationData rot;
            rot.axis = RotationData::YAxis;
            rot.angle = -angle*timeLine.value();
            rot.xRotationPoint = frontWindow->geometry().width();
            data.rotation = &rot;
            }
        else
            {
            // move to left
            distance = frontWindow->geometry().width()*0.5f - area.width()*0.5f +
                ((float)width*0.5f-((float)displayWidth()*0.5*scaleFactor))/leftWindows;
            float factor = 1.0;
            if( specialHandlingForward )
                factor = 2.0;
            data.xTranslate += distance * timeLine.value() * factor;
            RotationData rot;
            rot.axis = RotationData::YAxis;
            rot.angle = angle*timeLine.value();
            data.rotation = &rot;
            }
        }
    if( specialHandlingForward )
        {
        data.opacity *= (1.0 - timeLine.value() * 2.0);
        paintWindowCover( frontWindow, reflectedWindow, data );
        }
    else
        paintWindowCover( frontWindow, reflectedWindow, data );
    }

void CoverSwitchEffect::paintWindows( const EffectWindowList& windows, bool left, bool reflectedWindows, EffectWindow* additionalWindow )
    {
    int width = area.width();
    int windowCount = windows.count();
    EffectWindow* window;
    
    int rotateFactor = 1;
    if( !left )
        {
        rotateFactor = -1;
        }

    float xTranslate = -((float)(width)*0.5f-((float)displayWidth()*0.5*scaleFactor));
    if( !left )
        xTranslate = ((float)displayWidth()*0.5*scaleFactor)-(float)width*0.5f;
    // handling for additional window from other side
    // has to appear on this side after half of the time
    if( animation && timeLine.value() >= 0.5 && additionalWindow != NULL )
        {
        RotationData rot;
        rot.axis = RotationData::YAxis;
        rot.angle = angle;
        rot.angle = angle*rotateFactor;
        WindowPaintData data( additionalWindow );
        if( left )
            data.xTranslate += -xTranslate - additionalWindow->geometry().x();
        else
            {
            data.xTranslate += xTranslate + area.width() - 
                additionalWindow->geometry().x() - additionalWindow->geometry().width();
            rot.xRotationPoint = additionalWindow->geometry().width();
            }
        data.rotation = &rot;
        data.opacity *= ( timeLine.value() - 0.5 ) * 2.0;
        paintWindowCover( additionalWindow, reflectedWindows, data );
        }
    RotationData rot;
    rot.axis = RotationData::YAxis;
    // normal behaviour
    for( int i=0; i < windows.count(); i++)
        {
        window = windows.at( i );
        if( window == NULL || window->isDeleted() )
            {
            continue;
            }
        WindowPaintData data( window );
        rot.angle = angle;
        if( left )
            data.xTranslate += -xTranslate + xTranslate*i/windowCount - window->geometry().x();
        else
            data.xTranslate += xTranslate + width - xTranslate*i/windowCount - window->geometry().x() - window->geometry().width();
        if( animation )
            {
            if( direction == Right )
                {
                if( ( i == windowCount - 1 ) && left )
                    {
                    // right most window on left side -> move to front
                    // have to move one window distance plus half the difference between the window and the desktop size
                    data.xTranslate += (xTranslate/windowCount + (width - window->geometry().width())*0.5f)*timeLine.value();
                    rot.angle = ( angle - angle * timeLine.value() );
                    }
                // right most window does not have to be moved
                else if( !left && ( i == 0 ) ); // do nothing
                else
                    {
                    // all other windows - move to next position
                    data.xTranslate += xTranslate/windowCount * timeLine.value();
                    }
                }
            else
                {
                if( ( i == windowCount - 1 ) && !left )
                    {
                    // left most window on right side -> move to front
                    data.xTranslate -= (xTranslate/windowCount + (width - window->geometry().width())*0.5f)*timeLine.value();
                    rot.angle = ( angle - angle * timeLine.value() );
                    }
                // left most window does not have to be moved
                else if( i==0 && left); // do nothing
                else
                    {
                    // all other windows - move to next position
                    data.xTranslate -= xTranslate/windowCount * timeLine.value();
                    }
                }
            }
        if( left )
            rot.xRotationPoint = 0.0;
        else
            rot.xRotationPoint = window->geometry().width();
        rot.angle *= rotateFactor;
        data.rotation = &rot;
        // make window most to edge transparent if animation
        if( animation && i == 0 && ( ( direction == Left && left ) || ( direction == Right && !left ) ) )
            {
            // only for the first half of the animation
            if( timeLine.value() < 0.5 )
                {
                data.opacity *= (1.0 - timeLine.value() * 2.0);
                paintWindowCover( window, reflectedWindows, data );
                }
            }
        else
            {
            paintWindowCover( window, reflectedWindows, data );
            }
        }
    }

void CoverSwitchEffect::windowInputMouseEvent( Window w, QEvent* e )
    {
    assert( w == input );
    Q_UNUSED( w );
    if( e->type() != QEvent::MouseButtonPress )
        return;
    // we don't want click events during animations
    if( animation )
        return;
    QPoint pos = static_cast< QMouseEvent* >( e )->pos();

    // determine if a window has been clicked
    // not interested in events above a fullscreen window (ignoring panel size)
    if( pos.y() < (area.height()*scaleFactor - area.height())*0.5f*(1.0f/scaleFactor) )
        return;

    // if there is no selected window (that is no window at all) we cannot click it
    if( !selected_window )
        return;

    if( pos.x() < (area.width()*scaleFactor - selected_window->width())*0.5f*(1.0f/scaleFactor) )
        {
        float availableSize = (area.width()*scaleFactor - area.width())*0.5f*(1.0f/scaleFactor);
        for( int i=0;i<leftWindows.count();i++ )
            {
            int windowPos = availableSize/leftWindows.count()*i;
            if( pos.x() < windowPos )
                continue;
            if( i+1 < leftWindows.count() )
                {
                if( pos.x() > availableSize/leftWindows.count()*(i+1) )
                    continue;
                }
            
            effects->setTabBoxWindow( leftWindows[i] );
            return;
            }
        }

    if( pos.x() > area.width() - (area.width()*scaleFactor - selected_window->width())*0.5f*(1.0f/scaleFactor) )
        {
        float availableSize = (area.width()*scaleFactor - area.width())*0.5f*(1.0f/scaleFactor);
        for( int i=0;i<rightWindows.count();i++ )
            {
            int windowPos = area.width() - availableSize/rightWindows.count()*i;
            if( pos.x() > windowPos )
                continue;
            if( i+1 < rightWindows.count() )
                {
                if( pos.x() < area.width() - availableSize/rightWindows.count()*(i+1) )
                    continue;
                }
            
            effects->setTabBoxWindow( rightWindows[i] );
            return;
            }
        }
    }

void CoverSwitchEffect::abort()
    {
    // it's possible that abort is called after tabbox has been closed
    // in this case the cleanup is already done (see bug 207554)
    if( mActivated )
        {
        effects->unrefTabBox();
        effects->destroyInputWindow( input );
        }
    effects->setActiveFullScreenEffect( 0 );
    mActivated = false;
    stop = false;
    stopRequested = false;
    effects->addRepaintFull();
    captionFrame.free();
    }

void CoverSwitchEffect::windowClosed( EffectWindow* c )
    {
    // if the list is not empty, the effect is active
    if( !currentWindowList.isEmpty() )
        {
        c->refWindow();
        referrencedWindows.append( c );
        currentWindowList.removeAll( c );
        leftWindows.removeAll( c );
        rightWindows.removeAll( c );
        }
    }

} // namespace
