/*
    SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcommandbar.h"
#include "kcommandbarmodel_p.h"
#include "kconfigwidgets_debug.h"

#include <QAction>
#include <QCoreApplication>
#include <QGraphicsOpacityEffect>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMenu>
#include <QPainter>
#include <QPointer>
#include <QScreen>
#include <QSortFilterProxyModel>
#include <QStatusBar>
#include <QStyledItemDelegate>
#include <QTextLayout>
#include <QToolBar>
#include <QTreeView>
#include <QVBoxLayout>

#include <KConfigGroup>
#include <KFuzzyMatcher>
#include <KLocalizedString>
#include <KSharedConfig>

static QRect getCommandBarBoundingRect(KCommandBar *commandBar)
{
    QWidget *parentWidget = commandBar->parentWidget();
    Q_ASSERT(parentWidget);

    const QMainWindow *mainWindow = qobject_cast<const QMainWindow *>(parentWidget);
    if (!mainWindow) {
        return parentWidget->geometry();
    }

    QRect boundingRect = mainWindow->contentsRect();

    // exclude the menu bar from the bounding rect
    if (const QWidget *menuWidget = mainWindow->menuWidget()) {
        if (!menuWidget->isHidden()) {
            boundingRect.setTop(boundingRect.top() + menuWidget->height());
        }
    }

    // exclude the status bar from the bounding rect
    if (const QStatusBar *statusBar = mainWindow->findChild<QStatusBar *>()) {
        if (!statusBar->isHidden()) {
            boundingRect.setBottom(boundingRect.bottom() - statusBar->height());
        }
    }

    // exclude any undocked toolbar from the bounding rect
    const QList<QToolBar *> toolBars = mainWindow->findChildren<QToolBar *>();
    for (QToolBar *toolBar : toolBars) {
        if (toolBar->isHidden() || toolBar->isFloating()) {
            continue;
        }

        switch (mainWindow->toolBarArea(toolBar)) {
        case Qt::TopToolBarArea:
            boundingRect.setTop(std::max(boundingRect.top(), toolBar->geometry().bottom()));
            break;
        case Qt::RightToolBarArea:
            boundingRect.setRight(std::min(boundingRect.right(), toolBar->geometry().left()));
            break;
        case Qt::BottomToolBarArea:
            boundingRect.setBottom(std::min(boundingRect.bottom(), toolBar->geometry().top()));
            break;
        case Qt::LeftToolBarArea:
            boundingRect.setLeft(std::max(boundingRect.left(), toolBar->geometry().right()));
            break;
        default:
            break;
        }
    }

    return boundingRect;
}

// BEGIN CommandBarFilterModel
class CommandBarFilterModel final : public QSortFilterProxyModel
{
    Q_OBJECT
public:
    CommandBarFilterModel(QObject *parent = nullptr)
        : QSortFilterProxyModel(parent)
    {
        connect(this, &CommandBarFilterModel::modelAboutToBeReset, this, [this]() {
            m_hasActionsWithIcons = false;
        });
    }

    bool hasActionsWithIcons() const
    {
        return m_hasActionsWithIcons;
    }

    Q_SLOT void setFilterString(const QString &string)
    {
        // MUST reset the model here, we want to repopulate
        // invalidateFilter() will not work here
        beginResetModel();
        m_pattern = string;
        endResetModel();
    }

protected:
    bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
    {
        const int scoreLeft = sourceLeft.data(KCommandBarModel::Score).toInt();
        const int scoreRight = sourceRight.data(KCommandBarModel::Score).toInt();
        if (scoreLeft == scoreRight) {
            const QString textLeft = sourceLeft.data().toString();
            const QString textRight = sourceRight.data().toString();

            return textRight.localeAwareCompare(textLeft) < 0;
        }

        return scoreLeft < scoreRight;
    }

    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
    {
        const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

        bool accept = false;
        if (m_pattern.isEmpty()) {
            accept = true;
        } else {
            const QString row = index.data(Qt::DisplayRole).toString();
            KFuzzyMatcher::Result resAction = KFuzzyMatcher::match(m_pattern, row);
            sourceModel()->setData(index, resAction.score, KCommandBarModel::Score);
            accept = resAction.matched;
        }

        if (accept && !m_hasActionsWithIcons) {
            m_hasActionsWithIcons |= !index.data(Qt::DecorationRole).isNull();
        }

        return accept;
    }

private:
    QString m_pattern;
    mutable bool m_hasActionsWithIcons = false;
};
// END CommandBarFilterModel

static QColor getTextColor(const QStyleOptionViewItem &option)
{
    QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
    if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) {
        cg = QPalette::Inactive;
    }

    if (option.state & QStyle::State_Selected) {
        return option.palette.color(cg, QPalette::HighlightedText);
    }
    return option.palette.color(cg, QPalette::Text);
}

class CommandBarStyleDelegate final : public QStyledItemDelegate
{
    Q_OBJECT
public:
    CommandBarStyleDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
    {
    }

    /*
     * Paints a single item's text
     */
    static void
    paintItemText(QPainter *p, const QString &textt, const QRect &rect, const QStyleOptionViewItem &options, QList<QTextLayout::FormatRange> formats)
    {
        QString text = options.fontMetrics.elidedText(textt, Qt::ElideRight, rect.width());

        // set formats and font
        QTextLayout textLayout(text, options.font);
        formats.append(textLayout.formats());
        textLayout.setFormats(formats);

        // set alignment, rtls etc
        QTextOption textOption;
        textOption.setTextDirection(options.direction);
        textOption.setAlignment(QStyle::visualAlignment(options.direction, options.displayAlignment));
        textLayout.setTextOption(textOption);

        // layout the text
        textLayout.beginLayout();

        QTextLine line = textLayout.createLine();
        if (!line.isValid()) {
            return;
        }

        const int lineWidth = rect.width();
        line.setLineWidth(lineWidth);
        line.setPosition(QPointF(0, 0));

        textLayout.endLayout();

        /*
         * get "Y" so that we can properly V-Center align the text in row
         */
        const int y = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignVCenter, textLayout.boundingRect().size().toSize(), rect).y();

        // draw the text
        const QPointF pos(rect.x(), y);
        textLayout.draw(p, pos);
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
    {
        painter->save();

        /*
         * Draw everything, (widget, icon etc) except the text
         */
        QStyleOptionViewItem option = opt;
        initStyleOption(&option, index);
        const bool isSelected = option.state & QStyle::State_Selected;

        option.text.clear(); // clear old text
        QStyle *style = option.widget->style();
        style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);

        const int hMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, option.widget);

        QRect textRect = option.rect;

        const CommandBarFilterModel *model = static_cast<const CommandBarFilterModel *>(index.model());
        if (model->hasActionsWithIcons()) {
            const int iconWidth = option.decorationSize.width() + (hMargin * 2);
            if (option.direction == Qt::RightToLeft) {
                textRect.adjust(0, 0, -iconWidth, 0);
            } else {
                textRect.adjust(iconWidth, 0, 0, 0);
            }
        }

        QList<QTextLayout::FormatRange> formats;
        const QString original = index.data().toString();
        if (!isSelected) {
            // omit custom text coloring if item is selected because the contrast ratio between the custom text colors
            // and highlight background is often bad, especially if highlighted text has inverted text color
            QStringView str = original;
            int componentIdx = original.indexOf(QLatin1Char(':'));
            int actionNameStart = 0;
            if (componentIdx > 0) {
                actionNameStart = componentIdx + 2;
                // + 2 because there is a space after colon
                str = str.mid(actionNameStart);

                QTextCharFormat gray;
                gray.setForeground(option.palette.placeholderText());
                formats.append({0, componentIdx, gray});
            }

            /*
             * Highlight matches from fuzzy matcher
             */
            const auto fmtRanges = KFuzzyMatcher::matchedRanges(m_filterString, str);
            QTextCharFormat f;
            f.setForeground(option.palette.link());
            formats.reserve(formats.size() + fmtRanges.size());
            std::transform(fmtRanges.begin(), fmtRanges.end(), std::back_inserter(formats), [f, actionNameStart](const KFuzzyMatcher::Range &fr) {
                return QTextLayout::FormatRange{fr.start + actionNameStart, fr.length, f};
            });
        }

        textRect.adjust(hMargin, 0, -hMargin, 0);
        painter->setPen(getTextColor(option));
        paintItemText(painter, original, textRect, option, std::move(formats));

        painter->restore();
    }

