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

    SPDX-FileCopyrightText: 2009 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

// own
#include "tabboxhandler.h"
#include <config-kwin.h>
#include <kwinglobals.h>
#include "utils/xcbutils.h"
// tabbox
#include "clientmodel.h"
#include "desktopmodel.h"
#include "scripting/scripting.h"
#include "switcheritem.h"
#include "tabbox_logging.h"
#include "tabbox.h"
// Qt
#include <QKeyEvent>
#include <QStandardPaths>
#include <QTimer>
#include <QQmlContext>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <qpa/qwindowsysteminterface.h>
// KDE
#include <KLocalizedString>
#include <KProcess>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
#include <KWindowEffects>

namespace KWin
{
namespace TabBox
{

class TabBoxHandlerPrivate
{
public:
    TabBoxHandlerPrivate(TabBoxHandler *q);

    ~TabBoxHandlerPrivate();

    /**
     * Updates the current highlight window state
     */
    void updateHighlightWindows();
    /**
     * Ends window highlighting
     */
    void endHighlightWindows(bool abort = false);

    void show(enum ShowMode mode);
    QQuickWindow *window() const;
    SwitcherItem *switcherItem() const;
    SwitcherItem *fullScreenItem() const;

    ClientModel* clientModel() const;
    DesktopModel* desktopModel() const;

    TabBoxHandler *q; // public pointer
    // members
    TabBoxConfig config;
    QScopedPointer<QQmlContext> m_qmlContext;
    QScopedPointer<QQmlComponent> m_qmlComponent;
    QScopedPointer<QQmlComponent> m_qmlComponentFullScreen;
    QObject *m_mainItem;
    QObject *m_fullScreenItem;
    QMap<QString, QObject*> m_clientTabBoxes;
    QMap<QString, QObject*> m_clientFulleScreenBoxes;
    QMap<QString, QObject*> m_desktopTabBoxes;
    ClientModel* m_clientModel;
    DesktopModel* m_desktopModel;
    QModelIndex index;
    /**
     * Indicates if the tabbox is shown.
     */
    bool isShown;
    TabBoxClient *lastRaisedClient, *lastRaisedClientSucc;
    int wheelAngleDelta = 0;
    ShowMode m_switcherMode;

private:
    QObject *createSwitcherItem(bool desktopMode);
    QObject *createFulleScreenItem(bool desktopMode);
};

TabBoxHandlerPrivate::TabBoxHandlerPrivate(TabBoxHandler *q)
    : m_qmlContext()
    , m_qmlComponent()
    , m_qmlComponentFullScreen()
    , m_mainItem(nullptr)
    , m_fullScreenItem(nullptr)
{
    this->q = q;
    isShown = false;
    lastRaisedClient = nullptr;
    lastRaisedClientSucc = nullptr;
    config = TabBoxConfig();
    m_clientModel = new ClientModel(q);
    m_desktopModel = new DesktopModel(q);
}

TabBoxHandlerPrivate::~TabBoxHandlerPrivate()
{
    for (auto it = m_clientTabBoxes.constBegin(); it != m_clientTabBoxes.constEnd(); ++it) {
        delete it.value();
    }
    for (auto it = m_clientFulleScreenBoxes.constBegin(); it != m_clientFulleScreenBoxes.constEnd(); ++it) {
        delete it.value();
    }
    for (auto it = m_desktopTabBoxes.constBegin(); it != m_desktopTabBoxes.constEnd(); ++it) {
        delete it.value();
    }
}

QQuickWindow *TabBoxHandlerPrivate::window() const
{
    switch (m_switcherMode) {
    case TabBoxShowMode:
        if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem)) {
            return w;
        }
        return m_mainItem->findChild<QQuickWindow*>();
        break;
    case MaximizeShowMode:
        if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_fullScreenItem)) {
            return w;
        }
        return m_fullScreenItem->findChild<QQuickWindow*>();
        break;
    default :
        return nullptr;
        break;
    }
}

