/*
 *   Copyright (C) 2007, 2008, 2009, 2010 Ivan Cukic <ivan.cukic(at)kde.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License version 2,
 *   or (at your option) any later version, as published by the Free
 *   Software Foundation
 *
 *   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, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "LancelotWindow.h"

#include <QDesktopWidget>
#include <QGraphicsItem>
#include <QGraphicsView>
#include <QStyleOptionGraphicsItem>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusReply>

#include <KAboutApplicationDialog>
#include <KActionCollection>
#include <KAuthorized>
#include <KCmdLineArgs>
#include <KConfigDialog>
#include <KDebug>
#include <KGlobalAccel>
#include <KLineEdit>
#include <KProcess>
#include <KRecentDocument>
#include <KShortcutsDialog>
#include <KStandardAction>
#include <KWindowSystem>

#include <kworkspace/kworkspace.h>

#include <Plasma/LineEdit>
#include <Plasma/WindowEffects>

#include <Lancelot/PlasmaServiceListModel>
#include <Lancelot/PopupMenu>
#include <Lancelot/TabBar>
#include <Lancelot/ResizeBordersPanel>

#include <Lancelot/Models/Serializator>
#include <Lancelot/Models/Devices>
#include <Lancelot/Models/Places>
#include <Lancelot/Models/SystemServices>
#include <Lancelot/Models/RecentDocuments>
#include <Lancelot/Models/OpenDocuments>
#include <Lancelot/Models/NewDocuments>
#include <Lancelot/Models/FolderModel>
#include <Lancelot/Models/FavoriteApplications>
#include <Lancelot/Models/Applications>
#include <Lancelot/Models/Runner>
#include <Lancelot/Models/SystemActions>
#include <Lancelot/Models/ContactsKopete>
#include <Lancelot/Models/MessagesKmail>
#include <Lancelot/Models/BaseMergedModel>
#include <Lancelot/Models/Logger>

#define sectionsWidth \
    (m_mainConfig.readEntry("collapseSections", false) ? 64 : 128)

#define windowHeightDefault 500
#define mainWidthDefault    422

#define HIDE_TIMER_INTERVAL 1500
#define SEARCH_TIMER_INTERVAL 300

LancelotWindow::LancelotWindow()
    : m_root(NULL), m_corona(NULL),
    m_hovered(false), m_showingFull(true), m_sectionsSignalMapper(NULL),
    m_config("lancelotrc"), m_mainConfig(&m_config, "Main"),
    m_configWidget(NULL),
    m_resizeDirection(None),
    m_mainSize(mainWidthDefault, windowHeightDefault),
    m_skipEvent(false),
    menuSystemButton(NULL),
    menuLancelotContext(NULL),
    m_cachedOpenPosition(-1, -1),
    m_cachedWindowSize(-1, -1),
    m_cachedFlip(0),
    m_cachedOpenPositionCentered(false),
    m_cachedShowingFull(false),
    m_firstOpen(true)
{
    setFocusPolicy(Qt::WheelFocus);
    setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);// | Qt::Popup);
    setFrameStyle(QFrame::NoFrame);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::KeepAbove | NET::Sticky);

    connect(& m_hideTimer, SIGNAL(timeout()), this, SLOT(hideImmediate()));
    m_hideTimer.setInterval(HIDE_TIMER_INTERVAL);
    m_hideTimer.setSingleShot(true);

    m_corona = new Plasma::Corona(this);

    setMinimumSize(400, 300);
    setScene(m_corona);

    m_root = new Lancelot::ResizeBordersPanel();
    installEventFilter(this);

    m_root->setGroupByName("RootPanel");
    m_corona->addItem(m_root);

    setupUi(m_root);
    m_root->setLayoutItem(layoutMain);

    setupModels();

    /* TODO: Convert this to PUCK generated code */
    editSearch = new Plasma::LineEdit();
    editSearch->setParentItem(m_root);
    editSearch->nativeWidget()->setClearButtonShown(true);
    editSearch->nativeWidget()->setClickMessage(i18nc("Enter the text to search for", "Search..."));
    editSearch->nativeWidget()->setContextMenuPolicy(Qt::NoContextMenu);
    editSearch->show();
    layoutSearch->addItem(editSearch,
        Lancelot::NodeLayout::NodeCoordinate(0.0, 0.5, 0, 0),
        Lancelot::NodeLayout::NodeCoordinate(1.0, 0.5, 0, QREAL_INFINITY)
    );
    editSearch->nativeWidget()->installEventFilter(this);
    editSearch->setFocusPolicy(Qt::WheelFocus);
    editSearch->nativeWidget()->setFocusPolicy(Qt::WheelFocus);

    m_completion = new KCompletion();
    editSearch->nativeWidget()->setCompletionObject(m_completion);
    editSearch->nativeWidget()->setCompletionMode(
        KGlobalSettings::CompletionMan);
    m_completion->insertItems(m_configUi.searchHistory());

    passagewayApplications->setEntranceTitle(i18n("Favorites"));
    passagewayApplications->setEntranceIcon(KIcon("favorites"));
    passagewayApplications->setAtlasTitle(i18n("Applications"));
    passagewayApplications->setAtlasIcon(KIcon("applications-other"));

    tabbarSections->installEventFilter(this);
    tabbarSections->setTabsGroupName("SectionButtons");
    tabbarSections->addTab("documents",    KIcon("applications-office"), i18n("&Documents"),    "text/x-lancelotpart",
            "model=NewDocuments&type=list&version=1.0\nmodel=OpenDocuments&type=list&version=1.0\nmodel=RecentDocuments&type=list&version=1.0");
    tabbarSections->addTab("contacts",     KIcon("kontact"),             i18n("&Contacts"),
            "text/x-lancelotpart", "model=Messages&type=list&version=1.0\nmodel=Contacts&type=list&version=1.0");
    tabbarSections->addTab("computer",     KIcon("computer-laptop"),     i18n("Co&mputer"),     "text/x-lancelotpart",
            "model=Places&type=list&version=1.0\nmodel=System&type=list&version=1.0\nmodel=Devices%2FRemovable&type=list&version=1.0\nmodel=Devices%2FFixed&type=list&version=1.0");
    tabbarSections->addTab("applications", KIcon("applications-other"),  i18n("&Applications"), "text/x-lancelotpart",
            "model=FavoriteApplications&type=list&version=1.0\nmodel=Folder%20applications%3A%2F&type=list&version=1.0");
    tabbarSections->setOrientation(Qt::Vertical);
    tabbarSections->setTextDirection(Qt::Vertical);
    /* End TODO */

    connect(tabbarSections, SIGNAL(currentTabChanged(QString)),
            layoutCenter, SLOT(showCard(QString)));

    m_sectionsSignalMapper = new QSignalMapper(this);
    connect (m_sectionsSignalMapper,
        SIGNAL(mapped(QString)),
        this,
        SLOT(sectionActivated(QString))
    );

    connect(buttonSystem1, SIGNAL(activated()), this, SLOT(systemButtonClicked()));
    connect(buttonSystem2, SIGNAL(activated()), this, SLOT(systemButtonClicked()));
    connect(buttonSystem3, SIGNAL(activated()), this, SLOT(systemButtonClicked()));

    connect(buttonLancelotContext,  SIGNAL(activated()), this, SLOT(lancelotContext()));

    connect(editSearch->widget(),
        SIGNAL(textChanged(QString)),
        this, SLOT(search(QString))
    );

    loadConfig();
    setupActions();

    connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*,QWidget*)),
            this, SLOT(focusChanged(QWidget*,QWidget*)));

    setStyleSheet("LancelotWindow { background: black }");
}