public Q_SLOTS:
    void setFilterString(const QString &text)
    {
        m_filterString = text;
    }

private:
    QString m_filterString;
};

class ShortcutStyleDelegate final : public QStyledItemDelegate
{
    Q_OBJECT
public:
    ShortcutStyleDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
    {
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
    {
        // draw background
        QStyleOptionViewItem option = opt;
        initStyleOption(&option, index);
        option.widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);

        const QString shortcutString = index.data().toString();
        if (shortcutString.isEmpty()) {
            return;
        }

        const ShortcutSegments shortcutSegments = splitShortcut(shortcutString);
        if (shortcutSegments.isEmpty()) {
            return;
        }

        struct Button {
            int textWidth;
            QString text;
        };

        // compute the width of each shortcut segment
        QList<Button> btns;
        btns.reserve(shortcutSegments.count());
        const int hMargin = horizontalMargin(option);
        for (const QString &text : shortcutSegments) {
            int textWidth = option.fontMetrics.horizontalAdvance(text);
            textWidth += 2 * hMargin;
            btns.append({textWidth, text});
        }

        int textHeight = option.fontMetrics.lineSpacing();
        // this happens on gnome so we manually decrease the height a bit
        if (textHeight == option.rect.height()) {
            textHeight -= 4;
        }

        const int y = option.rect.y() + (option.rect.height() - textHeight) / 2;
        int x;
        if (option.direction == Qt::RightToLeft) {
            x = option.rect.x() + hMargin;
        } else {
            x = option.rect.right() - shortcutDrawingWidth(option, shortcutSegments, hMargin) - hMargin;
        }

        const QColor keyTextColor = option.palette.buttonText().color();
        const QColor textColor = getTextColor(option);
        painter->save();
        painter->setRenderHint(QPainter::Antialiasing);
        for (int i = 0, n = btns.count(); i < n; ++i) {
            const Button &button = btns.at(i);

            QRect outputRect(x, y, button.textWidth, textHeight);

            // an even element indicates that it is a key
            if (i % 2 == 0) {
                painter->save();
                painter->setPen(Qt::NoPen);

                // draw rounded rect shadow
                auto shadowRect = outputRect.translated(0, 1);
                painter->setBrush(option.palette.shadow());
                painter->drawRoundedRect(shadowRect, 3.0, 3.0);

                // draw rounded rect itself
                painter->setBrush(option.palette.window());
                painter->drawRoundedRect(outputRect, 3.0, 3.0);

                painter->restore();
            }

            // draw shortcut segment
            painter->setPen((i % 2 == 0) ? keyTextColor : textColor);
            painter->drawText(outputRect, Qt::AlignCenter, button.text);

            x += outputRect.width();
        }

        painter->restore();
    }

    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
    {
        if (index.isValid() && index.column() == KCommandBarModel::Column_Shortcut) {
            const QString shortcut = index.data().toString();
            if (!shortcut.isEmpty()) {
                const ShortcutSegments shortcutSegments = splitShortcut(shortcut);
                if (!shortcutSegments.isEmpty()) {
                    const int hMargin = horizontalMargin(option);
                    int width = shortcutDrawingWidth(option, shortcutSegments, hMargin);

                    // add left and right margins
                    width += 2 * hMargin;

                    return QSize(width, 0);
                }
            }
        }

        return QStyledItemDelegate::sizeHint(option, index);
    }

private:
    using ShortcutSegments = QStringList;

