File indexing completed on 2023-09-24 07:59:34
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"