void LancelotWindow::drawBackground(QPainter * painter, const QRectF & rect)
{
    painter->setCompositionMode(QPainter::CompositionMode_Clear);
    painter->fillRect(
        QRectF(rect.x() - 2, rect.y() - 2,
               rect.width() + 2, rect.height() + 2).toRect(),
               Qt::transparent);
    painter->setCompositionMode(QPainter::CompositionMode_Source);
}

void LancelotWindow::focusChanged(QWidget * old, QWidget * now)
{
    Q_UNUSED(old);

    // We have to return the focus to the QGraphicsView
    // since it handles the keyboard, the universe
    // and everything
    if (now == this) {
        editSearch->nativeWidget()->setFocus();
        editSearch->setFocus();
    }
}

void LancelotWindow::toggleCollapsedSections()
{
    updateCollapsedSections(true);
}

void LancelotWindow::updateCollapsedSections(bool toggle)
{
    bool collapseSections = m_mainConfig.readEntry("collapseSections", false);
    if (toggle) {
        collapseSections = !collapseSections;
        m_mainConfig.writeEntry("collapseSections", collapseSections);
        m_mainConfig.sync();
    }

    layoutMain->setSize((m_showingFull ? sectionsWidth : 0), Lancelot::FullBorderLayout::LeftBorder);

    if (collapseSections) {
        tabbarSections->setTabIconSize(QSize(32, 32));
        tabbarSections->setTextDirection(Qt::Horizontal);
    } else {
        tabbarSections->setTabIconSize(QSize(48, 48));
        tabbarSections->setTextDirection(Qt::Vertical);
    }

    updateWindowSize();
}

LancelotWindow::~LancelotWindow()
{
    m_configUi.setSearchHistory(m_completion->items());
    delete m_configWidget;
}

void LancelotWindow::lancelotShow(int x, int y)
{
    m_showingFull = true;
    showWindow(x, y);
}

void LancelotWindow::lancelotToggleCentered()
{
    if (isVisible()) {
        hide();
    } else lancelotShowCentered();
}

void LancelotWindow::lancelotShowCentered()
{
    m_showingFull = true;
    showWindow(0, 0, true);
}

void LancelotWindow::lancelotShowItem(int x, int y, const QString & name)
{
    sectionActivated(name);
    m_showingFull = false;
    showWindow(x, y);
}

void LancelotWindow::lancelotHide(bool immediate)
{
    if (immediate) {
        QTimer::singleShot(100, editSearch->nativeWidget(), SLOT(clear()));
        // editSearch->setText(QString());

        if (m_configUi.appbrowserReset()) {
            passagewayApplications->reset();
        }
        hide();

        return;
    }

    if (m_hovered) return;
    m_hideTimer.start();
}