    // split shortcut into segments i.e. will return
    // ["Ctrl", "+", "A", ", ", "Ctrl", "+", "K"] for "Ctrl+A, Ctrl+K"
    // twice as fast as using regular expressions
    static ShortcutSegments splitShortcut(const QString &shortcut)
    {
        ShortcutSegments segments;
        if (!shortcut.isEmpty()) {
            const int shortcutLength = shortcut.length();
            int start = 0;
            for (int i = 0; i < shortcutLength; ++i) {
                const QChar c = shortcut.at(i);
                if (c == QLatin1Char('+')) {
                    if (i > start) {
                        segments << shortcut.mid(start, i - start);
                    }
                    segments << shortcut.at(i);
                    start = i + 1;
                } else if (c == QLatin1Char(',')) {
                    if (i > start) {
                        segments << shortcut.mid(start, i - start);
                        start = i;
                    }
                    const int j = i + 1;
                    if (j < shortcutLength && shortcut.at(j) == QLatin1Char(' ')) {
                        segments << shortcut.mid(start, j - start + 1);
                        i = j;
                    } else {
                        segments << shortcut.at(i);
                    }
                    start = i + 1;
                }
            }
            if (start < shortcutLength) {
                segments << shortcut.mid(start);
            }

            // check we have successfully parsed the shortcut
            if (segments.isEmpty()) {
                qCWarning(KCONFIG_WIDGETS_LOG) << "Splitting shortcut failed" << shortcut;
            }
        }

        return segments;
    }

