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