void LancelotWindow::showWindow(int x, int y, bool centered)
{
    kDebug() << "=== showing the window";

    if (m_firstOpen || m_cachedShowingFull == m_showingFull) {
        m_cachedShowingFull = m_showingFull;

        kDebug() << "cache for m_showingFull not used";
        tabbarSections->setVisible(m_showingFull);
        layoutMain->setSize((m_showingFull ? sectionsWidth : 0), Lancelot::FullBorderLayout::LeftBorder);

        // in this case, we should really update everything...
        m_firstOpen = true;
    }

    updateCollapsedSections();

    m_resizeDirection = None;
    m_hideTimer.stop();

    if (isVisible()) {
        // We are exiting because we do not want to move already opened window
        // because most probably it is just invoked from the same applet and
        // needs to show only another category
        updateWindowSize();
        return;
    }

    if (centered) {
        x = QCursor::pos().x();
        y = QCursor::pos().y();
    }

    QRect screenRect = QApplication::desktop()->screenGeometry(QPoint(x, y));

    updateWindowSize();

    if (m_firstOpen ||
            (m_cachedOpenPosition != QPoint(x, y) || m_cachedOpenPositionCentered != centered)) {
        kDebug() << "cache for position not used";

        m_cachedOpenPosition = QPoint(x, y);
        m_cachedOpenPositionCentered = centered;

        Plasma::Flip flip;

        if (!centered) {
            flip = Plasma::VerticalFlip;

            if (x < screenRect.left()) {
                x = screenRect.left();
            }

            if (y < screenRect.top()) {
                y = screenRect.top();
            }

            if (x + width() > screenRect.right()) {
                x = x - width();
                flip |= Plasma::HorizontalFlip;
            }

            if (y + height() > screenRect.bottom()) {
                y = y - height();
                flip &= ~Plasma::VerticalFlip;
            }

            if (m_showingFull) {
                if (flip & Plasma::HorizontalFlip) {
                    x += sectionsWidth / 2;
                } else {
                    x -= sectionsWidth / 2;
                }
            }
        } else {
            flip = Plasma::NoFlip;

            x = screenRect.left()
                + (screenRect.width() - width()) / 2;
            y = screenRect.top()
                + (screenRect.height() - height()) / 2;
        }

        move(x, y);

        if (m_firstOpen || m_cachedFlip != flip) {
            kDebug() << "cache for flip not used";
            m_cachedFlip = flip;

            kDebug() << "Flip:" << flip;
            layoutMain->setFlip(flip);
            tabbarSections->setFlip(flip);

            if (m_configUi.activationMethod() == LancelotConfig::NoClick) {
                Lancelot::Global::self()->group("SystemButtons")->setProperty("extenderPosition", QVariant(
                        (flip & Plasma::VerticalFlip)?(Lancelot::TopExtender):(Lancelot::BottomExtender)
                ));
                Lancelot::Global::self()->group("SystemButtons")
                    ->setProperty("activationMethod", Lancelot::ExtenderActivate);
            } else {
                Lancelot::Global::self()->group("SystemButtons")
                    ->setProperty("extenderPosition", QVariant(Lancelot::NoExtender));
                Lancelot::Global::self()->group("SystemButtons")
                    ->setProperty("activationMethod", Lancelot::ClickActivate);
            }
            Lancelot::Global::self()->group("LancelotContext")->setProperty("extenderPosition",
                    Lancelot::Global::self()->group("SystemButtons")->property("extenderPosition"));
            Lancelot::Global::self()->group("LancelotContext")->setProperty("activationMethod",
                    Lancelot::Global::self()->group("SystemButtons")->property("activationMethod"));
            Lancelot::Global::self()->group("SystemButtons")->notifyUpdated();
            Lancelot::Global::self()->group("LancelotContext")->notifyUpdated();
        }
    }

    if (m_showingFull) {
        sectionActivated("applications");
    }

    if (KWindowSystem::compositingActive()) {
        if (m_cachedFlip & Plasma::VerticalFlip) {
            Plasma::WindowEffects::slideWindow(this, Plasma::TopEdge);

        } else {
            Plasma::WindowEffects::slideWindow(this, Plasma::BottomEdge);
        }
    }

    show();

    KWindowSystem::setState( winId(), NET::SkipTaskbar | NET::SkipPager | NET::KeepAbove );
    KWindowSystem::forceActiveWindow(winId());

    //editSearch->clearFocus();
    editSearch->nativeWidget()->setFocus();
    editSearch->setFocus();

    m_firstOpen = false;
}

bool LancelotWindow::updateWindowSize()
{
    QSize newSize = m_mainSize;
    if (m_showingFull) {
        newSize.rwidth() += sectionsWidth;
    }

    if (!m_firstOpen && newSize == m_cachedWindowSize) {
        return false;
    }

    kDebug() << "cache for size not used" << newSize << m_cachedWindowSize;
    m_cachedWindowSize = newSize;

    resize(newSize);
    m_corona->
        setSceneRect(QRectF(QPointF(), newSize));

    m_root->
        setGeometry(QRect(QPoint(), newSize));

    resize(newSize);
    m_root->group()->backgroundSvg()->resizeFrame(newSize);

    const QRegion & mask = m_root->group()->backgroundSvg()->mask();
    // setMask(mask);
    Plasma::WindowEffects::enableBlurBehind(winId(), true, mask);
    Plasma::WindowEffects::overrideShadow(winId(), true);

    return true;
}