    // returns the width needed to draw the shortcut
    static int shortcutDrawingWidth(const QStyleOptionViewItem &option, const ShortcutSegments &shortcutSegments, int hMargin)
    {
        int width = 0;
        if (!shortcutSegments.isEmpty()) {
            width = option.fontMetrics.horizontalAdvance(shortcutSegments.join(QString()));

            // add left and right margins for each segment
            width += shortcutSegments.count() * 2 * hMargin;
        }

        return width;
    }

    int horizontalMargin(const QStyleOptionViewItem &option) const
    {
        return option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option) + 2;
    }
};

// BEGIN KCommandBarPrivate
class KCommandBarPrivate
{
public:
    QTreeView m_treeView;
    QLineEdit m_lineEdit;
    KCommandBarModel m_model;
    CommandBarFilterModel m_proxyModel;

    /*
     * selects first item in treeview
     */
    void reselectFirst()
    {
        const QModelIndex index = m_proxyModel.index(0, 0);
        m_treeView.setCurrentIndex(index);
    }

    /*
     * blocks signals before clearing line edit to ensure
     * we don't trigger filtering / sorting
     */
    void clearLineEdit()
    {
        const QSignalBlocker blocker(m_lineEdit);
        m_lineEdit.clear();
    }

    void slotReturnPressed(KCommandBar *q);

    void setLastUsedActions();

    QStringList lastUsedActions() const;
};

void KCommandBarPrivate::slotReturnPressed(KCommandBar *q)
{
    auto act = m_proxyModel.data(m_treeView.currentIndex(), Qt::UserRole).value<QAction *>();
    if (act) {
        // if the action is a menu, we take all its actions
        // and reload our dialog with these instead.
        if (auto menu = act->menu()) {
            auto menuActions = menu->actions();
            KCommandBar::ActionGroup ag;

            // if there are no actions, trigger load actions
            // this happens with some menus that are loaded on demand
            if (menuActions.size() == 0) {
                Q_EMIT menu->aboutToShow();
                ag.actions = menu->actions();
            }

            QString groupName = KLocalizedString::removeAcceleratorMarker(act->text());
            ag.name = groupName;

            m_model.refresh({ag});
            reselectFirst();
            /*
             *  We want the "textChanged" signal here
             *  so that proxy model triggers filtering again
             *  so don't use d->clearLineEdit()
             */
            m_lineEdit.clear();
            return;
        } else {
            m_model.actionTriggered(act->text());
            q->hide();
            act->trigger();
        }
    }

    clearLineEdit();
    q->hide();
    q->deleteLater();
}

void KCommandBarPrivate::setLastUsedActions()
{
    auto cfg = KSharedConfig::openStateConfig();
    KConfigGroup cg(cfg, QStringLiteral("General"));

    QStringList actionNames = cg.readEntry(QStringLiteral("CommandBarLastUsedActions"), QStringList());

    return m_model.setLastUsedActions(actionNames);
}

QStringList KCommandBarPrivate::lastUsedActions() const
{
    return m_model.lastUsedActions();
}
// END KCommandBarPrivate

