1/*
2 SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "kcommandbar.h"
7#include "kcommandbarmodel_p.h"
8#include "kconfigwidgets_debug.h"
9
10#include <QAction>
11#include <QCoreApplication>
12#include <QGraphicsOpacityEffect>
13#include <QHeaderView>
14#include <QKeyEvent>
15#include <QLabel>
16#include <QLineEdit>
17#include <QMainWindow>
18#include <QMenu>
19#include <QPainter>
20#include <QPointer>
21#include <QScreen>
22#include <QSortFilterProxyModel>
23#include <QStatusBar>
24#include <QStyledItemDelegate>
25#include <QTextLayout>
26#include <QToolBar>
27#include <QTreeView>
28#include <QVBoxLayout>
29
30#include <KConfigGroup>
31#include <KFuzzyMatcher>
32#include <KLocalizedString>
33#include <KSharedConfig>
34
35static QRect getCommandBarBoundingRect(KCommandBar *commandBar)
36{
37 QWidget *parentWidget = commandBar->parentWidget();
38 Q_ASSERT(parentWidget);
39
40 const QMainWindow *mainWindow = qobject_cast<const QMainWindow *>(object: parentWidget);
41 if (!mainWindow) {
42 return parentWidget->geometry();
43 }
44
45 QRect boundingRect = mainWindow->contentsRect();
46
47 // exclude the menu bar from the bounding rect
48 if (const QWidget *menuWidget = mainWindow->menuWidget()) {
49 if (!menuWidget->isHidden()) {
50 boundingRect.setTop(boundingRect.top() + menuWidget->height());
51 }
52 }
53
54 // exclude the status bar from the bounding rect
55 if (const QStatusBar *statusBar = mainWindow->findChild<QStatusBar *>()) {
56 if (!statusBar->isHidden()) {
57 boundingRect.setBottom(boundingRect.bottom() - statusBar->height());
58 }
59 }
60
61 // exclude any undocked toolbar from the bounding rect
62 const QList<QToolBar *> toolBars = mainWindow->findChildren<QToolBar *>();
63 for (QToolBar *toolBar : toolBars) {
64 if (toolBar->isHidden() || toolBar->isFloating()) {
65 continue;
66 }
67
68 switch (mainWindow->toolBarArea(toolbar: toolBar)) {
69 case Qt::TopToolBarArea:
70 boundingRect.setTop(std::max(a: boundingRect.top(), b: toolBar->geometry().bottom()));
71 break;
72 case Qt::RightToolBarArea:
73 boundingRect.setRight(std::min(a: boundingRect.right(), b: toolBar->geometry().left()));
74 break;
75 case Qt::BottomToolBarArea:
76 boundingRect.setBottom(std::min(a: boundingRect.bottom(), b: toolBar->geometry().top()));
77 break;
78 case Qt::LeftToolBarArea:
79 boundingRect.setLeft(std::max(a: boundingRect.left(), b: toolBar->geometry().right()));
80 break;
81 default:
82 break;
83 }
84 }
85
86 return boundingRect;
87}
88
89// BEGIN CommandBarFilterModel
90class CommandBarFilterModel final : public QSortFilterProxyModel
91{
92public:
93 CommandBarFilterModel(QObject *parent = nullptr)
94 : QSortFilterProxyModel(parent)
95 {
96 connect(sender: this, signal: &CommandBarFilterModel::modelAboutToBeReset, context: this, slot: [this]() {
97 m_hasActionsWithIcons = false;
98 });
99 }
100
101 bool hasActionsWithIcons() const
102 {
103 return m_hasActionsWithIcons;
104 }
105
106 Q_SLOT void setFilterString(const QString &string)
107 {
108 // MUST reset the model here, we want to repopulate
109 // invalidateFilter() will not work here
110 beginResetModel();
111 m_pattern = string;
112 endResetModel();
113 }
114
115protected:
116 bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
117 {
118 const int scoreLeft = sourceLeft.data(arole: KCommandBarModel::Score).toInt();
119 const int scoreRight = sourceRight.data(arole: KCommandBarModel::Score).toInt();
120 if (scoreLeft == scoreRight) {
121 const QString textLeft = sourceLeft.data().toString();
122 const QString textRight = sourceRight.data().toString();
123
124 return textRight.localeAwareCompare(s: textLeft) < 0;
125 }
126
127 return scoreLeft < scoreRight;
128 }
129
130 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
131 {
132 const QModelIndex index = sourceModel()->index(row: sourceRow, column: 0, parent: sourceParent);
133
134 bool accept = false;
135 if (m_pattern.isEmpty()) {
136 accept = true;
137 } else {
138 const QString row = index.data(arole: Qt::DisplayRole).toString();
139 KFuzzyMatcher::Result resAction = KFuzzyMatcher::match(pattern: m_pattern, str: row);
140 sourceModel()->setData(index, value: resAction.score, role: KCommandBarModel::Score);
141 accept = resAction.matched;
142 }
143
144 if (accept && !m_hasActionsWithIcons) {
145 m_hasActionsWithIcons |= !index.data(arole: Qt::DecorationRole).isNull();
146 }
147
148 return accept;
149 }
150
151private:
152 QString m_pattern;
153 mutable bool m_hasActionsWithIcons = false;
154};
155// END CommandBarFilterModel
156
157class CommandBarStyleDelegate final : public QStyledItemDelegate
158{
159public:
160 CommandBarStyleDelegate(QObject *parent = nullptr)
161 : QStyledItemDelegate(parent)
162 {
163 }
164
165 /**
166 * Paints a single item's text
167 */
168 static void
169 paintItemText(QPainter *p, const QString &textt, const QRect &rect, const QStyleOptionViewItem &options, QList<QTextLayout::FormatRange> formats)
170 {
171 QString text = options.fontMetrics.elidedText(text: textt, mode: Qt::ElideRight, width: rect.width());
172
173 // set formats and font
174 QTextLayout textLayout(text, options.font);
175 formats.append(other: textLayout.formats());
176 textLayout.setFormats(formats);
177
178 // set alignment, rtls etc
179 QTextOption textOption;
180 textOption.setTextDirection(options.direction);
181 textOption.setAlignment(QStyle::visualAlignment(direction: options.direction, alignment: options.displayAlignment));
182 textLayout.setTextOption(textOption);
183
184 // layout the text
185 textLayout.beginLayout();
186
187 QTextLine line = textLayout.createLine();
188 if (!line.isValid()) {
189 return;
190 }
191
192 const int lineWidth = rect.width();
193 line.setLineWidth(lineWidth);
194 line.setPosition(QPointF(0, 0));
195
196 textLayout.endLayout();
197
198 /**
199 * get "Y" so that we can properly V-Center align the text in row
200 */
201 const int y = QStyle::alignedRect(direction: Qt::LeftToRight, alignment: Qt::AlignVCenter, size: textLayout.boundingRect().size().toSize(), rectangle: rect).y();
202
203 // draw the text
204 const QPointF pos(rect.x(), y);
205 textLayout.draw(p, pos);
206 }
207
208 void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
209 {
210 painter->save();
211
212 /**
213 * Draw everything, (widget, icon etc) except the text
214 */
215 QStyleOptionViewItem option = opt;
216 initStyleOption(option: &option, index);
217 option.text.clear(); // clear old text
218 QStyle *style = option.widget->style();
219 style->drawControl(element: QStyle::CE_ItemViewItem, opt: &option, p: painter, w: option.widget);
220
221 const int hMargin = style->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &option, widget: option.widget);
222
223 QRect textRect = option.rect;
224
225 const CommandBarFilterModel *model = static_cast<const CommandBarFilterModel *>(index.model());
226 if (model->hasActionsWithIcons()) {
227 const int iconWidth = option.decorationSize.width() + (hMargin * 2);
228 if (option.direction == Qt::RightToLeft) {
229 textRect.adjust(dx1: 0, dy1: 0, dx2: -iconWidth, dy2: 0);
230 } else {
231 textRect.adjust(dx1: iconWidth, dy1: 0, dx2: 0, dy2: 0);
232 }
233 }
234
235 const QString original = index.data().toString();
236 QStringView str = original;
237 int componentIdx = original.indexOf(c: QLatin1Char(':'));
238 int actionNameStart = 0;
239 if (componentIdx > 0) {
240 actionNameStart = componentIdx + 2;
241 // + 2 because there is a space after colon
242 str = str.mid(pos: actionNameStart);
243 }
244
245 QList<QTextLayout::FormatRange> formats;
246 if (componentIdx > 0) {
247 QTextCharFormat gray;
248 gray.setForeground(option.palette.placeholderText());
249 formats.append(t: {.start: 0, .length: componentIdx, .format: gray});
250 }
251
252 QTextCharFormat fmt;
253 fmt.setForeground(option.palette.link());
254 fmt.setFontWeight(QFont::Bold);
255
256 /**
257 * Highlight matches from fuzzy matcher
258 */
259 const auto fmtRanges = KFuzzyMatcher::matchedRanges(pattern: m_filterString, str);
260 QTextCharFormat f;
261 f.setForeground(option.palette.link());
262 formats.reserve(asize: formats.size() + fmtRanges.size());
263 std::transform(first: fmtRanges.begin(), last: fmtRanges.end(), result: std::back_inserter(x&: formats), unary_op: [f, actionNameStart](const KFuzzyMatcher::Range &fr) {
264 return QTextLayout::FormatRange{.start: fr.start + actionNameStart, .length: fr.length, .format: f};
265 });
266
267 textRect.adjust(dx1: hMargin, dy1: 0, dx2: -hMargin, dy2: 0);
268 paintItemText(p: painter, textt: original, rect: textRect, options: option, formats: std::move(formats));
269
270 painter->restore();
271 }
272
273public Q_SLOTS:
274 void setFilterString(const QString &text)
275 {
276 m_filterString = text;
277 }
278
279private:
280 QString m_filterString;
281};
282
283class ShortcutStyleDelegate final : public QStyledItemDelegate
284{
285public:
286 ShortcutStyleDelegate(QObject *parent = nullptr)
287 : QStyledItemDelegate(parent)
288 {
289 }
290
291 void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
292 {
293 // draw background
294 option.widget->style()->drawPrimitive(pe: QStyle::PE_PanelItemViewItem, opt: &option, p: painter);
295
296 const QString shortcutString = index.data().toString();
297 if (shortcutString.isEmpty()) {
298 return;
299 }
300
301 const ShortcutSegments shortcutSegments = splitShortcut(shortcut: shortcutString);
302 if (shortcutSegments.isEmpty()) {
303 return;
304 }
305
306 struct Button {
307 int textWidth;
308 QString text;
309 };
310
311 // compute the width of each shortcut segment
312 QList<Button> btns;
313 btns.reserve(asize: shortcutSegments.count());
314 const int hMargin = horizontalMargin(option);
315 for (const QString &text : shortcutSegments) {
316 int textWidth = option.fontMetrics.horizontalAdvance(text);
317 textWidth += 2 * hMargin;
318 btns.append(t: {.textWidth: textWidth, .text: text});
319 }
320
321 int textHeight = option.fontMetrics.lineSpacing();
322 // this happens on gnome so we manually decrease the height a bit
323 if (textHeight == option.rect.height()) {
324 textHeight -= 4;
325 }
326
327 const int y = option.rect.y() + (option.rect.height() - textHeight) / 2;
328 int x;
329 if (option.direction == Qt::RightToLeft) {
330 x = option.rect.x() + hMargin;
331 } else {
332 x = option.rect.right() - shortcutDrawingWidth(option, shortcutSegments, hMargin) - hMargin;
333 }
334
335 painter->save();
336 painter->setPen(option.palette.buttonText().color());
337 painter->setRenderHint(hint: QPainter::Antialiasing);
338 for (int i = 0, n = btns.count(); i < n; ++i) {
339 const Button &button = btns.at(i);
340
341 QRect outputRect(x, y, button.textWidth, textHeight);
342
343 // an even element indicates that it is a key
344 if (i % 2 == 0) {
345 painter->save();
346 painter->setPen(Qt::NoPen);
347
348 // draw rounded rect shadow
349 auto shadowRect = outputRect.translated(dx: 0, dy: 1);
350 painter->setBrush(option.palette.shadow());
351 painter->drawRoundedRect(rect: shadowRect, xRadius: 3.0, yRadius: 3.0);
352
353 // draw rounded rect itself
354 painter->setBrush(option.palette.window());
355 painter->drawRoundedRect(rect: outputRect, xRadius: 3.0, yRadius: 3.0);
356
357 painter->restore();
358 }
359
360 // draw shortcut segment
361 painter->drawText(r: outputRect, flags: Qt::AlignCenter, text: button.text);
362
363 x += outputRect.width();
364 }
365
366 painter->restore();
367 }
368
369 QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
370 {
371 if (index.isValid() && index.column() == KCommandBarModel::Column_Shortcut) {
372 const QString shortcut = index.data().toString();
373 if (!shortcut.isEmpty()) {
374 const ShortcutSegments shortcutSegments = splitShortcut(shortcut);
375 if (!shortcutSegments.isEmpty()) {
376 const int hMargin = horizontalMargin(option);
377 int width = shortcutDrawingWidth(option, shortcutSegments, hMargin);
378
379 // add left and right margins
380 width += 2 * hMargin;
381
382 return QSize(width, 0);
383 }
384 }
385 }
386
387 return QStyledItemDelegate::sizeHint(option, index);
388 }
389
390private:
391 using ShortcutSegments = QStringList;
392
393 // split shortcut into segments i.e. will return
394 // ["Ctrl", "+", "A", ", ", "Ctrl", "+", "K"] for "Ctrl+A, Ctrl+K"
395 // twice as fast as using regular expressions
396 static ShortcutSegments splitShortcut(const QString &shortcut)
397 {
398 ShortcutSegments segments;
399 if (!shortcut.isEmpty()) {
400 const int shortcutLength = shortcut.length();
401 int start = 0;
402 for (int i = 0; i < shortcutLength; ++i) {
403 const QChar c = shortcut.at(i);
404 if (c == QLatin1Char('+')) {
405 if (i > start) {
406 segments << shortcut.mid(position: start, n: i - start);
407 }
408 segments << shortcut.at(i);
409 start = i + 1;
410 } else if (c == QLatin1Char(',')) {
411 if (i > start) {
412 segments << shortcut.mid(position: start, n: i - start);
413 start = i;
414 }
415 const int j = i + 1;
416 if (j < shortcutLength && shortcut.at(i: j) == QLatin1Char(' ')) {
417 segments << shortcut.mid(position: start, n: j - start + 1);
418 i = j;
419 } else {
420 segments << shortcut.at(i);
421 }
422 start = i + 1;
423 }
424 }
425 if (start < shortcutLength) {
426 segments << shortcut.mid(position: start);
427 }
428
429 // check we have successfully parsed the shortcut
430 if (segments.isEmpty()) {
431 qCWarning(KCONFIG_WIDGETS_LOG) << "Splitting shortcut failed" << shortcut;
432 }
433 }
434
435 return segments;
436 }
437
438 // returns the width needed to draw the shortcut
439 static int shortcutDrawingWidth(const QStyleOptionViewItem &option, const ShortcutSegments &shortcutSegments, int hMargin)
440 {
441 int width = 0;
442 if (!shortcutSegments.isEmpty()) {
443 width = option.fontMetrics.horizontalAdvance(shortcutSegments.join(sep: QString()));
444
445 // add left and right margins for each segment
446 width += shortcutSegments.count() * 2 * hMargin;
447 }
448
449 return width;
450 }
451
452 int horizontalMargin(const QStyleOptionViewItem &option) const
453 {
454 return option.widget->style()->pixelMetric(metric: QStyle::PM_FocusFrameHMargin, option: &option) + 2;
455 }
456};
457
458// BEGIN KCommandBarPrivate
459class KCommandBarPrivate
460{
461public:
462 QTreeView m_treeView;
463 QLineEdit m_lineEdit;
464 KCommandBarModel m_model;
465 CommandBarFilterModel m_proxyModel;
466
467 /**
468 * selects first item in treeview
469 */
470 void reselectFirst()
471 {
472 const QModelIndex index = m_proxyModel.index(row: 0, column: 0);
473 m_treeView.setCurrentIndex(index);
474 }
475
476 /**
477 * blocks signals before clearing line edit to ensure
478 * we don't trigger filtering / sorting
479 */
480 void clearLineEdit()
481 {
482 const QSignalBlocker blocker(m_lineEdit);
483 m_lineEdit.clear();
484 }
485
486 void slotReturnPressed(KCommandBar *q);
487
488 void setLastUsedActions();
489
490 QStringList lastUsedActions() const;
491};
492
493void KCommandBarPrivate::slotReturnPressed(KCommandBar *q)
494{
495 auto act = m_proxyModel.data(index: m_treeView.currentIndex(), role: Qt::UserRole).value<QAction *>();
496 if (act) {
497 // if the action is a menu, we take all its actions
498 // and reload our dialog with these instead.
499 if (auto menu = act->menu()) {
500 auto menuActions = menu->actions();
501 KCommandBar::ActionGroup ag;
502
503 // if there are no actions, trigger load actions
504 // this happens with some menus that are loaded on demand
505 if (menuActions.size() == 0) {
506 Q_EMIT menu->aboutToShow();
507 ag.actions = menu->actions();
508 }
509
510 QString groupName = KLocalizedString::removeAcceleratorMarker(label: act->text());
511 ag.name = groupName;
512
513 m_model.refresh(actionGroups: {ag});
514 reselectFirst();
515 /**
516 * We want the "textChanged" signal here
517 * so that proxy model triggers filtering again
518 * so don't use d->clearLineEdit()
519 */
520 m_lineEdit.clear();
521 return;
522 } else {
523 m_model.actionTriggered(name: act->text());
524 q->hide();
525 act->trigger();
526 }
527 }
528
529 clearLineEdit();
530 q->hide();
531 q->deleteLater();
532}
533
534void KCommandBarPrivate::setLastUsedActions()
535{
536 auto cfg = KSharedConfig::openStateConfig();
537 KConfigGroup cg(cfg, QStringLiteral("General"));
538
539 QStringList actionNames = cg.readEntry(QStringLiteral("CommandBarLastUsedActions"), aDefault: QStringList());
540
541 return m_model.setLastUsedActions(actionNames);
542}
543
544QStringList KCommandBarPrivate::lastUsedActions() const
545{
546 return m_model.lastUsedActions();
547}
548// END KCommandBarPrivate
549
550// BEGIN KCommandBar
551KCommandBar::KCommandBar(QWidget *parent)
552 : QFrame(parent)
553 , d(new KCommandBarPrivate)
554{
555 QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect(this);
556 e->setColor(palette().color(cr: QPalette::Shadow));
557 e->setOffset(2.);
558 e->setBlurRadius(8.);
559 setGraphicsEffect(e);
560
561 setAutoFillBackground(true);
562 setFrameShadow(QFrame::Raised);
563 setFrameShape(QFrame::Box);
564
565 QVBoxLayout *layout = new QVBoxLayout();
566 layout->setSpacing(0);
567 layout->setContentsMargins(left: 2, top: 2, right: 2, bottom: 2);
568 setLayout(layout);
569
570 setFocusProxy(&d->m_lineEdit);
571
572 layout->addWidget(&d->m_lineEdit);
573
574 layout->addWidget(&d->m_treeView);
575 d->m_treeView.setTextElideMode(Qt::ElideLeft);
576 d->m_treeView.setUniformRowHeights(true);
577
578 CommandBarStyleDelegate *delegate = new CommandBarStyleDelegate(this);
579 ShortcutStyleDelegate *del = new ShortcutStyleDelegate(this);
580 d->m_treeView.setItemDelegateForColumn(column: KCommandBarModel::Column_Command, delegate);
581 d->m_treeView.setItemDelegateForColumn(column: KCommandBarModel::Column_Shortcut, delegate: del);
582
583 connect(sender: &d->m_lineEdit, signal: &QLineEdit::returnPressed, context: this, slot: [this]() {
584 d->slotReturnPressed(q: this);
585 });
586 connect(sender: &d->m_lineEdit, signal: &QLineEdit::textChanged, context: &d->m_proxyModel, slot: &CommandBarFilterModel::setFilterString);
587 connect(sender: &d->m_lineEdit, signal: &QLineEdit::textChanged, context: delegate, slot: &CommandBarStyleDelegate::setFilterString);
588 connect(sender: &d->m_lineEdit, signal: &QLineEdit::textChanged, context: this, slot: [this]() {
589 d->m_treeView.viewport()->update();
590 d->reselectFirst();
591 });
592 connect(sender: &d->m_treeView, signal: &QTreeView::clicked, context: this, slot: [this]() {
593 d->slotReturnPressed(q: this);
594 });
595
596 d->m_proxyModel.setSourceModel(&d->m_model);
597 d->m_treeView.setSortingEnabled(true);
598 d->m_treeView.setModel(&d->m_proxyModel);
599
600 d->m_treeView.header()->setMinimumSectionSize(0);
601 d->m_treeView.header()->setStretchLastSection(false);
602 d->m_treeView.header()->setSectionResizeMode(logicalIndex: KCommandBarModel::Column_Command, mode: QHeaderView::Stretch);
603 d->m_treeView.header()->setSectionResizeMode(logicalIndex: KCommandBarModel::Column_Shortcut, mode: QHeaderView::ResizeToContents);
604
605 parent->installEventFilter(filterObj: this);
606 d->m_treeView.installEventFilter(filterObj: this);
607 d->m_lineEdit.installEventFilter(filterObj: this);
608
609 d->m_treeView.setHeaderHidden(true);
610 d->m_treeView.setRootIsDecorated(false);
611 d->m_treeView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
612 d->m_treeView.setSelectionMode(QTreeView::SingleSelection);
613
614 QLabel *placeholderLabel = new QLabel;
615 placeholderLabel->setAlignment(Qt::AlignCenter);
616 placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
617 placeholderLabel->setWordWrap(true);
618 placeholderLabel->setText(i18n("No commands matching the filter"));
619 // To match the size of a level 2 Heading/KTitleWidget
620 QFont placeholderLabelFont = placeholderLabel->font();
621 placeholderLabelFont.setPointSize(qRound(d: placeholderLabelFont.pointSize() * 1.3));
622 placeholderLabel->setFont(placeholderLabelFont);
623 // Match opacity of QML placeholder label component
624 QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(placeholderLabel);
625 opacityEffect->setOpacity(0.5);
626 placeholderLabel->setGraphicsEffect(opacityEffect);
627
628 QHBoxLayout *placeholderLayout = new QHBoxLayout;
629 placeholderLayout->addWidget(placeholderLabel);
630 d->m_treeView.setLayout(placeholderLayout);
631
632 connect(sender: &d->m_proxyModel, signal: &CommandBarFilterModel::modelReset, context: this, slot: [this, placeholderLabel]() {
633 placeholderLabel->setHidden(d->m_proxyModel.rowCount() > 0);
634 });
635
636 setHidden(true);
637
638 // Migrate last used action config to new location
639 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
640 if (cg.hasKey(key: "CommandBarLastUsedActions")) {
641 const QStringList actionNames = cg.readEntry(key: "CommandBarLastUsedActions", aDefault: QStringList());
642
643 KConfigGroup stateCg(KSharedConfig::openStateConfig(), QStringLiteral("General"));
644 stateCg.writeEntry(QStringLiteral("CommandBarLastUsedActions"), value: actionNames);
645
646 cg.deleteEntry(QStringLiteral("CommandBarLastUsedActions"));
647 }
648}
649
650/**
651 * Destructor defined here to make unique_ptr work
652 */
653KCommandBar::~KCommandBar()
654{
655 auto lastUsedActions = d->lastUsedActions();
656 auto cfg = KSharedConfig::openStateConfig();
657 KConfigGroup cg(cfg, QStringLiteral("General"));
658 cg.writeEntry(key: "CommandBarLastUsedActions", value: lastUsedActions);
659
660 // Explicitly remove installed event filters of children of d-pointer
661 // class, otherwise while KCommandBar is being torn down, an event could
662 // fire and the eventFilter() accesses d, which would cause a crash
663 // bug 452527
664 d->m_treeView.removeEventFilter(obj: this);
665 d->m_lineEdit.removeEventFilter(obj: this);
666}
667
668void KCommandBar::setActions(const QList<ActionGroup> &actions)
669{
670 // First set last used actions in the model
671 d->setLastUsedActions();
672
673 d->m_model.refresh(actionGroups: actions);
674 d->reselectFirst();
675
676 show();
677 setFocus();
678}
679
680void KCommandBar::show()
681{
682 const QRect boundingRect = getCommandBarBoundingRect(commandBar: this);
683
684 static constexpr int minWidth = 500;
685 const int maxWidth = boundingRect.width();
686 const int preferredWidth = maxWidth / 2.4;
687
688 static constexpr int minHeight = 250;
689 const int maxHeight = boundingRect.height();
690 const int preferredHeight = maxHeight / 2;
691
692 const QSize size{std::min(a: maxWidth, b: std::max(a: preferredWidth, b: minWidth)), std::min(a: maxHeight, b: std::max(a: preferredHeight, b: minHeight))};
693
694 setFixedSize(size);
695
696 // set the position to the top-center of the parent
697 // just below the menubar/toolbar (if any)
698 const QPoint position{boundingRect.center().x() - size.width() / 2, boundingRect.y()};
699 move(position);
700
701 QWidget::show();
702}
703
704bool KCommandBar::eventFilter(QObject *obj, QEvent *event)
705{
706 if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
707 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
708 if (obj == &d->m_lineEdit) {
709 const int key = keyEvent->key();
710 const bool forward2list = (key == Qt::Key_Up) || (key == Qt::Key_Down) || (key == Qt::Key_PageUp) || (key == Qt::Key_PageDown);
711 if (forward2list) {
712 QCoreApplication::sendEvent(receiver: &d->m_treeView, event);
713 return true;
714 }
715 } else if (obj == &d->m_treeView) {
716 const int key = keyEvent->key();
717 const bool forward2input = (key != Qt::Key_Up) && (key != Qt::Key_Down) && (key != Qt::Key_PageUp) && (key != Qt::Key_PageDown)
718 && (key != Qt::Key_Tab) && (key != Qt::Key_Backtab);
719 if (forward2input) {
720 QCoreApplication::sendEvent(receiver: &d->m_lineEdit, event);
721 return true;
722 }
723 }
724
725 if (keyEvent->key() == Qt::Key_Escape) {
726 hide();
727 deleteLater();
728 return true;
729 }
730 }
731
732 // hide on focus out, if neither input field nor list have focus!
733 else if (event->type() == QEvent::FocusOut && isVisible() && !(d->m_lineEdit.hasFocus() || d->m_treeView.hasFocus())) {
734 d->clearLineEdit();
735 deleteLater();
736 hide();
737 return true;
738 }
739
740 // handle resizing
741 if (parent() == obj && event->type() == QEvent::Resize) {
742 show();
743 }
744
745 return QWidget::eventFilter(watched: obj, event);
746}
747// END KCommandBar
748
749#include "moc_kcommandbar.cpp"
750

source code of kconfigwidgets/src/kcommandbar.cpp