QStringList LancelotWindow::sectionIDs()
{
    QStringList res;
    res << "applications" << "computer" << "contacts" << "documents";
    return res;
}

QStringList LancelotWindow::sectionNames()
{
    QStringList res;
    res << i18n("Applications") << i18n("Computer") << i18n("Contacts") << i18n("Documents");
    return res;
}

QStringList LancelotWindow::sectionIcons()
{
    QStringList res;
    res << "kde" << "computer-laptop" << "kontact" << "applications-office";
    // res << "applications-other" << "computer-laptop" << "kontact" << "applications-office";
    return res;
}

QString LancelotWindow::currentSection()
{
    if (isHidden()) {
        return QString();
    }

    return tabbarSections->currentTab();
}

void LancelotWindow::sectionActivated(const QString & item)
{
    kDebug() << item;
    tabbarSections->setCurrentTab(item);

    // TODO: m_activeSection should be examined
    // - since we have a tabbarSections, it is not really
    // used and it is not representing the active section
    if (item == m_activeSection) {
        return;
    }

    m_activeSection = item;

    if (m_focusIndex >= 0 && m_focusIndex < m_focusList.count()) {
        if (m_focusList.at(m_focusIndex) != passagewayApplications) {
            ((Lancelot::ActionListView *) m_focusList.at(m_focusIndex))->clearSelection();
        } else {
            ((Lancelot::PassagewayView *) m_focusList.at(m_focusIndex))->clearSelection();
        }
    }

    m_focusList.clear();
    m_focusIndex = 0;

    if (item == "search") {
        m_focusList << listSearchLeft;
    } else if (item == "applications") {
        m_focusList << passagewayApplications;
    } else if (item == "computer") {
        m_focusList << listComputerLeft;
        m_focusList << listComputerRight;
    } else if (item == "contacts") {
        m_focusList << listContactsLeft;
        m_focusList << listContactsRight;
    } else if (item == "documents") {
        m_focusList << listDocumentsLeft;
        m_focusList << listDocumentsRight;
    }

    layoutCenter->showCard(item);
}

void LancelotWindow::search(const QString & string)
{
    if (editSearch->text() != string) {
        editSearch->setText(string);
    }

    m_searchString = string;

    ((Lancelot::Models::Runner *) m_models["Runner"])->setSearchString(m_searchString);
    if (m_searchString.isEmpty()) {
        sectionActivated("applications");
    } else {
        sectionActivated("search");
    }
}

void LancelotWindow::systemButtonClicked()
{
    Lancelot::ExtenderButton * button =
        static_cast < Lancelot::ExtenderButton * > (sender());

    Lancelot::ActionTreeModel * model =
        Lancelot::Models::SystemActions::self()->action(systemButtonActions[button]);

    if (!model) return;

    if (!menuSystemButton) {
        menuSystemButton = new Lancelot::PopupList();
        menuSystemButton->resize(200, 200);
        Lancelot::Models::ApplicationConnector * ac = Lancelot::Models::ApplicationConnector::self();
        connect(
                ac, SIGNAL(doHide(bool)),
                menuSystemButton, SLOT(close())
        );
    }

    menuSystemButton->setModel(model);
    menuSystemButton->updateSize();

    QRect g = button->geometry().toRect();
    g.moveTopLeft(g.topLeft() + geometry().topLeft());

    if (layoutMain->flip() & Plasma::VerticalFlip) {
        menuSystemButton->move(g.bottomLeft());
    } else {
        menuSystemButton->move(g.topLeft() - QPoint(0, menuSystemButton->geometry().height()));
    }

    menuSystemButton->exec(menuSystemButton->pos());
}