// BEGIN KCommandBar
KCommandBar::KCommandBar(QWidget *parent)
    : QFrame(parent)
    , d(new KCommandBarPrivate)
{
    setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
    setProperty("_breeze_force_frame", true);

    QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect(this);
    e->setColor(palette().color(QPalette::Dark));
    e->setOffset(0, 4);
    e->setBlurRadius(48);
    setGraphicsEffect(e);

    QVBoxLayout *layout = new QVBoxLayout();
    layout->setSpacing(0);
    layout->setContentsMargins(QMargins());
    setLayout(layout);

    layout->addWidget(&d->m_lineEdit);
    d->m_lineEdit.setClearButtonEnabled(true);
    d->m_lineEdit.addAction(QIcon::fromTheme(QStringLiteral("search")), QLineEdit::LeadingPosition);
    d->m_lineEdit.setFrame(false);
    d->m_lineEdit.setTextMargins(QMargins() + style()->pixelMetric(QStyle::PM_ButtonMargin));
    setFocusProxy(&d->m_lineEdit);

    layout->addWidget(&d->m_treeView);
    d->m_treeView.setTextElideMode(Qt::ElideLeft);
    d->m_treeView.setUniformRowHeights(true);
    d->m_treeView.setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags(Qt::TopEdge)));

    CommandBarStyleDelegate *delegate = new CommandBarStyleDelegate(this);
    ShortcutStyleDelegate *del = new ShortcutStyleDelegate(this);
    d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Command, delegate);
    d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Shortcut, del);

    connect(&d->m_lineEdit, &QLineEdit::returnPressed, this, [this]() {
        d->slotReturnPressed(this);
    });
    connect(&d->m_lineEdit, &QLineEdit::textChanged, &d->m_proxyModel, &CommandBarFilterModel::setFilterString);
    connect(&d->m_lineEdit, &QLineEdit::textChanged, delegate, &CommandBarStyleDelegate::setFilterString);
    connect(&d->m_lineEdit, &QLineEdit::textChanged, this, [this]() {
        d->m_treeView.viewport()->update();
        d->reselectFirst();
    });
    connect(&d->m_treeView, &QTreeView::clicked, this, [this]() {
        d->slotReturnPressed(this);
    });

    d->m_proxyModel.setSourceModel(&d->m_model);
    d->m_treeView.setSortingEnabled(true);
    d->m_treeView.setModel(&d->m_proxyModel);

    d->m_treeView.header()->setMinimumSectionSize(0);
    d->m_treeView.header()->setStretchLastSection(false);
    d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Command, QHeaderView::Stretch);
    d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Shortcut, QHeaderView::ResizeToContents);

    parent->installEventFilter(this);
    d->m_treeView.installEventFilter(this);
    d->m_lineEdit.installEventFilter(this);

    d->m_treeView.setHeaderHidden(true);
    d->m_treeView.setRootIsDecorated(false);
    d->m_treeView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    d->m_treeView.setSelectionMode(QTreeView::SingleSelection);

    QLabel *placeholderLabel = new QLabel;
    placeholderLabel->setAlignment(Qt::AlignCenter);
    placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
    placeholderLabel->setWordWrap(true);
    // To match the size of a level 2 Heading/KTitleWidget
    QFont placeholderLabelFont = placeholderLabel->font();
    placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
    placeholderLabel->setFont(placeholderLabelFont);
    // Match opacity of QML placeholder label component
    QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(placeholderLabel);
    opacityEffect->setOpacity(0.5);
    placeholderLabel->setGraphicsEffect(opacityEffect);

    QHBoxLayout *placeholderLayout = new QHBoxLayout;
    placeholderLayout->addWidget(placeholderLabel);
    d->m_treeView.setLayout(placeholderLayout);

    connect(&d->m_proxyModel, &CommandBarFilterModel::modelReset, this, [this, placeholderLabel]() {
        if (d->m_proxyModel.rowCount() > 0) {
            placeholderLabel->hide();
        } else {
            if (d->m_model.rowCount() == 0) {
                placeholderLabel->setText(i18n("No commands to display"));
            } else {
                placeholderLabel->setText(i18n("No commands matching the filter"));
            }
            placeholderLabel->show();
        }
    });

    setHidden(true);

    // Migrate last used action config to new location
    KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
    if (cg.hasKey("CommandBarLastUsedActions")) {
        const QStringList actionNames = cg.readEntry("CommandBarLastUsedActions", QStringList());

        KConfigGroup stateCg(KSharedConfig::openStateConfig(), QStringLiteral("General"));
        stateCg.writeEntry(QStringLiteral("CommandBarLastUsedActions"), actionNames);

        cg.deleteEntry(QStringLiteral("CommandBarLastUsedActions"));
    }
}

