/********************************************************************
 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>

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(false)
    , 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);
    connect(effects, SIGNAL(windowClosed(KWin::EffectWindow*)), this, SLOT(slotWindowClosed(KWin::EffectWindow*)));
    connect(effects, SIGNAL(tabBoxAdded(int)), this, SLOT(slotTabBoxAdded(int)));
    connect(effects, SIGNAL(tabBoxClosed()), this, SLOT(slotTabBoxClosed()));
    connect(effects, SIGNAL(tabBoxUpdated()), this, SLOT(slotTabBoxUpdated()));
    connect(effects, SIGNAL(windowGeometryShapeChanged(KWin::EffectWindow*,QRect)), this, SLOT(slotWindowGeometryShapeChanged(KWin::EffectWindow*,QRect)));
    connect(effects, SIGNAL(windowDamaged(KWin::EffectWindow*,QRect)), this, SLOT(slotWindowDamaged(KWin::EffectWindow*,QRect)));
}

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(QTimeLine::EaseInOutCurve);
    timeLine.setDuration(animationTime(150));
    timeLine.setCurveShape(QTimeLine::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);

    // the tabbox merely exists as CoverSwitch sidearm, force it to be not the tabbox
    primaryTabBox = false;
    secondaryTabBox = false;
}

void BoxSwitchEffect::prePaintWindow(EffectWindow* w, WindowPrePaintData& data, int time)
{
    if (activeTimeLine.currentValue() != 0.0 && !mProxyActivated) {
        if (mMode == TabBoxWindowsMode || mMode == TabBoxWindowsAlternativeMode) {
            if (windows.contains(w)) {
                if (w == selected_window)
                    w->enablePainting(EffectWindow::PAINT_DISABLED_BY_TAB_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.setCurrentTime(activeTimeLine.currentTime() + time);
    else {
        activeTimeLine.setCurrentTime(activeTimeLine.currentTime() - time);
        if (activeTimeLine.currentValue() == 0.0) {
            // No longer need the window data
            qDeleteAll(windows);
            windows.clear();
        }
    }
    if (mActivated && animation) {
        timeLine.setCurrentTime(timeLine.currentTime() + 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.currentValue() != 1.0)
        effects->addRepaintFull();
    if (!mActivated && activeTimeLine.currentValue() != 0.0)
        effects->addRepaintFull();
    if (mActivated && animation) {
        if (timeLine.currentValue() == 1.0) {
            timeLine.setCurrentTime(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.currentValue() != 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.currentValue() * bg_opacity;
            else
                data.opacity *= 1.0 - activeTimeLine.currentValue() * (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::slotWindowDamaged(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);
            }
        }
#ifdef KWIN_HAVE_OPENGLES
        // without full repaints, blur effect flickers on GLES, see BUG 272688
        effects->addRepaintFull();
#endif
    }
}

void BoxSwitchEffect::slotWindowGeometryShapeChanged(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::slotTabBoxAdded(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::slotTabBoxClosed()
{
    if (mActivated)
        setInactive();
}

void BoxSwitchEffect::slotTabBoxUpdated()
{
    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::slotWindowClosed(EffectWindow* w)
{
    if (!mActivated) {
        return;
    }
    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)) {
            if (original_windows.isEmpty()) {
                // can happen when last window is closed.
                return;
            }
            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.currentValue() < 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.currentValue() >= 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.currentValue());
                else
                    offset = -(float)item_max_size.width() * (1.0 - timeLine.currentValue());
            }
            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.currentValue() < 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.currentValue();
        } else {
            splitPoint = w->geometry().width() - w->geometry().width() * timeLine.currentValue();
        }
        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.currentValue() < 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,
                             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.currentValue() < 0.5) {
                data.quads = right_quads;
                secondThumbnail = QRect(frame_area.x() + frame_area.width() -
                                        (float)item_max_size.width() * timeLine.currentValue(),
                                        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.currentValue(),
                                        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.currentValue() - 0.5),
                                            frame_area.y(), item_max_size.width(), item_max_size.height());
            }
        } else {
            if (timeLine.currentValue() < 0.5) {
                data.quads = left_quads;
                secondThumbnail = QRect(frame_area.x() -
                                        (float)item_max_size.width() * (1.0 - timeLine.currentValue()),
                                        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.currentValue()),
                                        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,
                             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.currentValue() <= 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.currentValue();
                animationOffset = -(float)item_max_size.width() * timeLine.currentValue();
            } else {
                splitPoint -= w->geometry().width() * timeLine.currentValue();
                animationOffset = (float)item_max_size.width() * timeLine.currentValue();
            }
        }
        if (animation && timeLine.currentValue() > 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.currentValue());
                animationOffset = (float)item_max_size.width() * (1.0 - timeLine.currentValue());
            } else {
                splitPoint += w->geometry().width() * (1.0 - timeLine.currentValue());
                animationOffset = -(float)item_max_size.width() * (1.0 - timeLine.currentValue());
            }
        }
        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,
                            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,
                            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.currentValue() <= 0.5) {
                    if (direction == Left) {
                        x -= info->area.width() * timeLine.currentValue();
                        x = qMax(x, frame_area.x());
                    } else
                        x += info->area.width() * timeLine.currentValue();
                } else {
                    if (direction == Left)
                        x += info->area.width() * (1.0 - timeLine.currentValue());
                    else {
                        x -= info->area.width() * (1.0 - timeLine.currentValue());
                        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.currentValue() < 0.5) {
                if (direction == Left)
                    x += info->area.width() * timeLine.currentValue();
                else
                    x -= info->area.width() * timeLine.currentValue();
            } else {
                if (direction == Left)
                    x -= info->area.width() * (1.0 - timeLine.currentValue());
                else
                    x += info->area.width() * (1.0 - timeLine.currentValue());
            }
        }
    }

    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;
    }
}

bool BoxSwitchEffect::isActive() const
{
    return mActivated;
}

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

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

} // namespace