#ifndef KWIN_UNIT_TEST
SwitcherItem *TabBoxHandlerPrivate::switcherItem() const
{
    if (!m_mainItem) {
        return nullptr;
    }
    if (SwitcherItem *i = qobject_cast<SwitcherItem*>(m_mainItem)) {
        return i;
    } else if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem)) {
        return w->contentItem()->findChild<SwitcherItem*>();
    }
    return m_mainItem->findChild<SwitcherItem*>();
}
SwitcherItem *TabBoxHandlerPrivate::fullScreenItem() const
{
    if (!m_fullScreenItem) {
        return nullptr;
    }
    if (SwitcherItem *i = qobject_cast<SwitcherItem*>(m_fullScreenItem)) {
        return i;
    } else if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_fullScreenItem)) {
        return w->contentItem()->findChild<SwitcherItem*>();
    }
    return m_fullScreenItem->findChild<SwitcherItem*>();
}
#endif

ClientModel* TabBoxHandlerPrivate::clientModel() const
{
    return m_clientModel;
}

DesktopModel* TabBoxHandlerPrivate::desktopModel() const
{
    return m_desktopModel;
}

void TabBoxHandlerPrivate::updateHighlightWindows()
{
    if (!isShown || config.tabBoxMode() != TabBoxConfig::ClientTabBox)
        return;
    if (m_switcherMode == TabBoxShowMode)
        return;

    TabBoxClient *currentClient = q->client(index);
    QWindow *w = window();

    if (q->isKWinCompositing()) {
        if (lastRaisedClient)
            q->elevateClient(lastRaisedClient, w, false);
        lastRaisedClient = currentClient;
        if (currentClient)
            q->elevateClient(currentClient, w, true);
    } else {
        if (lastRaisedClient) {
            q->shadeClient(lastRaisedClient, true);
            if (lastRaisedClientSucc)
                q->restack(lastRaisedClient, lastRaisedClientSucc);
            // TODO lastRaisedClient->setMinimized( lastRaisedClientWasMinimized );
        }

        lastRaisedClient = currentClient;
        if (lastRaisedClient) {
            q->shadeClient(lastRaisedClient, false);
            // TODO if ( (lastRaisedClientWasMinimized = lastRaisedClient->isMinimized()) )
            //         lastRaisedClient->setMinimized( false );
            TabBoxClientList order = q->stackingOrder();
            int succIdx = order.count() + 1;
            for (int i=0; i<order.count(); ++i) {
                if (order.at(i).toStrongRef() == lastRaisedClient) {
                    succIdx = i + 1;
                    break;
                }
            }
            lastRaisedClientSucc = (succIdx < order.count()) ? order.at(succIdx).toStrongRef().data() : nullptr;
            q->raiseClient(lastRaisedClient);
        }
    }

    if (config.isShowTabBox() && w) {
        q->highlightWindows(currentClient, w);
    } else {
        q->highlightWindows(currentClient);
    }
}

void TabBoxHandlerPrivate::endHighlightWindows(bool abort)
{
    TabBoxClient *currentClient = q->client(index);
    if (config.isHighlightWindows() && q->isKWinCompositing()) {
        const auto stackingOrder = q->stackingOrder();
        for (const QWeakPointer<TabBoxClient> &clientPointer : stackingOrder) {
            if (QSharedPointer<TabBoxClient> client = clientPointer.toStrongRef()) {
                if (client != currentClient) { // to not mess up with wanted ShadeActive/ShadeHover state
                    q->shadeClient(client.data(), true);
                }
            }
        }
    }
    QWindow *w = window();
    if (currentClient)
        q->elevateClient(currentClient, w, false);
    if (abort && lastRaisedClient && lastRaisedClientSucc)
        q->restack(lastRaisedClient, lastRaisedClientSucc);
    lastRaisedClient = nullptr;
    lastRaisedClientSucc = nullptr;
    // highlight windows
    q->highlightWindows();
}

