File indexing completed on 2024-04-21 14:54:18

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