void LancelotWindow::setupModels()
{
    // Models:
    m_models["Places"]            = new Lancelot::Models::Places();
    m_models["SystemServices"]    = new Lancelot::Models::SystemServices();
    m_models["Devices/Removable"] = new Lancelot::Models::Devices(Lancelot::Models::Devices::Removable);
    m_models["Devices/Fixed"]     = new Lancelot::Models::Devices(Lancelot::Models::Devices::Fixed);

    m_models["NewDocuments"]      = new Lancelot::Models::NewDocuments();
    m_models["RecentDocuments"]   = new Lancelot::Models::RecentDocuments();
    m_models["OpenDocuments"]     = new Lancelot::Models::OpenDocuments();

    m_models["Runner"]            = new Lancelot::Models::Runner(); //allowedRunners);

    // Groups:

    m_modelGroups["ComputerLeft"]   = new Lancelot::Models::BaseMergedModel();
    m_modelGroups["DocumentsLeft"]  = new Lancelot::Models::BaseMergedModel();
    m_modelGroups["ContactsLeft"]   = new Lancelot::Models::BaseMergedModel();

    m_modelGroups["ComputerRight"]  = new Lancelot::Models::BaseMergedModel();
    m_modelGroups["DocumentsRight"] = new Lancelot::Models::BaseMergedModel();
    m_modelGroups["ContactsRight"]  = new Lancelot::Models::BaseMergedModel();

    // Assignments: Model - Group:
    #define MergedAddModel(MergedModel, ModelID, Model, Title) \
        ((Lancelot::Models::BaseMergedModel *)(MergedModel))      \
        ->addModel((ModelID), QIcon(), Title, Model);

    MergedAddModel(m_modelGroups["ComputerLeft"], "Places", m_models["Places"], i18n("Places"));
    MergedAddModel(m_modelGroups["ComputerLeft"], "System", m_models["SystemServices"], i18n("System"));



    MergedAddModel(m_modelGroups["ComputerRight"], "Devices/Removable", m_models["Devices/Removable"], i18nc("@title Removable devices", "Removable"));
    MergedAddModel(m_modelGroups["ComputerRight"], "Devices/Fixed", m_models["Devices/Fixed"], i18nc("@title Fixed devices", "Fixed"));

    MergedAddModel(m_modelGroups["DocumentsLeft"], "NewDocuments", m_models["NewDocuments"], i18nc("@title New documents", "New"));

    MergedAddModel(m_modelGroups["DocumentsRight"], "OpenDocuments", m_models["OpenDocuments"], i18nc("@title List of open documents", "Open"));
    MergedAddModel(m_modelGroups["DocumentsRight"], "RecentDocuments", m_models["RecentDocuments"], i18nc("@title Recent documents", "Recent"));

    QString plugins;

    // Contacts Mail
    plugins = m_mainConfig.readEntry("mailPlugins", QString());
    if (plugins.isEmpty()) {
        m_models["Messages"]          = new Lancelot::Models::MessagesKmail();
        MergedAddModel(m_modelGroups["ContactsLeft"], "Messages", m_models["Messages"], i18n("Unread messages"));
    } else if (plugins != "disabled") {
        Lancelot::ActionListModel * model;
        foreach (const QString &plugin, plugins.split(',')) {
            model = new Lancelot::PlasmaServiceListModel(plugin);
            MergedAddModel(m_modelGroups["ContactsLeft"], model->selfTitle(), model, i18n("Unread messages"));
        }
    }

    // Contacts IM
    plugins = m_mainConfig.readEntry("imPlugins", QString());
    if (plugins.isEmpty()) {
        m_models["Contacts"]          = new Lancelot::Models::ContactsKopete();
        MergedAddModel(m_modelGroups["ContactsRight"], "Contacts", m_models["Contacts"], i18n("Online contacts"));
    } else if (plugins != "disabled") {
        Lancelot::ActionListModel * model;
        foreach (const QString &plugin, plugins.split(',')) {
            model = new Lancelot::PlasmaServiceListModel(plugin);
            MergedAddModel(m_modelGroups["ContactsRight"], model->selfTitle(), model, i18n("Online contacts"));
        }
    }

    m_modelGroups["SearchLeft"] = m_models["Runner"];

    // Assignments: ListView - Group

    listComputerLeft->setModel(m_modelGroups["ComputerLeft"]);
    listDocumentsLeft->setModel(m_modelGroups["DocumentsLeft"]);
    listContactsLeft->setModel(m_modelGroups["ContactsLeft"]);
    listSearchLeft->setModel(m_modelGroups["SearchLeft"]);

    listComputerRight->setModel(m_modelGroups["ComputerRight"]);
    listDocumentsRight->setModel(m_modelGroups["DocumentsRight"]);
    listContactsRight->setModel(m_modelGroups["ContactsRight"]);
    //listSearchRight->setModel(m_modelGroups["SearchRight"]);

    // Applications passageview
    passagewayApplications->setEntranceModel(
        new Lancelot::Models::FavoriteApplications::PassagewayViewProxy()
    );
    passagewayApplications->setAtlasModel(new Lancelot::Models::Applications());

    #undef MergedAddModel
}

// Resizing:

void LancelotWindow::mousePressEvent(QMouseEvent * e)
{
    m_resizeDirection = None;

    if (e->x() >= width() - m_root->borderSize(Plasma::RightMargin))  m_resizeDirection |= Right;
    else if (e->x() <= m_root->borderSize(Plasma::LeftMargin))        m_resizeDirection |= Left;

    if (e->y() >= height() - m_root->borderSize(Plasma::BottomMargin)) m_resizeDirection |= Down;
    else if (e->y() <= m_root->borderSize(Plasma::TopMargin))          m_resizeDirection |= Up;

    if (m_resizeDirection != None) {
        m_originalMousePosition  = e->globalPos();
        m_originalWindowPosition = pos();
        m_originalMainSize       = m_mainSize;
    }

    QGraphicsView::mousePressEvent(e);
}

void LancelotWindow::mouseReleaseEvent(QMouseEvent * e)
{
    if (m_resizeDirection != None) {
        m_mainConfig.writeEntry("width",  m_mainSize.width());
        m_mainConfig.writeEntry("height", m_mainSize.height());
        m_mainConfig.sync();
        m_resizeDirection = None;
    }
    QGraphicsView::mouseReleaseEvent(e);
}

