File indexing completed on 2024-03-24 03:56:07

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "kcommandbar.h"
0007 #include "kcommandbarmodel_p.h"
0008 #include "kconfigwidgets_debug.h"
0009 
0010 #include <QAction>
0011 #include <QCoreApplication>
0012 #include <QGraphicsOpacityEffect>
0013 #include <QHeaderView>
0014 #include <QKeyEvent>
0015 #include <QLabel>
0016 #include <QLineEdit>
0017 #include <QMainWindow>
0018 #include <QMenu>
0019 #include <QPainter>
0020 #include <QPointer>
0021 #include <QScreen>
0022 #include <QSortFilterProxyModel>
0023 #include <QStatusBar>
0024 #include <QStyledItemDelegate>
0025 #include <QTextLayout>
0026 #include <QToolBar>
0027 #include <QTreeView>
0028 #include <QVBoxLayout>
0029 
0030 #include <KConfigGroup>
0031 #include <KFuzzyMatcher>
0032 #include <KLocalizedString>
0033 #include <KSharedConfig>
0034 
0035 static QRect getCommandBarBoundingRect(KCommandBar *commandBar)
0036 {
0037     QWidget *parentWidget = commandBar->parentWidget();
0038     Q_ASSERT(parentWidget);
0039 
0040     const QMainWindow *mainWindow = qobject_cast<const QMainWindow *>(parentWidget);
0041     if (!mainWindow) {
0042         return parentWidget->geometry();
0043     }
0044 
0045     QRect boundingRect = mainWindow->contentsRect();
0046 
0047     // exclude the menu bar from the bounding rect
0048     if (const QWidget *menuWidget = mainWindow->menuWidget()) {
0049         if (!menuWidget->isHidden()) {
0050             boundingRect.setTop(boundingRect.top() + menuWidget->height());
0051         }
0052     }
0053 
0054     // exclude the status bar from the bounding rect
0055     if (const QStatusBar *statusBar = mainWindow->findChild<QStatusBar *>()) {
0056         if (!statusBar->isHidden()) {
0057             boundingRect.setBottom(boundingRect.bottom() - statusBar->height());
0058         }
0059     }
0060 
0061     // exclude any undocked toolbar from the bounding rect
0062     const QList<QToolBar *> toolBars = mainWindow->findChildren<QToolBar *>();
0063     for (QToolBar *toolBar : toolBars) {
0064         if (toolBar->isHidden() || toolBar->isFloating()) {
0065             continue;
0066         }
0067 
0068         switch (mainWindow->toolBarArea(toolBar)) {
0069         case Qt::TopToolBarArea:
0070             boundingRect.setTop(std::max(boundingRect.top(), toolBar->geometry().bottom()));
0071             break;
0072         case Qt::RightToolBarArea:
0073             boundingRect.setRight(std::min(boundingRect.right(), toolBar->geometry().left()));
0074             break;
0075         case Qt::BottomToolBarArea:
0076             boundingRect.setBottom(std::min(boundingRect.bottom(), toolBar->geometry().top()));
0077             break;
0078         case Qt::LeftToolBarArea:
0079             boundingRect.setLeft(std::max(boundingRect.left(), toolBar->geometry().right()));
0080             break;
0081         default:
0082             break;
0083         }
0084     }
0085 
0086     return boundingRect;
0087 }
0088 
0089 // BEGIN CommandBarFilterModel
0090 class CommandBarFilterModel final : public QSortFilterProxyModel
0091 {
0092 public:
0093     CommandBarFilterModel(QObject *parent = nullptr)
0094         : QSortFilterProxyModel(parent)
0095     {
0096         connect(this, &CommandBarFilterModel::modelAboutToBeReset, this, [this]() {
0097             m_hasActionsWithIcons = false;
0098         });
0099     }
0100 
0101     bool hasActionsWithIcons() const
0102     {
0103         return m_hasActionsWithIcons;
0104     }
0105 
0106     Q_SLOT void setFilterString(const QString &string)
0107     {
0108         // MUST reset the model here, we want to repopulate
0109         // invalidateFilter() will not work here
0110         beginResetModel();
0111         m_pattern = string;
0112         endResetModel();
0113     }
0114 
0115 protected:
0116     bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
0117     {
0118         const int scoreLeft = sourceLeft.data(KCommandBarModel::Score).toInt();
0119         const int scoreRight = sourceRight.data(KCommandBarModel::Score).toInt();
0120         if (scoreLeft == scoreRight) {
0121             const QString textLeft = sourceLeft.data().toString();
0122             const QString textRight = sourceRight.data().toString();
0123 
0124             return textRight.localeAwareCompare(textLeft) < 0;
0125         }
0126 
0127         return scoreLeft < scoreRight;
0128     }
0129 
0130     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
0131     {
0132         const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
0133 
0134         bool accept = false;
0135         if (m_pattern.isEmpty()) {
0136             accept = true;
0137         } else {
0138             const QString row = index.data(Qt::DisplayRole).toString();
0139             KFuzzyMatcher::Result resAction = KFuzzyMatcher::match(m_pattern, row);
0140             sourceModel()->setData(index, resAction.score, KCommandBarModel::Score);
0141             accept = resAction.matched;
0142         }
0143 
0144         if (accept && !m_hasActionsWithIcons) {
0145             m_hasActionsWithIcons |= !index.data(Qt::DecorationRole).isNull();
0146         }
0147 
0148         return accept;
0149     }
0150 
0151 private:
0152     QString m_pattern;
0153     mutable bool m_hasActionsWithIcons = false;
0154 };
0155 // END CommandBarFilterModel
0156 
0157 class CommandBarStyleDelegate final : public QStyledItemDelegate
0158 {
0159 public:
0160     CommandBarStyleDelegate(QObject *parent = nullptr)
0161         : QStyledItemDelegate(parent)
0162     {
0163     }
0164 
0165     /**
0166      * Paints a single item's text
0167      */
0168     static void
0169     paintItemText(QPainter *p, const QString &textt, const QRect &rect, const QStyleOptionViewItem &options, QList<QTextLayout::FormatRange> formats)
0170     {
0171         QString text = options.fontMetrics.elidedText(textt, Qt::ElideRight, rect.width());
0172 
0173         // set formats and font
0174         QTextLayout textLayout(text, options.font);
0175         formats.append(textLayout.formats());
0176         textLayout.setFormats(formats);
0177 
0178         // set alignment, rtls etc
0179         QTextOption textOption;
0180         textOption.setTextDirection(options.direction);
0181         textOption.setAlignment(QStyle::visualAlignment(options.direction, options.displayAlignment));
0182         textLayout.setTextOption(textOption);
0183 
0184         // layout the text
0185         textLayout.beginLayout();
0186 
0187         QTextLine line = textLayout.createLine();
0188         if (!line.isValid()) {
0189             return;
0190         }
0191 
0192         const int lineWidth = rect.width();
0193         line.setLineWidth(lineWidth);
0194         line.setPosition(QPointF(0, 0));
0195 
0196         textLayout.endLayout();
0197 
0198         /**
0199          * get "Y" so that we can properly V-Center align the text in row
0200          */
0201         const int y = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignVCenter, textLayout.boundingRect().size().toSize(), rect).y();
0202 
0203         // draw the text
0204         const QPointF pos(rect.x(), y);
0205         textLayout.draw(p, pos);
0206     }
0207 
0208     void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
0209     {
0210         painter->save();
0211 
0212         /**
0213          * Draw everything, (widget, icon etc) except the text
0214          */
0215         QStyleOptionViewItem option = opt;
0216         initStyleOption(&option, index);
0217         option.text.clear(); // clear old text
0218         QStyle *style = option.widget->style();
0219         style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
0220 
0221         const int hMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, option.widget);
0222 
0223         QRect textRect = option.rect;
0224 
0225         const CommandBarFilterModel *model = static_cast<const CommandBarFilterModel *>(index.model());
0226         if (model->hasActionsWithIcons()) {
0227             const int iconWidth = option.decorationSize.width() + (hMargin * 2);
0228             if (option.direction == Qt::RightToLeft) {
0229                 textRect.adjust(0, 0, -iconWidth, 0);
0230             } else {
0231                 textRect.adjust(iconWidth, 0, 0, 0);
0232             }
0233         }
0234 
0235         const QString original = index.data().toString();
0236         QStringView str = original;
0237         int componentIdx = original.indexOf(QLatin1Char(':'));
0238         int actionNameStart = 0;
0239         if (componentIdx > 0) {
0240             actionNameStart = componentIdx + 2;
0241             // + 2 because there is a space after colon
0242             str = str.mid(actionNameStart);
0243         }
0244 
0245         QList<QTextLayout::FormatRange> formats;
0246         if (componentIdx > 0) {
0247             QTextCharFormat gray;
0248             gray.setForeground(option.palette.placeholderText());
0249             formats.append({0, componentIdx, gray});
0250         }
0251 
0252         QTextCharFormat fmt;
0253         fmt.setForeground(option.palette.link());
0254         fmt.setFontWeight(QFont::Bold);
0255 
0256         /**
0257          * Highlight matches from fuzzy matcher
0258          */
0259         const auto fmtRanges = KFuzzyMatcher::matchedRanges(m_filterString, str);
0260         QTextCharFormat f;
0261         f.setForeground(option.palette.link());
0262         formats.reserve(formats.size() + fmtRanges.size());
0263         std::transform(fmtRanges.begin(), fmtRanges.end(), std::back_inserter(formats), [f, actionNameStart](const KFuzzyMatcher::Range &fr) {
0264             return QTextLayout::FormatRange{fr.start + actionNameStart, fr.length, f};
0265         });
0266 
0267         textRect.adjust(hMargin, 0, -hMargin, 0);
0268         paintItemText(painter, original, textRect, option, std::move(formats));
0269 
0270         painter->restore();
0271     }
0272 
0273 public Q_SLOTS:
0274     void setFilterString(const QString &text)
0275     {
0276         m_filterString = text;
0277     }
0278 
0279 private:
0280     QString m_filterString;
0281 };
0282 
0283 class ShortcutStyleDelegate final : public QStyledItemDelegate
0284 {
0285 public:
0286     ShortcutStyleDelegate(QObject *parent = nullptr)
0287         : QStyledItemDelegate(parent)
0288     {
0289     }
0290 
0291     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0292     {
0293         // draw background
0294         option.widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
0295 
0296         const QString shortcutString = index.data().toString();
0297         if (shortcutString.isEmpty()) {
0298             return;
0299         }
0300 
0301         const ShortcutSegments shortcutSegments = splitShortcut(shortcutString);
0302         if (shortcutSegments.isEmpty()) {
0303             return;
0304         }
0305 
0306         struct Button {
0307             int textWidth;
0308             QString text;
0309         };
0310 
0311         // compute the width of each shortcut segment
0312         QList<Button> btns;
0313         btns.reserve(shortcutSegments.count());
0314         const int hMargin = horizontalMargin(option);
0315         for (const QString &text : shortcutSegments) {
0316             int textWidth = option.fontMetrics.horizontalAdvance(text);
0317             textWidth += 2 * hMargin;
0318             btns.append({textWidth, text});
0319         }
0320 
0321         int textHeight = option.fontMetrics.lineSpacing();
0322         // this happens on gnome so we manually decrease the height a bit
0323         if (textHeight == option.rect.height()) {
0324             textHeight -= 4;
0325         }
0326 
0327         const int y = option.rect.y() + (option.rect.height() - textHeight) / 2;
0328         int x;
0329         if (option.direction == Qt::RightToLeft) {
0330             x = option.rect.x() + hMargin;
0331         } else {
0332             x = option.rect.right() - shortcutDrawingWidth(option, shortcutSegments, hMargin) - hMargin;
0333         }
0334 
0335         painter->save();
0336         painter->setPen(option.palette.buttonText().color());
0337         painter->setRenderHint(QPainter::Antialiasing);
0338         for (int i = 0, n = btns.count(); i < n; ++i) {
0339             const Button &button = btns.at(i);
0340 
0341             QRect outputRect(x, y, button.textWidth, textHeight);
0342 
0343             // an even element indicates that it is a key
0344             if (i % 2 == 0) {
0345                 painter->save();
0346                 painter->setPen(Qt::NoPen);
0347 
0348                 // draw rounded rect shadow
0349                 auto shadowRect = outputRect.translated(0, 1);
0350                 painter->setBrush(option.palette.shadow());
0351                 painter->drawRoundedRect(shadowRect, 3.0, 3.0);
0352 
0353                 // draw rounded rect itself
0354                 painter->setBrush(option.palette.window());
0355                 painter->drawRoundedRect(outputRect, 3.0, 3.0);
0356 
0357                 painter->restore();
0358             }
0359 
0360             // draw shortcut segment
0361             painter->drawText(outputRect, Qt::AlignCenter, button.text);
0362 
0363             x += outputRect.width();
0364         }
0365 
0366         painter->restore();
0367     }
0368 
0369     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
0370     {
0371         if (index.isValid() && index.column() == KCommandBarModel::Column_Shortcut) {
0372             const QString shortcut = index.data().toString();
0373             if (!shortcut.isEmpty()) {
0374                 const ShortcutSegments shortcutSegments = splitShortcut(shortcut);
0375                 if (!shortcutSegments.isEmpty()) {
0376                     const int hMargin = horizontalMargin(option);
0377                     int width = shortcutDrawingWidth(option, shortcutSegments, hMargin);
0378 
0379                     // add left and right margins
0380                     width += 2 * hMargin;
0381 
0382                     return QSize(width, 0);
0383                 }
0384             }
0385         }
0386 
0387         return QStyledItemDelegate::sizeHint(option, index);
0388     }
0389 
0390 private:
0391     using ShortcutSegments = QStringList;
0392 
0393     // split shortcut into segments i.e. will return
0394     // ["Ctrl", "+", "A", ", ", "Ctrl", "+", "K"] for "Ctrl+A, Ctrl+K"
0395     // twice as fast as using regular expressions
0396     static ShortcutSegments splitShortcut(const QString &shortcut)
0397     {
0398         ShortcutSegments segments;
0399         if (!shortcut.isEmpty()) {
0400             const int shortcutLength = shortcut.length();
0401             int start = 0;
0402             for (int i = 0; i < shortcutLength; ++i) {
0403                 const QChar c = shortcut.at(i);
0404                 if (c == QLatin1Char('+')) {
0405                     if (i > start) {
0406                         segments << shortcut.mid(start, i - start);
0407                     }
0408                     segments << shortcut.at(i);
0409                     start = i + 1;
0410                 } else if (c == QLatin1Char(',')) {
0411                     if (i > start) {
0412                         segments << shortcut.mid(start, i - start);
0413                         start = i;
0414                     }
0415                     const int j = i + 1;
0416                     if (j < shortcutLength && shortcut.at(j) == QLatin1Char(' ')) {
0417                         segments << shortcut.mid(start, j - start + 1);
0418                         i = j;
0419                     } else {
0420                         segments << shortcut.at(i);
0421                     }
0422                     start = i + 1;
0423                 }
0424             }
0425             if (start < shortcutLength) {
0426                 segments << shortcut.mid(start);
0427             }
0428 
0429             // check we have successfully parsed the shortcut
0430             if (segments.isEmpty()) {
0431                 qCWarning(KCONFIG_WIDGETS_LOG) << "Splitting shortcut failed" << shortcut;
0432             }
0433         }
0434 
0435         return segments;
0436     }
0437 
0438     // returns the width needed to draw the shortcut
0439     static int shortcutDrawingWidth(const QStyleOptionViewItem &option, const ShortcutSegments &shortcutSegments, int hMargin)
0440     {
0441         int width = 0;
0442         if (!shortcutSegments.isEmpty()) {
0443             width = option.fontMetrics.horizontalAdvance(shortcutSegments.join(QString()));
0444 
0445             // add left and right margins for each segment
0446             width += shortcutSegments.count() * 2 * hMargin;
0447         }
0448 
0449         return width;
0450     }
0451 
0452     int horizontalMargin(const QStyleOptionViewItem &option) const
0453     {
0454         return option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option) + 2;
0455     }
0456 };
0457 
0458 // BEGIN KCommandBarPrivate
0459 class KCommandBarPrivate
0460 {
0461 public:
0462     QTreeView m_treeView;
0463     QLineEdit m_lineEdit;
0464     KCommandBarModel m_model;
0465     CommandBarFilterModel m_proxyModel;
0466 
0467     /**
0468      * selects first item in treeview
0469      */
0470     void reselectFirst()
0471     {
0472         const QModelIndex index = m_proxyModel.index(0, 0);
0473         m_treeView.setCurrentIndex(index);
0474     }
0475 
0476     /**
0477      * blocks signals before clearing line edit to ensure
0478      * we don't trigger filtering / sorting
0479      */
0480     void clearLineEdit()
0481     {
0482         const QSignalBlocker blocker(m_lineEdit);
0483         m_lineEdit.clear();
0484     }
0485 
0486     void slotReturnPressed(KCommandBar *q);
0487 
0488     void setLastUsedActions();
0489 
0490     QStringList lastUsedActions() const;
0491 };
0492 
0493 void KCommandBarPrivate::slotReturnPressed(KCommandBar *q)
0494 {
0495     QPointer<KCommandBar> crashGuard = q;
0496     auto act = m_proxyModel.data(m_treeView.currentIndex(), Qt::UserRole).value<QAction *>();
0497     if (act) {
0498         // if the action is a menu, we take all its actions
0499         // and reload our dialog with these instead.
0500         if (auto menu = act->menu()) {
0501             auto menuActions = menu->actions();
0502             KCommandBar::ActionGroup ag;
0503 
0504             // if there are no actions, trigger load actions
0505             // this happens with some menus that are loaded on demand
0506             if (menuActions.size() == 0) {
0507                 Q_EMIT menu->aboutToShow();
0508                 ag.actions = menu->actions();
0509             }
0510 
0511             QString groupName = KLocalizedString::removeAcceleratorMarker(act->text());
0512             ag.name = groupName;
0513 
0514             m_model.refresh({ag});
0515             reselectFirst();
0516             /**
0517              *  We want the "textChanged" signal here
0518              *  so that proxy model triggers filtering again
0519              *  so don't use d->clearLineEdit()
0520              */
0521             m_lineEdit.clear();
0522             return;
0523         } else {
0524             m_model.actionTriggered(act->text());
0525             act->trigger();
0526         }
0527     }
0528 
0529     // We might trigger an action that triggers focus change
0530     // resulting in us getting deleted
0531     if (crashGuard) {
0532         clearLineEdit();
0533         q->hide();
0534         q->deleteLater();
0535     }
0536 }
0537 
0538 void KCommandBarPrivate::setLastUsedActions()
0539 {
0540     auto cfg = KSharedConfig::openStateConfig();
0541     KConfigGroup cg(cfg, QStringLiteral("General"));
0542 
0543     QStringList actionNames = cg.readEntry(QStringLiteral("CommandBarLastUsedActions"), QStringList());
0544 
0545     return m_model.setLastUsedActions(actionNames);
0546 }
0547 
0548 QStringList KCommandBarPrivate::lastUsedActions() const
0549 {
0550     return m_model.lastUsedActions();
0551 }
0552 // END KCommandBarPrivate
0553 
0554 // BEGIN KCommandBar
0555 KCommandBar::KCommandBar(QWidget *parent)
0556     : QFrame(parent)
0557     , d(new KCommandBarPrivate)
0558 {
0559     QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect(this);
0560     e->setColor(palette().color(QPalette::Shadow));
0561     e->setOffset(2.);
0562     e->setBlurRadius(8.);
0563     setGraphicsEffect(e);
0564 
0565     setAutoFillBackground(true);
0566     setFrameShadow(QFrame::Raised);
0567     setFrameShape(QFrame::Box);
0568 
0569     QVBoxLayout *layout = new QVBoxLayout();
0570     layout->setSpacing(0);
0571     layout->setContentsMargins(2, 2, 2, 2);
0572     setLayout(layout);
0573 
0574     setFocusProxy(&d->m_lineEdit);
0575 
0576     layout->addWidget(&d->m_lineEdit);
0577 
0578     layout->addWidget(&d->m_treeView);
0579     d->m_treeView.setTextElideMode(Qt::ElideLeft);
0580     d->m_treeView.setUniformRowHeights(true);
0581 
0582     CommandBarStyleDelegate *delegate = new CommandBarStyleDelegate(this);
0583     ShortcutStyleDelegate *del = new ShortcutStyleDelegate(this);
0584     d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Command, delegate);
0585     d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Shortcut, del);
0586 
0587     connect(&d->m_lineEdit, &QLineEdit::returnPressed, this, [this]() {
0588         d->slotReturnPressed(this);
0589     });
0590     connect(&d->m_lineEdit, &QLineEdit::textChanged, &d->m_proxyModel, &CommandBarFilterModel::setFilterString);
0591     connect(&d->m_lineEdit, &QLineEdit::textChanged, delegate, &CommandBarStyleDelegate::setFilterString);
0592     connect(&d->m_lineEdit, &QLineEdit::textChanged, this, [this]() {
0593         d->m_treeView.viewport()->update();
0594         d->reselectFirst();
0595     });
0596     connect(&d->m_treeView, &QTreeView::clicked, this, [this]() {
0597         d->slotReturnPressed(this);
0598     });
0599 
0600     d->m_proxyModel.setSourceModel(&d->m_model);
0601     d->m_treeView.setSortingEnabled(true);
0602     d->m_treeView.setModel(&d->m_proxyModel);
0603 
0604     d->m_treeView.header()->setMinimumSectionSize(0);
0605     d->m_treeView.header()->setStretchLastSection(false);
0606     d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Command, QHeaderView::Stretch);
0607     d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Shortcut, QHeaderView::ResizeToContents);
0608 
0609     parent->installEventFilter(this);
0610     d->m_treeView.installEventFilter(this);
0611     d->m_lineEdit.installEventFilter(this);
0612 
0613     d->m_treeView.setHeaderHidden(true);
0614     d->m_treeView.setRootIsDecorated(false);
0615     d->m_treeView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0616     d->m_treeView.setSelectionMode(QTreeView::SingleSelection);
0617 
0618     QLabel *placeholderLabel = new QLabel;
0619     placeholderLabel->setAlignment(Qt::AlignCenter);
0620     placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
0621     placeholderLabel->setWordWrap(true);
0622     placeholderLabel->setText(i18n("No commands matching the filter"));
0623     // To match the size of a level 2 Heading/KTitleWidget
0624     QFont placeholderLabelFont = placeholderLabel->font();
0625     placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
0626     placeholderLabel->setFont(placeholderLabelFont);
0627     // Match opacity of QML placeholder label component
0628     QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(placeholderLabel);
0629     opacityEffect->setOpacity(0.5);
0630     placeholderLabel->setGraphicsEffect(opacityEffect);
0631 
0632     QHBoxLayout *placeholderLayout = new QHBoxLayout;
0633     placeholderLayout->addWidget(placeholderLabel);
0634     d->m_treeView.setLayout(placeholderLayout);
0635 
0636     connect(&d->m_proxyModel, &CommandBarFilterModel::modelReset, this, [this, placeholderLabel]() {
0637         placeholderLabel->setHidden(d->m_proxyModel.rowCount() > 0);
0638     });
0639 
0640     setHidden(true);
0641 
0642     // Migrate last used action config to new location
0643     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
0644     if (cg.hasKey("CommandBarLastUsedActions")) {
0645         const QStringList actionNames = cg.readEntry("CommandBarLastUsedActions", QStringList());
0646 
0647         KConfigGroup stateCg(KSharedConfig::openStateConfig(), QStringLiteral("General"));
0648         stateCg.writeEntry(QStringLiteral("CommandBarLastUsedActions"), actionNames);
0649 
0650         cg.deleteEntry(QStringLiteral("CommandBarLastUsedActions"));
0651     }
0652 }
0653 
0654 /**
0655  * Destructor defined here to make unique_ptr work
0656  */
0657 KCommandBar::~KCommandBar()
0658 {
0659     auto lastUsedActions = d->lastUsedActions();
0660     auto cfg = KSharedConfig::openStateConfig();
0661     KConfigGroup cg(cfg, QStringLiteral("General"));
0662     cg.writeEntry("CommandBarLastUsedActions", lastUsedActions);
0663 
0664     // Explicitly remove installed event filters of children of d-pointer
0665     // class, otherwise while KCommandBar is being torn down, an event could
0666     // fire and the eventFilter() accesses d, which would cause a crash
0667     // bug 452527
0668     d->m_treeView.removeEventFilter(this);
0669     d->m_lineEdit.removeEventFilter(this);
0670 }
0671 
0672 void KCommandBar::setActions(const QList<ActionGroup> &actions)
0673 {
0674     // First set last used actions in the model
0675     d->setLastUsedActions();
0676 
0677     d->m_model.refresh(actions);
0678     d->reselectFirst();
0679 
0680     show();
0681     setFocus();
0682 }
0683 
0684 void KCommandBar::show()
0685 {
0686     const QRect boundingRect = getCommandBarBoundingRect(this);
0687 
0688     static constexpr int minWidth = 500;
0689     const int maxWidth = boundingRect.width();
0690     const int preferredWidth = maxWidth / 2.4;
0691 
0692     static constexpr int minHeight = 250;
0693     const int maxHeight = boundingRect.height();
0694     const int preferredHeight = maxHeight / 2;
0695 
0696     const QSize size{std::min(maxWidth, std::max(preferredWidth, minWidth)), std::min(maxHeight, std::max(preferredHeight, minHeight))};
0697 
0698     setFixedSize(size);
0699 
0700     // set the position to the top-center of the parent
0701     // just below the menubar/toolbar (if any)
0702     const QPoint position{boundingRect.center().x() - size.width() / 2, boundingRect.y()};
0703     move(position);
0704 
0705     QWidget::show();
0706 }
0707 
0708 bool KCommandBar::eventFilter(QObject *obj, QEvent *event)
0709 {
0710     if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
0711         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0712         if (obj == &d->m_lineEdit) {
0713             const int key = keyEvent->key();
0714             const bool forward2list = (key == Qt::Key_Up) || (key == Qt::Key_Down) || (key == Qt::Key_PageUp) || (key == Qt::Key_PageDown);
0715             if (forward2list) {
0716                 QCoreApplication::sendEvent(&d->m_treeView, event);
0717                 return true;
0718             }
0719         } else if (obj == &d->m_treeView) {
0720             const int key = keyEvent->key();
0721             const bool forward2input = (key != Qt::Key_Up) && (key != Qt::Key_Down) && (key != Qt::Key_PageUp) && (key != Qt::Key_PageDown)
0722                 && (key != Qt::Key_Tab) && (key != Qt::Key_Backtab);
0723             if (forward2input) {
0724                 QCoreApplication::sendEvent(&d->m_lineEdit, event);
0725                 return true;
0726             }
0727         }
0728 
0729         if (keyEvent->key() == Qt::Key_Escape) {
0730             hide();
0731             deleteLater();
0732             return true;
0733         }
0734     }
0735 
0736     // hide on focus out, if neither input field nor list have focus!
0737     else if (event->type() == QEvent::FocusOut && !(d->m_lineEdit.hasFocus() || d->m_treeView.hasFocus())) {
0738         d->clearLineEdit();
0739         deleteLater();
0740         hide();
0741         return true;
0742     }
0743 
0744     // handle resizing
0745     if (parent() == obj && event->type() == QEvent::Resize) {
0746         show();
0747     }
0748 
0749     return QWidget::eventFilter(obj, event);
0750 }
0751 // END KCommandBar
0752 
0753 #include "moc_kcommandbar.cpp"