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"