void LancelotWindow::mouseMoveEvent(QMouseEvent * e)
{
    if (m_resizeDirection != None) {
        m_mainSize = m_originalMainSize;

        QPoint newWindowPosition = m_originalWindowPosition;
        QPoint diff = e->globalPos() - m_originalMousePosition;

        if (m_resizeDirection & Right) {
            m_mainSize.rwidth() += diff.x();
        } else if (m_resizeDirection & Left) {
            m_mainSize.rwidth() -= diff.x();
            newWindowPosition.rx() += diff.x();
        }

        if (m_resizeDirection & Down) {
            m_mainSize.rheight() += diff.y();
        } else if (m_resizeDirection & Up) {
            m_mainSize.rheight() -= diff.y();
            newWindowPosition.ry() += diff.y();
        }

        QSize newSize = m_mainSize;
        if (m_showingFull) {
            newSize.rwidth() += sectionsWidth;
        }

        newSize = newSize.expandedTo(minimumSize());

        setGeometry(QRect(newWindowPosition, newSize));

        const QRegion & mask = m_root->group()->backgroundSvg()->mask();
        // setMask(mask);
        Plasma::WindowEffects::enableBlurBehind(winId(), true, mask);
        Plasma::WindowEffects::overrideShadow(winId(), true);
    }
    QGraphicsView::mouseMoveEvent(e);
}

void LancelotWindow::sendKeyEvent(QKeyEvent * event)
{
    // We get this twice for every keypress... ???
    m_skipEvent = !m_skipEvent;
    if (!m_skipEvent) {
        return;
    }

    if (passagewayApplications == m_focusList.at(m_focusIndex)) {
        ((Lancelot::PassagewayView *) m_focusList.at(m_focusIndex))->keyPressEvent(event);
    } else {
        ((Lancelot::ActionListView *) m_focusList.at(m_focusIndex))->keyPressEvent(event);
    }
}

bool LancelotWindow::eventFilter(QObject * object, QEvent * event)
{
    // Right-click the tabbarSections
    if (event->type() == QEvent::GraphicsSceneMousePress &&
        object == tabbarSections) {
        QGraphicsSceneMouseEvent * mouseEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
        if (mouseEvent->button() == Qt::RightButton) {
            Lancelot::PopupMenu * menu = new Lancelot::PopupMenu(this);

            QString text;
            if (m_mainConfig.readEntry("collapseSections", false)) {
                text = i18n("Make buttons wider");
            } else {
                text = i18n("Make buttons narrower");
            }
            connect(
                    menu->addAction(KIcon(), text), SIGNAL(triggered(bool)),
                    this, SLOT(toggleCollapsedSections())
                    );

            menu->exec(QCursor::pos());
        }
    }

    // Catching key presses because no item has explicit
    // focus
    if (event->type() == QEvent::KeyPress) {
        bool pass = false;
        int oindex = m_focusIndex;
        QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
        switch (keyEvent->key()) {
            case Qt::Key_Escape:
                lancelotHide(true);
                break;

            case Qt::Key_Tab:
            {
                QKeyEvent * endKeyEvent =
                    new QKeyEvent(QEvent::KeyPress, Qt::Key_End,
                               Qt::NoModifier);
                QCoreApplication::sendEvent(editSearch->nativeWidget(), endKeyEvent);

                endKeyEvent =
                    new QKeyEvent(QEvent::KeyRelease, Qt::Key_End,
                               Qt::NoModifier);
                QCoreApplication::sendEvent(editSearch->nativeWidget(), endKeyEvent);

                return true;
            }

            case Qt::Key_Return:
            case Qt::Key_Enter:
                if (m_activeSection == "search") {
                    if (listSearchLeft->selectedIndex() == -1) {
                        listSearchLeft->initialSelection();
                    }

                    if (!editSearch->text().isEmpty()) {
                        m_completion->addItem(editSearch->text());
                        m_configUi.setSearchHistory(m_completion->items());
                    }
                }

                sendKeyEvent(keyEvent);
                break;

            case Qt::Key_Up:
            case Qt::Key_Down:
            case Qt::Key_Menu:
                sendKeyEvent(keyEvent);
                break;

            case Qt::Key_Left:
                m_focusIndex--;
                break;

            case Qt::Key_Right:
                m_focusIndex++;
                break;

            case Qt::Key_PageUp:
                nextSection(-1);
                return true;

            case Qt::Key_PageDown:
                nextSection(1);
                return true;

            default:
                pass = true;
        }

        if (m_focusIndex < 0) {
            m_focusIndex = 0;
            pass = true;
        } else if (m_focusIndex >= m_focusList.size()) {
            m_focusIndex = m_focusList.size() - 1;
            pass = true;
        }

        if (oindex != m_focusIndex) {
            if (m_focusList.at(oindex) != passagewayApplications) {
                ((Lancelot::ActionListView *) m_focusList.at(oindex))->clearSelection();
            }
            if (m_focusList.at(m_focusIndex) != passagewayApplications) {
                ((Lancelot::ActionListView *) m_focusList.at(m_focusIndex))->initialSelection();
            }
        }

        if (pass) {
            sendKeyEvent(keyEvent);
        }

        editSearch->nativeWidget()->setFocus();
        editSearch->setFocus();
    }

    return QWidget::eventFilter(object, event);
}