#ifndef KWIN_UNIT_TEST
QObject *TabBoxHandlerPrivate::createSwitcherItem(bool desktopMode)
{
    // first try look'n'feel package
    QString file = QStandardPaths::locate(
        QStandardPaths::GenericDataLocation,
        QStringLiteral("plasma/look-and-feel/%1/contents/%2")
            .arg(config.layoutName(),
                 desktopMode ? QStringLiteral("desktopswitcher/DesktopSwitcher.qml") : QStringLiteral("windowswitcher/WindowSwitcher.qml")));
    if (file.isNull()) {
        const QString folderName = QLatin1String(KWIN_NAME) + (desktopMode ? QLatin1String("/desktoptabbox/") : QLatin1String("/tabbox/"));
        auto findSwitcher = [this, desktopMode, folderName] {
            const QString type = desktopMode ? QStringLiteral("KWin/DesktopSwitcher") : QStringLiteral("KWin/WindowSwitcher");
            auto offers = KPackage::PackageLoader::self()->findPackages(type,  folderName,
                [this] (const KPluginMetaData &data) {
                    return data.pluginId().compare(config.layoutName(), Qt::CaseInsensitive) == 0;
                }
            );
            if (offers.isEmpty()) {
                // load default
                offers = KPackage::PackageLoader::self()->findPackages(type,  folderName,
                    [] (const KPluginMetaData &data) {
                        return data.pluginId().compare(QStringLiteral("informative"), Qt::CaseInsensitive) == 0;
                    }
                );
                if (offers.isEmpty()) {
                    qCDebug(KWIN_TABBOX) << "could not find default window switcher layout";
                    return KPluginMetaData();
                }
            }
            return offers.first();
        };
        auto service = findSwitcher();
        if (!service.isValid()) {
            return nullptr;
        }
        if (service.value(QStringLiteral("X-Plasma-API")) != QLatin1String("declarativeappletscript")) {
            qCDebug(KWIN_TABBOX) << "Window Switcher Layout is no declarativeappletscript";
            return nullptr;
        }
        auto findScriptFile = [service, folderName] {
            const QString pluginName = service.pluginId();
            const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript"));
            return QStandardPaths::locate(QStandardPaths::GenericDataLocation, folderName + pluginName + QLatin1String("/contents/") + scriptName);
        };
        file = findScriptFile();
    }
    if (file.isNull()) {
        qCDebug(KWIN_TABBOX) << "Could not find QML file for window switcher";
        return nullptr;
    }
    m_qmlComponent->loadUrl(QUrl::fromLocalFile(file));
    if (m_qmlComponent->isError()) {
        qCDebug(KWIN_TABBOX) << "Component failed to load: " << m_qmlComponent->errors();
        QStringList args;
        args << QStringLiteral("--passivepopup") << i18n("The Window Switcher installation is broken, resources are missing.\n"
                                            "Contact your distribution about this.") << QStringLiteral("20");
        KProcess::startDetached(QStringLiteral("kdialog"), args);
    } else {
        QObject *object = m_qmlComponent->create(m_qmlContext.data());
        if (desktopMode) {
            m_desktopTabBoxes.insert(config.layoutName(), object);
        } else {
            m_clientTabBoxes.insert(config.layoutName(), object);
        }
        return object;
    }
    return nullptr;
}