/*
 * Destructor defined here to make unique_ptr work
 */
KCommandBar::~KCommandBar()
{
    auto lastUsedActions = d->lastUsedActions();
    auto cfg = KSharedConfig::openStateConfig();
    KConfigGroup cg(cfg, QStringLiteral("General"));
    cg.writeEntry("CommandBarLastUsedActions", lastUsedActions);

    // Explicitly remove installed event filters of children of d-pointer
    // class, otherwise while KCommandBar is being torn down, an event could
    // fire and the eventFilter() accesses d, which would cause a crash
    // bug 452527
    d->m_treeView.removeEventFilter(this);
    d->m_lineEdit.removeEventFilter(this);
}

void KCommandBar::setActions(const QList<ActionGroup> &actions)
{
    // First set last used actions in the model
    d->setLastUsedActions();

    d->m_model.refresh(actions);
    d->reselectFirst();

    show();
    setFocus();
}

void KCommandBar::show()
{
    const QRect boundingRect = getCommandBarBoundingRect(this);

    static constexpr int minWidth = 500;
    const int maxWidth = boundingRect.width();
    const int preferredWidth = maxWidth / 2.4;

    static constexpr int minHeight = 250;
    const int maxHeight = boundingRect.height();
    const int preferredHeight = maxHeight / 2;

    const QSize size{std::min(maxWidth, std::max(preferredWidth, minWidth)), std::min(maxHeight, std::max(preferredHeight, minHeight))};

    setFixedSize(size);

    // set the position to the top-center of the parent
    // just below the menubar/toolbar (if any)
    const QPoint position{boundingRect.center().x() - size.width() / 2, boundingRect.y() + 6};
    move(position);

    QWidget::show();
}

bool KCommandBar::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (obj == &d->m_lineEdit) {
            const int key = keyEvent->key();
            const bool forward2list = (key == Qt::Key_Up) || (key == Qt::Key_Down) || (key == Qt::Key_PageUp) || (key == Qt::Key_PageDown);
            if (forward2list) {
                QCoreApplication::sendEvent(&d->m_treeView, event);
                return true;
            }
        } else if (obj == &d->m_treeView) {
            const int key = keyEvent->key();
            const bool forward2input = (key != Qt::Key_Up) && (key != Qt::Key_Down) && (key != Qt::Key_PageUp) && (key != Qt::Key_PageDown)
                && (key != Qt::Key_Tab) && (key != Qt::Key_Backtab);
            if (forward2input) {
                QCoreApplication::sendEvent(&d->m_lineEdit, event);
                return true;
            }
        }

        if (keyEvent->key() == Qt::Key_Escape) {
            if (event->type() == QEvent::ShortcutOverride) {
                // Override the shortcut. We will get KeyPress event for Key_Escape
                // later and then hide
                return true;
            }
            hide();
            deleteLater();
            return true;
        }
    }

    // hide on focus out, if neither input field nor list have focus!
    else if (event->type() == QEvent::FocusOut && isVisible() && !(d->m_lineEdit.hasFocus() || d->m_treeView.hasFocus())) {
        d->clearLineEdit();
        deleteLater();
        hide();
        return true;
    }

    // handle resizing
    if (parent() == obj && event->type() == QEvent::Resize) {
        show();
    }

    return QWidget::eventFilter(obj, event);
}
// END KCommandBar

#include "kcommandbar.moc"
#include "moc_kcommandbar.cpp"