void LancelotWindow::setupActions()
{
    m_actionCollection = new KActionCollection(this);
    KAction * a = 0;

    if (KAuthorized::authorizeKAction("show_lancelot")) {
        a = m_actionCollection->addAction(i18n("Lancelot"), this);
        a->setText(i18n("Open Lancelot menu"));
        a->setGlobalShortcut(KShortcut(Qt::ALT + Qt::Key_F5));
        a->setIcon(KIcon("lancelot"));
        connect(
                a, SIGNAL(triggered(bool)),
                this, SLOT(lancelotToggleCentered())
                );
    }

    QStringList sIDs = sectionIDs();
    QStringList sNames = sectionNames();
    QStringList sIcons = sectionIcons();
    for (int i = 0; i < sIDs.size(); i++) {
        a = m_actionCollection->addAction(sIDs.at(i), this);
        a->setText(sNames.at(i));
        a->setIcon(KIcon(sIcons.at(i)));
        a->setShortcut(Qt::ALT + Qt::Key_1 + i);
        connect(
                a, SIGNAL(triggered(bool)),
                m_sectionsSignalMapper, SLOT(map())
                );
        m_sectionsSignalMapper->setMapping(a, sIDs.at(i));
    }
    m_actionCollection->readSettings();
    m_actionCollection->associateWidget(this);
}

void LancelotWindow::configureShortcuts()
{
    lancelotHide(true);
    KShortcutsDialog::configure(m_actionCollection);
}

void LancelotWindow::configurationChanged()
{
    loadConfig();
}

void LancelotWindow::nextSection(int increment)
{
    kDebug() << increment;
    if (! (layoutMain->flip() & Plasma::VerticalFlip)) {
        increment = - increment;
    }

    int currentIndex = sectionIDs().indexOf(m_activeSection);
    currentIndex += increment;

    if (currentIndex >= sectionIDs().size()) {
        currentIndex = 0;
    } else if (currentIndex < 0) {
        currentIndex = sectionIDs().size() - 1;
    }

    sectionActivated(sectionIDs()[currentIndex]);
}

void LancelotWindow::loadConfig()
{
    // Non configurable options
    m_mainSize = QSize(
        m_mainConfig.readEntry("width",  mainWidthDefault),
        m_mainConfig.readEntry("height", windowHeightDefault)
    );

    // Creating the config widget
    if (m_configWidget == NULL) {
        m_configWidget = new QWidget();
        m_configUi.setupUi(m_configWidget);
    }
    m_configUi.loadConfig();

    // Loading activation method for groups
    bool sectionNoClick = true;
    bool listsNoClick = true;
    bool systemNoClick = true;

    switch (m_configUi.activationMethod()) {
        case LancelotConfig::Click:
            sectionNoClick = false;
            listsNoClick = false;
            systemNoClick = false;
            break;
        case LancelotConfig::Classic:
            listsNoClick = false;
            systemNoClick = false;
            break;
        case LancelotConfig::NoClick:
            break;
    }

    if (systemNoClick) {
        Lancelot::Global::self()->group("SystemButtons")->setProperty("extenderPosition",
                (layoutMain->flip() & Plasma::VerticalFlip)
                    ? (Lancelot::TopExtender) : (Lancelot::BottomExtender)
        );
        Lancelot::Global::self()->group("SystemButtons")
            ->setProperty("activationMethod", Lancelot::ExtenderActivate);
    } else {
        Lancelot::Global::self()->group("SystemButtons")
            ->setProperty("extenderPosition", QVariant(Lancelot::NoExtender));
        Lancelot::Global::self()->group("SystemButtons")
            ->setProperty("activationMethod", Lancelot::ClickActivate);
    }

    Lancelot::Global::self()->group("LancelotContext")->setProperty("extenderPosition",
            Lancelot::Global::self()->group("SystemButtons")->property("extenderPosition"));
    Lancelot::Global::self()->group("LancelotContext")->setProperty("activationMethod",
            Lancelot::Global::self()->group("SystemButtons")->property("activationMethod"));
    Lancelot::Global::self()->group("SystemButtons")->notifyUpdated();
    Lancelot::Global::self()->group("LancelotContext")->notifyUpdated();

    if (sectionNoClick) {
        Lancelot::Global::self()->group("SectionButtons")
            ->setProperty("activationMethod", Lancelot::HoverActivate);
    } else {
        Lancelot::Global::self()->group("SectionButtons")
            ->setProperty("activationMethod", Lancelot::ClickActivate);
    }
    Lancelot::Global::self()->group("SectionButtons")->notifyUpdated();

    if (listsNoClick) {
        Lancelot::Global::self()->group("ActionListView-Left")
            ->setProperty("extenderPosition", Lancelot::LeftExtender);
        Lancelot::Global::self()->group("ActionListView-Right")
            ->setProperty("extenderPosition", Lancelot::RightExtender);
        Lancelot::Global::self()->group("PassagewayView")
            ->setProperty("activationMethod", Lancelot::ExtenderActivate);
        Lancelot::Global::self()->group("PopupList")
            ->setProperty("extenderPosition", Lancelot::RightExtender);
    } else {
        Lancelot::Global::self()->group("ActionListView-Left")
            ->setProperty("extenderPosition", Lancelot::NoExtender);
        Lancelot::Global::self()->group("ActionListView-Right")
            ->setProperty("extenderPosition", Lancelot::NoExtender);
        Lancelot::Global::self()->group("PassagewayView")
            ->setProperty("activationMethod", Lancelot::ClickActivate);
        Lancelot::Global::self()->group("PopupList")
            ->setProperty("extenderPosition", Lancelot::NoExtender);
    }
    Lancelot::Global::self()->group("ActionListView-Left")->notifyUpdated();
    Lancelot::Global::self()->group("ActionListView-Right")->notifyUpdated();
    Lancelot::Global::self()->group("PassagewayView")->notifyUpdated();

    // PassagewayView settings
    if (m_configUi.appbrowserPopupSubmenus()) {
        passagewayApplications->setColumnLimit(-1);
    } else if (m_configUi.appbrowserColumnLimitted()) {
        passagewayApplications->setColumnLimit(2);
    } else {
        passagewayApplications->setColumnLimit(22); // TODO: Temp
    }

    // Loading system buttons actions
    systemButtonActions[buttonSystem1] = m_configUi.systemButtonActions[m_configUi.buttonSystem1];
    systemButtonActions[buttonSystem2] = m_configUi.systemButtonActions[m_configUi.buttonSystem2];
    systemButtonActions[buttonSystem3] = m_configUi.systemButtonActions[m_configUi.buttonSystem3];

    QHashIterator < Lancelot::ExtenderButton *, QString > i(systemButtonActions);
    while (i.hasNext()) {
        i.next();

        i.key()->setTitle(Lancelot::Models::SystemActions::self()->actionTitle(
                    i.value()));
        i.key()->setIcon(Lancelot::Models::SystemActions::self()->actionIcon(
                    i.value()));
    }

    // Logging
    Lancelot::Models::Logger::self()->setEnabled(m_configUi.enableUsageStatistics());

    // Keep open
    Lancelot::Models::ApplicationConnector::self()->setAutohideEnabled(!m_configUi.checkKeepOpen->isChecked());

    // Runners
    ((Lancelot::Models::Runner *) m_models["Runner"])->reloadConfiguration();
}