QObject *TabBoxHandlerPrivate::createFulleScreenItem(bool desktopMode)
{
    // first try look'n'feel package
    QString file = QStandardPaths::locate(
        QStandardPaths::GenericDataLocation,
        QStringLiteral("plasma/look-and-feel/%1/contents/%2")
            .arg(config.layoutName(),
                 desktopMode ? QStringLiteral("desktopswitcher/DesktopSwitcher.qml") : QStringLiteral("windowswitcher/WindowSwitcher.qml")));
    if (file.isNull()) {
        const QString folderName = QLatin1String(KWIN_NAME) + (desktopMode ? QLatin1String("/desktoptabbox/") : QLatin1String("/fullscreentabbox/"));
        auto findSwitcher = [this, desktopMode, folderName] {
            const QString type = desktopMode ? QStringLiteral("KWin/DesktopSwitcher") : QStringLiteral("KWin/WindowSwitcher");
            auto offers = KPackage::PackageLoader::self()->findPackages(type,  folderName,
                [this] (const KPluginMetaData &data) {
                    return data.pluginId().compare(config.layoutName(), Qt::CaseInsensitive) == 0;
                }
            );
            if (offers.isEmpty()) {
                // load default
                offers = KPackage::PackageLoader::self()->findPackages(type,  folderName,
                    [] (const KPluginMetaData &data) {
                        return data.pluginId().compare(QStringLiteral("informative"), Qt::CaseInsensitive) == 0;
                    }
                );
                if (offers.isEmpty()) {
                    qCDebug(KWIN_TABBOX) << "could not find default window switcher layout";
                    return KPluginMetaData();
                }
            }
            return offers.first();
        };
        auto service = findSwitcher();
        if (!service.isValid()) {
            return nullptr;
        }
        if (service.value(QStringLiteral("X-Plasma-API")) != QLatin1String("declarativeappletscript")) {
            qCDebug(KWIN_TABBOX) << "Window Switcher Layout is no declarativeappletscript";
            return nullptr;
        }
        auto findScriptFile = [service, folderName] {
            const QString pluginName = service.pluginId();
            const QString scriptName = service.value(QStringLiteral("X-Plasma-MainScript"));
            return QStandardPaths::locate(QStandardPaths::GenericDataLocation, folderName + pluginName + QLatin1String("/contents/") + scriptName);
        };
        file = findScriptFile();
    }
    if (file.isNull()) {
        qCDebug(KWIN_TABBOX) << "Could not find QML file for window switcher";
        return nullptr;
    }
    m_qmlComponentFullScreen->loadUrl(QUrl::fromLocalFile(file));
    if (m_qmlComponentFullScreen->isError()) {
        qCDebug(KWIN_TABBOX) << "Component failed to load: " << m_qmlComponentFullScreen->errors();
        QStringList args;
        args << QStringLiteral("--passivepopup") << i18n("The Window Switcher installation is broken, resources are missing.\n"
                                            "Contact your distribution about this.") << QStringLiteral("20");
        KProcess::startDetached(QStringLiteral("kdialog"), args);
    } else {
        QObject *object = m_qmlComponentFullScreen->create(m_qmlContext.data());
        if (!desktopMode) {
            m_clientFulleScreenBoxes.insert(config.layoutName(), object);
        }
        return object;
    }
    return nullptr;
}
#endif

