File indexing completed on 2023-11-26 10:42:12

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