void LancelotWindow::lancelotContext()
{
    if (!menuLancelotContext) {
        menuLancelotContext = new Lancelot::PopupMenu();

        connect(
                menuLancelotContext->addAction(KIcon("kmenuedit"),
                    i18n("Menu Editor")), SIGNAL(triggered(bool)),
                this, SLOT(showMenuEditor()));

        connect(
                menuLancelotContext->addAction(KIcon("configure-shortcuts"),
                    i18n("Configure Shortcuts...")), SIGNAL(triggered(bool)),
                this, SLOT(configureShortcuts()));

        connect(
                menuLancelotContext->addAction(KIcon("configure"),
                    i18n("Configure Lancelot menu...")), SIGNAL(triggered(bool)),
                this, SLOT(configureMenu()));

        connect(
                menuLancelotContext->addAction(KIcon("lancelot"),
                    i18n("About Lancelot")), SIGNAL(triggered(bool)),
                this, SLOT(showAboutDialog()));
    }

    // menuLancelotContext->show();
    // menuLancelotContext->exec(QCursor::pos());

    menuLancelotContext->updateSize();

    QRect g = buttonLancelotContext->geometry().toRect();
    g.moveTopLeft(g.topLeft() + geometry().topLeft());

    if (layoutMain->flip() & Plasma::VerticalFlip) {
        menuLancelotContext->move(g.bottomLeft());
    } else {
        menuLancelotContext->move(g.topLeft()
            - QPoint(0, menuLancelotContext->geometry().height()));
    }

    menuLancelotContext->exec(menuLancelotContext->pos());
}

void LancelotWindow::configureMenu()
{
    lancelotHide(true);
    const QString dialogID = "LancelotMenuConfigurationDialog";
    KConfigDialog * dialog;

    if ((dialog = KConfigDialog::exists(dialogID))) {
        KWindowSystem::setOnDesktop(dialog->winId(), KWindowSystem::currentDesktop());
        dialog->show();
        KWindowSystem::activateWindow(dialog->winId());
        return;
    }

    KConfigSkeleton * nullManager = new KConfigSkeleton(0);
    dialog = new KConfigDialog(this, dialogID, nullManager);
    dialog->setFaceType(KPageDialog::Auto);
    dialog->setWindowTitle(i18n("Configure Lancelot menu"));
    dialog->setAttribute(Qt::WA_DeleteOnClose, false);
    dialog->addPage(m_configWidget, i18n("Configure Lancelot menu"), "lancelot");
    dialog->showButton(KDialog::Apply, false); // To follow the current Plasma applet style
    dialog->show();
    connect(dialog, SIGNAL(applyClicked()), this, SLOT(saveConfig()));
    connect(dialog, SIGNAL(okClicked()),    this, SLOT(saveConfig()));
}

void LancelotWindow::saveConfig()
{
    m_configUi.saveConfig();
    loadConfig();
}

void LancelotWindow::showAboutDialog()
{
    lancelotHide(true);

    KAboutApplicationDialog * about = new KAboutApplicationDialog(
            KCmdLineArgs::aboutData()
            );
    about->setAttribute(Qt::WA_DeleteOnClose, true);
    about->show();
}

void LancelotWindow::showMenuEditor()
{
    KProcess::execute("kmenuedit");
}

void LancelotWindow::hideImmediate()
{
    lancelotHide(true);
}

void LancelotWindow::resizeEvent(QResizeEvent * event)
{
    QWidget::resizeEvent(event);

    if (updateWindowSize()) {
    //     m_corona->
    //         setSceneRect(QRectF(QPointF(), event->size()));

    //     m_root->
    //         setGeometry(QRect(QPoint(), event->size()));
    }
}