void TabBoxHandlerPrivate::show(enum ShowMode mode)
{
#ifndef KWIN_UNIT_TEST
    if (m_qmlContext.isNull()) {
        qmlRegisterType<SwitcherItem>("org.kde.kwin", 2, 0, "Switcher");
        qmlRegisterType<SwitcherItem>("org.kde.kwin", 3, 0, "TabBoxSwitcher");
        m_qmlContext.reset(new QQmlContext(Scripting::self()->qmlEngine()));
    }
    if (m_qmlComponent.isNull()) {
        m_qmlComponent.reset(new QQmlComponent(Scripting::self()->qmlEngine()));
    }
    if (m_qmlComponentFullScreen.isNull()) {
        m_qmlComponentFullScreen.reset(new QQmlComponent(Scripting::self()->qmlEngine()));
    }
    const bool desktopMode = (config.tabBoxMode() == TabBoxConfig::DesktopTabBox);
    auto findMainItem = [this](const QMap<QString, QObject *> &tabBoxes) -> QObject* {
        auto it = tabBoxes.constFind(config.layoutName());
        if (it != tabBoxes.constEnd()) {
            return it.value();
        }
        return nullptr;
    };
    m_mainItem = nullptr;
    m_mainItem = desktopMode ? findMainItem(m_desktopTabBoxes) : findMainItem(m_clientTabBoxes);
    m_fullScreenItem = findMainItem(m_clientFulleScreenBoxes);

    m_switcherMode = mode;
    SwitcherItem *item;
    switch (mode) {
    default:
    case TabBoxShowMode:
        if (!m_mainItem) {
            m_mainItem = createSwitcherItem(desktopMode);
            if (!m_mainItem)
                return;
        }
        item = switcherItem();
        break;
    case MaximizeShowMode:
        if (!m_fullScreenItem) {
            m_fullScreenItem = createFulleScreenItem(desktopMode);
            if (!m_fullScreenItem)
                return;
        }
        item = fullScreenItem();
        break;
    }

    if (item) {
        // In case the model isn't yet set (see below), index will be reset and therefore we
        // need to save the current index row (https://bugs.kde.org/show_bug.cgi?id=333511).
        int indexRow = index.row();
        if (!item->model()) {
            QAbstractItemModel *model = nullptr;
            if (desktopMode) {
                model = desktopModel();
            } else {
                model = clientModel();
            }
            item->setModel(model);
        }
        if (!desktopMode && clientModel()->clientList().isEmpty()) {
            return;
        }
        item->setAllDesktops(config.clientDesktopMode() == TabBoxConfig::AllDesktopsClients);
        item->setCurrentIndex(indexRow);
        item->setNoModifierGrab(q->noModifierGrab());
        // everything is prepared, so let's make the whole thing visible
        item->setVisible(true);
    }
    if (QWindow *w = window()) {
        wheelAngleDelta = 0;
        w->installEventFilter(q);
        // pretend to activate the window to enable accessibility notifications
        KWindowEffects::enableBlurBehind(w->winId(),true,QRect(0,0,w->width(),w->height()));
        QWindowSystemInterface::handleWindowActivated(w, Qt::TabFocusReason);
    }
#endif
}

/***********************************************
* TabBoxHandler
***********************************************/

TabBoxHandler::TabBoxHandler(QObject *parent)
    : QObject(parent)
{
    KWin::TabBox::tabBox = this;
    d = new TabBoxHandlerPrivate(this);
}

TabBoxHandler::~TabBoxHandler()
{
    delete d;
}

const KWin::TabBox::TabBoxConfig& TabBoxHandler::config() const
{
    return d->config;
}

void TabBoxHandler::setConfig(const TabBoxConfig& config)
{
    d->config = config;
    Q_EMIT configChanged();
}

void TabBoxHandler::show(enum ShowMode mode)
{
    d->isShown = true;
    d->lastRaisedClient = nullptr;
    d->lastRaisedClientSucc = nullptr;
    if (d->config.isShowTabBox()) {
        d->show(mode);
    }
    if (d->config.isHighlightWindows()) {
        if (kwinApp()->x11Connection()) {
            Xcb::sync();
        }
        // TODO this should be
        // QMetaObject::invokeMethod(this, "initHighlightWindows", Qt::QueuedConnection);
        // but we somehow need to cross > 1 event cycle (likely because of queued invocation in the effects)
        // to ensure the EffectWindow is present when updateHighlightWindows, thus elevating the window/tabbox
        QTimer::singleShot(1, this, &TabBoxHandler::initHighlightWindows);
    }
}

void TabBoxHandler::initHighlightWindows()
{
    if (isKWinCompositing()) {
        const auto stack = stackingOrder();
        for (const QWeakPointer<TabBoxClient> &clientPointer : stack) {
            if (QSharedPointer<TabBoxClient> client = clientPointer.toStrongRef()) {
                shadeClient(client.data(), false);
            }
        }
    }
    d->updateHighlightWindows();
}

void TabBoxHandler::hide(bool abort, enum ShowMode mode)
{
    d->isShown = false;
    if (d->config.isHighlightWindows()) {
        d->endHighlightWindows(abort);
    }
#ifndef KWIN_UNIT_TEST
    switch (mode) {
    default:
    case TabBoxShowMode:
        if (SwitcherItem *item = d->switcherItem()) {
            item->setVisible(false);
        }
        break;
    case MaximizeShowMode:
        if (SwitcherItem *item = d->fullScreenItem()) {
            item->setVisible(false);
        }
        break;
    }
#endif
    if (QQuickWindow *w = d->window()) {
        w->hide();
        w->destroy();
    }
    d->m_mainItem = nullptr;
    d->m_fullScreenItem = nullptr;
}

QModelIndex TabBoxHandler::nextPrev(bool forward) const
{
    QModelIndex ret;
    QAbstractItemModel* model;
    switch(d->config.tabBoxMode()) {
    case TabBoxConfig::ClientTabBox:
        model = d->clientModel();
        break;
    case TabBoxConfig::DesktopTabBox:
        model = d->desktopModel();
        break;
    default:
        Q_UNREACHABLE();
    }
    if (forward) {
        int column = d->index.column() + 1;
        int row = d->index.row();
        if (column == model->columnCount()) {
            column = 0;
            row++;
            if (row == model->rowCount())
                row = 0;
        }
        ret = model->index(row, column);
        if (!ret.isValid())
            ret = model->index(0, 0);
    } else {
        int column = d->index.column() - 1;
        int row = d->index.row();
        if (column < 0) {
            column = model->columnCount() - 1;
            row--;
            if (row < 0)
                row = model->rowCount() - 1;
        }
        ret = model->index(row, column);
        if (!ret.isValid()) {
            row = model->rowCount() - 1;
            for (int i = model->columnCount() - 1; i >= 0; i--) {
                ret = model->index(row, i);
                if (ret.isValid())
                    break;
            }
        }
    }
    if (ret.isValid())
        return ret;
    else
        return d->index;
}

QModelIndex TabBoxHandler::desktopIndex(int desktop) const
{
    if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox)
        return QModelIndex();
    return d->desktopModel()->desktopIndex(desktop);
}

QList< int > TabBoxHandler::desktopList() const
{
    if (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox)
        return QList< int >();
    return d->desktopModel()->desktopList();
}

int TabBoxHandler::desktop(const QModelIndex& index) const
{
    if (!index.isValid() || (d->config.tabBoxMode() != TabBoxConfig::DesktopTabBox))
        return -1;
    QVariant ret = d->desktopModel()->data(index, DesktopModel::DesktopRole);
    if (ret.isValid())
        return ret.toInt();
    else
        return -1;
}

void TabBoxHandler::setCurrentIndex(const QModelIndex& index)
{
    if (d->index == index) {
        return;
    }
    if (!index.isValid()) {
        return;
    }
    d->index = index;
    if (d->config.tabBoxMode() == TabBoxConfig::ClientTabBox) {
        if (d->config.isHighlightWindows()) {
            d->updateHighlightWindows();
        }
    }
    Q_EMIT selectedIndexChanged();
}

const QModelIndex& TabBoxHandler::currentIndex() const
{
    return d->index;
}

void TabBoxHandler::grabbedKeyEvent(QKeyEvent* event) const
{
    ShowMode mode = d->m_switcherMode;
    if (mode == TabBoxShowMode) {
        if (!d->m_mainItem || !d->window())
            return;
    } else if (mode == MaximizeShowMode) {
        if (!d->m_fullScreenItem || !d->window())
            return;
    }
    QCoreApplication::sendEvent(d->window(), event);
}

bool TabBoxHandler::containsPos(const QPoint& pos) const
{
    if (!d->m_mainItem) {
        return false;
    }
    QWindow *w = d->window();
    if (w) {
        return w->geometry().contains(pos);
    }
    return false;
}

QModelIndex TabBoxHandler::index(QWeakPointer<KWin::TabBox::TabBoxClient> client) const
{
    return d->clientModel()->index(client);
}

TabBoxClientList TabBoxHandler::clientList() const
{
    if (d->config.tabBoxMode() != TabBoxConfig::ClientTabBox)
        return TabBoxClientList();
    return d->clientModel()->clientList();
}

TabBoxClient* TabBoxHandler::client(const QModelIndex& index) const
{
    if ((!index.isValid()) ||
            (d->config.tabBoxMode() != TabBoxConfig::ClientTabBox))
        return nullptr;
    TabBoxClient* c = static_cast< TabBoxClient* >(
                          d->clientModel()->data(index, ClientModel::ClientRole).value<void *>());
    return c;
}

void TabBoxHandler::createModel(bool partialReset, enum ShowMode mode)
{
    switch(d->config.tabBoxMode()) {
    case TabBoxConfig::ClientTabBox: {
        switch (mode) {
            default:
            case TabBoxShowMode:
                d->clientModel()->createClientList(partialReset, TabBoxShowMode);
                break;
            case MaximizeShowMode:
                d->clientModel()->createClientList(partialReset, MaximizeShowMode);
                break;
        }
        // TODO: C++11 use lambda function
        bool lastRaised = false;
        bool lastRaisedSucc = false;
        const auto clients = stackingOrder();
        for (const auto &clientPointer : clients) {
            QSharedPointer<TabBoxClient> client = clientPointer.toStrongRef();
            if (!client) {
                continue;
            }
            if (client.data() == d->lastRaisedClient) {
                lastRaised = true;
            }
            if (client.data() == d->lastRaisedClientSucc) {
                lastRaisedSucc = true;
            }
        }
        if (d->lastRaisedClient && !lastRaised)
            d->lastRaisedClient = nullptr;
        if (d->lastRaisedClientSucc && !lastRaisedSucc)
            d->lastRaisedClientSucc = nullptr;
        break;
    }
    case TabBoxConfig::DesktopTabBox:
        d->desktopModel()->createDesktopList();
        break;
    }
}

QModelIndex TabBoxHandler::first() const
{
    QAbstractItemModel* model;
    switch(d->config.tabBoxMode()) {
    case TabBoxConfig::ClientTabBox:
        model = d->clientModel();
        break;
    case TabBoxConfig::DesktopTabBox:
        model = d->desktopModel();
        break;
    default:
        Q_UNREACHABLE();
    }
    return model->index(0, 0);
}

bool TabBoxHandler::eventFilter(QObject *watched, QEvent *e)
{
    if (e->type() == QEvent::Wheel && watched == d->window()) {
        QWheelEvent *event = static_cast<QWheelEvent*>(e);
        // On x11 the delta for vertical scrolling might also be on X for whatever reason
        const int delta = qAbs(event->angleDelta().x()) > qAbs(event->angleDelta().y()) ? event->angleDelta().x() : event->angleDelta().y();
        d->wheelAngleDelta += delta;
        while (d->wheelAngleDelta <= -120) {
            d->wheelAngleDelta += 120;
            const QModelIndex index = nextPrev(true);
            if (index.isValid()) {
                setCurrentIndex(index);
            }
        }
        while (d->wheelAngleDelta >= 120) {
            d->wheelAngleDelta -= 120;
            const QModelIndex index = nextPrev(false);
            if (index.isValid()) {
                setCurrentIndex(index);
            }
        }
        return true;
    }
    // pass on
    return QObject::eventFilter(watched, e);
}

TabBoxHandler* tabBox = nullptr;

TabBoxClient::TabBoxClient()
{
}

TabBoxClient::~TabBoxClient()
{
}

} // namespace TabBox
} // namespace KWin
