1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
4 SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
5 SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
6 SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
7 SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
8 SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
9 SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
10 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
11 SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
12 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
13 SPDX-FileCopyrightText: 2006 Albert Astals Cid <aacid@kde.org>
14 SPDX-FileCopyrightText: 2006 Clarence Dang <dang@kde.org>
15 SPDX-FileCopyrightText: 2006 Michel Hermier <michel.hermier@gmail.com>
16 SPDX-FileCopyrightText: 2007 Nick Shaforostoff <shafff@ukr.net>
17
18 SPDX-License-Identifier: LGPL-2.0-only
19 */
20
21 #include "kselectaction.h"
22 #include "kselectaction_p.h"
23
24 #include "loggingcategory.h"
25
26 #include <QActionEvent>
27 #include <QEvent>
28 #include <QMenu>
29 #include <QStandardItem>
30 #include <QToolBar>
31
32 // QAction::setText("Hi") and then KPopupAccelManager exec'ing, causes
33 // QAction::text() to return "&Hi" :( Comboboxes don't have accels and
34 // display ampersands literally.
DropAmpersands(const QString & text)35 static QString DropAmpersands(const QString &text)
36 {
37 QString label = text;
38
39 int p = label.indexOf(QLatin1Char('&'));
40 while (p >= 0 && p < label.length() - 1) {
41 if (label[p + 1].isLetterOrNumber() // Valid accelerator.
42 || label[p + 1] == QLatin1Char('&')) { // Escaped accelerator marker.
43 label.remove(p, 1);
44 }
45
46 p = label.indexOf(QLatin1Char('&'), p + 1);
47 }
48
49 return label;
50 }
51
KSelectAction(QObject * parent)52 KSelectAction::KSelectAction(QObject *parent)
53 : KSelectAction(*new KSelectActionPrivate(this), parent)
54 {
55 }
56
KSelectAction(const QString & text,QObject * parent)57 KSelectAction::KSelectAction(const QString &text, QObject *parent)
58 : KSelectAction(*new KSelectActionPrivate(this), parent)
59 {
60 setText(text);
61 }
62
KSelectAction(const QIcon & icon,const QString & text,QObject * parent)63 KSelectAction::KSelectAction(const QIcon &icon, const QString &text, QObject *parent)
64 : KSelectAction(*new KSelectActionPrivate(this), parent)
65 {
66 setIcon(icon);
67 setText(text);
68 }
69
KSelectAction(KSelectActionPrivate & dd,QObject * parent)70 KSelectAction::KSelectAction(KSelectActionPrivate &dd, QObject *parent)
71 : QWidgetAction(parent)
72 , d_ptr(&dd)
73 {
74 Q_D(KSelectAction);
75 d->init();
76 }
77
~KSelectAction()78 KSelectAction::~KSelectAction()
79 {
80 menu()->deleteLater();
81 }
82
init()83 void KSelectActionPrivate::init()
84 {
85 QObject::connect(q_ptr->selectableActionGroup(), &QActionGroup::triggered, q_ptr, &KSelectAction::actionTriggered);
86 QObject::connect(q_ptr, &QAction::toggled, q_ptr, &KSelectAction::slotToggled);
87 q_ptr->setMenu(new QMenu());
88 q_ptr->setEnabled(false);
89 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 78)
90 // forward deprecated signals to undeprecated, to be backward-compatible to unported subclasses
91 QObject::connect(q_ptr, qOverload<int>(&KSelectAction::triggered), q_ptr, &KSelectAction::indexTriggered);
92 QObject::connect(q_ptr, qOverload<const QString &>(&KSelectAction::triggered), q_ptr, &KSelectAction::textTriggered);
93 #endif
94 }
95
selectableActionGroup() const96 QActionGroup *KSelectAction::selectableActionGroup() const
97 {
98 Q_D(const KSelectAction);
99 return d->m_actionGroup;
100 }
101
actions() const102 QList<QAction *> KSelectAction::actions() const
103 {
104 return selectableActionGroup()->actions();
105 }
106
currentAction() const107 QAction *KSelectAction::currentAction() const
108 {
109 return selectableActionGroup()->checkedAction();
110 }
111
currentItem() const112 int KSelectAction::currentItem() const
113 {
114 return selectableActionGroup()->actions().indexOf(currentAction());
115 }
116
currentText() const117 QString KSelectAction::currentText() const
118 {
119 if (QAction *a = currentAction()) {
120 return ::DropAmpersands(a->text());
121 }
122
123 return QString();
124 }
125
setCurrentAction(QAction * action)126 bool KSelectAction::setCurrentAction(QAction *action)
127 {
128 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::setCurrentAction(" << action << ")";
129 if (action) {
130 if (actions().contains(action)) {
131 if (action->isVisible() && action->isEnabled() && action->isCheckable()) {
132 action->setChecked(true);
133 if (isCheckable()) {
134 setChecked(true);
135 }
136 return true;
137 } else {
138 qCWarning(KWidgetsAddonsLog) << "Action does not have the correct properties to be current:" << action->text();
139 }
140 } else {
141 qCWarning(KWidgetsAddonsLog) << "Action does not belong to group:" << action->text();
142 }
143 return false;
144 }
145
146 if (currentAction()) {
147 currentAction()->setChecked(false);
148 }
149
150 return false;
151 }
152
setCurrentItem(int index)153 bool KSelectAction::setCurrentItem(int index)
154 {
155 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::setCurrentIndex(" << index << ")";
156 return setCurrentAction(action(index));
157 }
158
action(int index) const159 QAction *KSelectAction::action(int index) const
160 {
161 if (index >= 0 && index < selectableActionGroup()->actions().count()) {
162 return selectableActionGroup()->actions().at(index);
163 }
164
165 return nullptr;
166 }
167
action(const QString & text,Qt::CaseSensitivity cs) const168 QAction *KSelectAction::action(const QString &text, Qt::CaseSensitivity cs) const
169 {
170 QString compare;
171 if (cs == Qt::CaseSensitive) {
172 compare = text;
173 } else {
174 compare = text.toLower();
175 }
176
177 const auto selectableActions = selectableActionGroup()->actions();
178 for (QAction *action : selectableActions) {
179 const QString text = ::DropAmpersands(action->text());
180 if (cs == Qt::CaseSensitive) {
181 if (text == compare) {
182 return action;
183 }
184
185 } else if (cs == Qt::CaseInsensitive) {
186 if (text.toLower() == compare) {
187 return action;
188 }
189 }
190 }
191
192 return nullptr;
193 }
194
setCurrentAction(const QString & text,Qt::CaseSensitivity cs)195 bool KSelectAction::setCurrentAction(const QString &text, Qt::CaseSensitivity cs)
196 {
197 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::setCurrentAction(" << text << ",cs=" << cs << ")";
198 return setCurrentAction(action(text, cs));
199 }
200
setComboWidth(int width)201 void KSelectAction::setComboWidth(int width)
202 {
203 Q_D(KSelectAction);
204 if (width < 0) {
205 return;
206 }
207
208 d->m_comboWidth = width;
209
210 for (QComboBox *box : std::as_const(d->m_comboBoxes)) {
211 box->setMaximumWidth(d->m_comboWidth);
212 }
213
214 Q_EMIT changed();
215 }
216
setMaxComboViewCount(int n)217 void KSelectAction::setMaxComboViewCount(int n)
218 {
219 Q_D(KSelectAction);
220 d->m_maxComboViewCount = n;
221
222 for (QComboBox *box : std::as_const(d->m_comboBoxes)) {
223 if (d->m_maxComboViewCount != -1) {
224 box->setMaxVisibleItems(d->m_maxComboViewCount);
225 } else
226 // hardcoded qt default
227 {
228 box->setMaxVisibleItems(10);
229 }
230 }
231
232 Q_EMIT changed();
233 }
234
addAction(QAction * action)235 void KSelectAction::addAction(QAction *action)
236 {
237 insertAction(nullptr, action);
238 }
239
addAction(const QString & text)240 QAction *KSelectAction::addAction(const QString &text)
241 {
242 Q_D(KSelectAction);
243 QAction *newAction = new QAction(parent());
244 newAction->setText(text);
245 newAction->setCheckable(true);
246 newAction->setProperty("isShortcutConfigurable", false);
247
248 if (!d->m_menuAccelsEnabled) {
249 newAction->setText(text);
250 newAction->setShortcut(QKeySequence());
251 }
252
253 addAction(newAction);
254 return newAction;
255 }
256
addAction(const QIcon & icon,const QString & text)257 QAction *KSelectAction::addAction(const QIcon &icon, const QString &text)
258 {
259 QAction *newAction = addAction(text);
260 newAction->setIcon(icon);
261 return newAction;
262 }
263
removeAction(QAction * action)264 QAction *KSelectAction::removeAction(QAction *action)
265 {
266 Q_D(KSelectAction);
267 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::removeAction(" << action << ")";
268 // int index = selectableActionGroup()->actions().indexOf(action);
269 // qCDebug(KWidgetsAddonsLog) << "\tindex=" << index;
270
271 // Removes the action from the group and sets its parent to null.
272 d->m_actionGroup->removeAction(action);
273
274 // Disable when no action is in the group
275 bool hasActions = selectableActionGroup()->actions().isEmpty();
276 setEnabled(!hasActions);
277
278 for (QToolButton *button : std::as_const(d->m_buttons)) {
279 button->setEnabled(!hasActions);
280 button->removeAction(action);
281 }
282
283 for (QComboBox *comboBox : std::as_const(d->m_comboBoxes)) {
284 comboBox->setEnabled(!hasActions);
285 comboBox->removeAction(action);
286 }
287
288 menu()->removeAction(action);
289
290 return action;
291 }
292
insertAction(QAction * before,QAction * action)293 void KSelectAction::insertAction(QAction *before, QAction *action)
294 {
295 Q_D(KSelectAction);
296 action->setActionGroup(selectableActionGroup());
297
298 // Re-Enable when an action is added
299 setEnabled(true);
300
301 // Keep in sync with createToolBarWidget()
302 for (QToolButton *button : std::as_const(d->m_buttons)) {
303 button->setEnabled(true);
304 button->insertAction(before, action);
305 }
306
307 for (QComboBox *comboBox : std::as_const(d->m_comboBoxes)) {
308 comboBox->setEnabled(true);
309 comboBox->insertAction(before, action);
310 }
311
312 menu()->insertAction(before, action);
313 }
314
actionTriggered(QAction * action)315 void KSelectAction::actionTriggered(QAction *action)
316 {
317 // cache values so we don't need access to members in the action
318 // after we've done an emit()
319 const QString text = ::DropAmpersands(action->text());
320 const int index = selectableActionGroup()->actions().indexOf(action);
321 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::actionTriggered(" << action << ") text=" << text
322 // << " index=" << index << " emitting triggered()" << endl;
323
324 if (isCheckable()) { // if this is subsidiary of other KSelectAction-derived class
325 trigger(); // then imitate usual QAction behaviour so that other submenus (and their items) become unchecked
326 }
327
328 Q_EMIT triggered(action);
329 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 78)
330 // will also indirectly emit indexTriggered & textTriggered, due to signal connection in init()
331 Q_EMIT triggered(index);
332 Q_EMIT triggered(text);
333 #else
334 Q_EMIT indexTriggered(index);
335 Q_EMIT textTriggered(text);
336 #endif
337 }
338
items() const339 QStringList KSelectAction::items() const
340 {
341 Q_D(const KSelectAction);
342 QStringList ret;
343
344 const auto actions = d->m_actionGroup->actions();
345 ret.reserve(actions.size());
346 for (QAction *action : actions) {
347 ret << ::DropAmpersands(action->text());
348 }
349
350 return ret;
351 }
352
changeItem(int index,const QString & text)353 void KSelectAction::changeItem(int index, const QString &text)
354 {
355 Q_D(KSelectAction);
356 if (index < 0 || index >= actions().count()) {
357 qCWarning(KWidgetsAddonsLog) << "KSelectAction::changeItem Index out of scope";
358 return;
359 }
360
361 actions()[index]->setText(d->makeMenuText(text));
362 }
363
setItems(const QStringList & lst)364 void KSelectAction::setItems(const QStringList &lst)
365 {
366 Q_D(KSelectAction);
367 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::setItems(" << lst << ")";
368
369 clear();
370
371 for (const QString &string : lst) {
372 if (!string.isEmpty()) {
373 addAction(string);
374 } else {
375 QAction *action = new QAction(this);
376 action->setSeparator(true);
377 addAction(action);
378 }
379 }
380
381 // Disable if empty and not editable
382 setEnabled(lst.count() > 0 || d->m_edit);
383 }
384
comboWidth() const385 int KSelectAction::comboWidth() const
386 {
387 Q_D(const KSelectAction);
388 return d->m_comboWidth;
389 }
390
clear()391 void KSelectAction::clear()
392 {
393 Q_D(KSelectAction);
394 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::clear()";
395
396 // we need to delete the actions later since we may get a call to clear()
397 // from a method called due to a triggered(...) signal
398 const QList<QAction *> actions = d->m_actionGroup->actions();
399 for (int i = 0; i < actions.count(); ++i) {
400 // deleteLater() only removes us from the actions() list (among
401 // other things) on the next entry into the event loop. Until then,
402 // e.g. action() and setCurrentItem() will be working on items
403 // that are supposed to have been deleted. So detach the action to
404 // prevent this from happening.
405 removeAction(actions[i]);
406
407 actions[i]->deleteLater();
408 }
409 }
410
removeAllActions()411 void KSelectAction::removeAllActions()
412 {
413 Q_D(KSelectAction);
414 while (d->m_actionGroup->actions().count()) {
415 removeAction(d->m_actionGroup->actions().first());
416 }
417 }
418
setEditable(bool edit)419 void KSelectAction::setEditable(bool edit)
420 {
421 Q_D(KSelectAction);
422 d->m_edit = edit;
423
424 for (QComboBox *comboBox : std::as_const(d->m_comboBoxes)) {
425 comboBox->setEditable(edit);
426 }
427
428 Q_EMIT changed();
429 }
430
isEditable() const431 bool KSelectAction::isEditable() const
432 {
433 Q_D(const KSelectAction);
434 return d->m_edit;
435 }
436
slotToggled(bool checked)437 void KSelectAction::slotToggled(bool checked)
438 {
439 // if (checked && selectableActionGroup()->checkedAction())
440 if (!checked && currentAction()) { // other's submenu item has been selected
441 currentAction()->setChecked(false);
442 }
443 }
444
toolBarMode() const445 KSelectAction::ToolBarMode KSelectAction::toolBarMode() const
446 {
447 Q_D(const KSelectAction);
448 return d->m_toolBarMode;
449 }
450
setToolBarMode(ToolBarMode mode)451 void KSelectAction::setToolBarMode(ToolBarMode mode)
452 {
453 Q_D(KSelectAction);
454 d->m_toolBarMode = mode;
455 }
456
toolButtonPopupMode() const457 QToolButton::ToolButtonPopupMode KSelectAction::toolButtonPopupMode() const
458 {
459 Q_D(const KSelectAction);
460 return d->m_toolButtonPopupMode;
461 }
462
setToolButtonPopupMode(QToolButton::ToolButtonPopupMode mode)463 void KSelectAction::setToolButtonPopupMode(QToolButton::ToolButtonPopupMode mode)
464 {
465 Q_D(KSelectAction);
466 d->m_toolButtonPopupMode = mode;
467 }
468
comboBoxDeleted(QComboBox * combo)469 void KSelectActionPrivate::comboBoxDeleted(QComboBox *combo)
470 {
471 m_comboBoxes.removeAll(combo);
472 }
473
comboBoxCurrentIndexChanged(int index)474 void KSelectActionPrivate::comboBoxCurrentIndexChanged(int index)
475 {
476 Q_Q(KSelectAction);
477 // qCDebug(KWidgetsAddonsLog) << "KSelectActionPrivate::comboBoxCurrentIndexChanged(" << index << ")";
478
479 QComboBox *triggeringCombo = qobject_cast<QComboBox *>(q->sender());
480
481 QAction *a = q->action(index);
482 // qCDebug(KWidgetsAddonsLog) << "\ta=" << a;
483 if (a) {
484 // qCDebug(KWidgetsAddonsLog) << "\t\tsetting as current action";
485 a->trigger();
486
487 } else if (q->isEditable() && triggeringCombo && triggeringCombo->count() > 0 && index == triggeringCombo->count() - 1) {
488 // User must have added a new item by typing and pressing enter.
489 const QString newItemText = triggeringCombo->currentText();
490 // qCDebug(KWidgetsAddonsLog) << "\t\tuser typed new item '" << newItemText << "'";
491
492 // Only 1 combobox contains this and it's not a proper action.
493 bool blocked = triggeringCombo->blockSignals(true);
494 triggeringCombo->removeItem(index);
495 triggeringCombo->blockSignals(blocked);
496
497 QAction *newAction = q->addAction(newItemText);
498
499 newAction->trigger();
500 } else {
501 if (q->selectableActionGroup()->checkedAction()) {
502 q->selectableActionGroup()->checkedAction()->setChecked(false);
503 }
504 }
505 }
506
507 // TODO: DropAmpersands() certainly makes sure this doesn't work. But I don't
508 // think it did anyway esp. in the presence KCheckAccelerator - Clarence.
setMenuAccelsEnabled(bool b)509 void KSelectAction::setMenuAccelsEnabled(bool b)
510 {
511 Q_D(KSelectAction);
512 d->m_menuAccelsEnabled = b;
513 }
514
menuAccelsEnabled() const515 bool KSelectAction::menuAccelsEnabled() const
516 {
517 Q_D(const KSelectAction);
518 return d->m_menuAccelsEnabled;
519 }
520
createWidget(QWidget * parent)521 QWidget *KSelectAction::createWidget(QWidget *parent)
522 {
523 Q_D(KSelectAction);
524 QMenu *menu = qobject_cast<QMenu *>(parent);
525 if (menu) { // If used in a menu want to return 0 and use only the text, not a widget
526 return nullptr;
527 }
528 ToolBarMode mode = toolBarMode();
529 QToolBar *toolBar = qobject_cast<QToolBar *>(parent);
530 if (!toolBar && mode != ComboBoxMode) { // we can return a combobox just fine.
531 return nullptr;
532 }
533 switch (mode) {
534 case MenuMode: {
535 QToolButton *button = new QToolButton(toolBar);
536 button->setToolTip(toolTip());
537 button->setWhatsThis(whatsThis());
538 button->setStatusTip(statusTip());
539 button->setAutoRaise(true);
540 button->setFocusPolicy(Qt::NoFocus);
541 button->setIconSize(toolBar->iconSize());
542 button->setToolButtonStyle(toolBar->toolButtonStyle());
543 QObject::connect(toolBar, &QToolBar::iconSizeChanged, button, &QAbstractButton::setIconSize);
544 QObject::connect(toolBar, &QToolBar::toolButtonStyleChanged, button, &QToolButton::setToolButtonStyle);
545 button->setDefaultAction(this);
546 QObject::connect(button, &QToolButton::triggered, toolBar, &QToolBar::actionTriggered);
547
548 button->setPopupMode(toolButtonPopupMode());
549
550 button->addActions(selectableActionGroup()->actions());
551
552 d->m_buttons.append(button);
553 return button;
554 }
555
556 case ComboBoxMode: {
557 QComboBox *comboBox = new QComboBox(parent);
558 comboBox->installEventFilter(this);
559
560 if (d->m_maxComboViewCount != -1) {
561 comboBox->setMaxVisibleItems(d->m_maxComboViewCount);
562 }
563
564 if (d->m_comboWidth > 0) {
565 comboBox->setMaximumWidth(d->m_comboWidth);
566 }
567
568 comboBox->setEditable(isEditable());
569 comboBox->setToolTip(toolTip());
570 comboBox->setWhatsThis(whatsThis());
571 comboBox->setStatusTip(statusTip());
572
573 const auto selectableActions = selectableActionGroup()->actions();
574 for (QAction *action : selectableActions) {
575 comboBox->addAction(action);
576 }
577
578 if (selectableActions.isEmpty()) {
579 comboBox->setEnabled(false);
580 }
581
582 connect(comboBox, &QComboBox::destroyed, this, [d, comboBox]() {
583 d->comboBoxDeleted(comboBox);
584 });
585
586 connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, [d](int value) {
587 d->comboBoxCurrentIndexChanged(value);
588 });
589
590 d->m_comboBoxes.append(comboBox);
591
592 return comboBox;
593 }
594 }
595
596 return nullptr;
597 }
598
deleteWidget(QWidget * widget)599 void KSelectAction::deleteWidget(QWidget *widget)
600 {
601 Q_D(KSelectAction);
602 if (QToolButton *toolButton = qobject_cast<QToolButton *>(widget)) {
603 d->m_buttons.removeAll(toolButton);
604 } else if (QComboBox *comboBox = qobject_cast<QComboBox *>(widget)) {
605 d->m_comboBoxes.removeAll(comboBox);
606 }
607 QWidgetAction::deleteWidget(widget);
608 }
609
event(QEvent * event)610 bool KSelectAction::event(QEvent *event)
611 {
612 Q_D(KSelectAction);
613 if (event->type() == QEvent::ActionChanged) {
614 for (QComboBox *comboBox : std::as_const(d->m_comboBoxes)) {
615 comboBox->setToolTip(toolTip());
616 comboBox->setWhatsThis(whatsThis());
617 comboBox->setStatusTip(statusTip());
618 }
619 for (QToolButton *toolButton : std::as_const(d->m_buttons)) {
620 toolButton->setToolTip(toolTip());
621 toolButton->setWhatsThis(whatsThis());
622 toolButton->setStatusTip(statusTip());
623 }
624 }
625 return QWidgetAction::event(event);
626 }
627
628 // KSelectAction::eventFilter() is called before action->setChecked()
629 // invokes the signal to update QActionGroup so KSelectAction::currentItem()
630 // returns an old value. There are 3 possibilities, where n actions will
631 // report QAction::isChecked() where n is:
632 //
633 // 0: the checked action was unchecked
634 // 1: the checked action did not change
635 // 2: another action was checked but QActionGroup has not been invoked yet
636 // to uncheck the one that was checked before
637 //
638 // TODO: we might want to cache this since QEvent::ActionChanged is fired
639 // often.
TrueCurrentItem(KSelectAction * sa)640 static int TrueCurrentItem(KSelectAction *sa)
641 {
642 QAction *curAction = sa->currentAction();
643 // qCDebug(KWidgetsAddonsLog) << "\tTrueCurrentItem(" << sa << ") curAction=" << curAction;
644
645 const auto actions = sa->actions();
646 int i = 0;
647 for (QAction *action : actions) {
648 if (action->isChecked()) {
649 // qCDebug(KWidgetsAddonsLog) << "\t\taction " << action << " (text=" << action->text () << ") isChecked";
650
651 // 2 actions checked case?
652 if (action != curAction) {
653 // qCDebug(KWidgetsAddonsLog) << "\t\t\tmust be newly selected one";
654 return i;
655 }
656 }
657 ++i;
658 }
659
660 // qCDebug(KWidgetsAddonsLog) << "\t\tcurrent action still selected? " << (curAction && curAction->isChecked ());
661 // 1 or 0 actions checked case (in that order)?
662 return (curAction && curAction->isChecked()) ? sa->actions().indexOf(curAction) : -1;
663 }
664
eventFilter(QObject * watched,QEvent * event)665 bool KSelectAction::eventFilter(QObject *watched, QEvent *event)
666 {
667 QComboBox *comboBox = qobject_cast<QComboBox *>(watched);
668 if (!comboBox) {
669 return false /*propagate event*/;
670 }
671
672 // If focus is lost, replace any edited text with the currently selected
673 // item.
674 if (event->type() == QEvent::FocusOut) {
675 QFocusEvent *const e = static_cast<QFocusEvent *>(event);
676 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::eventFilter(FocusOut)"
677 // << " comboBox: ptr=" << comboBox
678 // << " reason=" << e->reason ()
679 // << endl;
680
681 if (e->reason() != Qt::ActiveWindowFocusReason // switch window
682 && e->reason() != Qt::PopupFocusReason // menu
683 && e->reason() != Qt::OtherFocusReason // inconsistently reproduceable actions...
684 ) {
685 // qCDebug(KWidgetsAddonsLog) << "\tkilling text";
686 comboBox->setEditText(comboBox->itemText(comboBox->currentIndex()));
687 }
688
689 return false /*propagate event*/;
690 }
691
692 bool blocked = comboBox->blockSignals(true);
693
694 if (event->type() == QEvent::ActionAdded) {
695 QActionEvent *const e = static_cast<QActionEvent *>(event);
696
697 const int index = e->before() ? comboBox->findData(QVariant::fromValue(e->before())) : comboBox->count();
698 const int newItem = ::TrueCurrentItem(this);
699 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::eventFilter(ActionAdded)"
700 // << " comboBox: ptr=" << comboBox
701 // << " currentItem=" << comboBox->currentIndex ()
702 // << " add index=" << index
703 // << " action new: e->before=" << e->before ()
704 // << " ptr=" << e->action ()
705 // << " icon=" << e->action ()->icon ()
706 // << " text=" << e->action ()->text ()
707 // << " currentItem=" << newItem
708 // << endl;
709 comboBox->insertItem(index, e->action()->icon(), ::DropAmpersands(e->action()->text()), QVariant::fromValue(e->action()));
710 if (QStandardItemModel *model = qobject_cast<QStandardItemModel *>(comboBox->model())) {
711 QStandardItem *item = model->item(index);
712 item->setEnabled(e->action()->isEnabled());
713 }
714
715 // Inserting an item into a combobox can change the current item so
716 // make sure the item corresponding to the checked action is selected.
717 comboBox->setCurrentIndex(newItem);
718 } else if (event->type() == QEvent::ActionChanged) {
719 QActionEvent *const e = static_cast<QActionEvent *>(event);
720
721 const int index = comboBox->findData(QVariant::fromValue(e->action()));
722 const int newItem = ::TrueCurrentItem(this);
723 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::eventFilter(ActionChanged)"
724 // << " comboBox: ptr=" << comboBox
725 // << " currentItem=" << comboBox->currentIndex ()
726 // << " changed action's index=" << index
727 // << " action new: ptr=" << e->action ()
728 // << " icon=" << e->action ()->icon ()
729 // << " text=" << e->action ()->text ()
730 // << " currentItem=" << newItem
731 // << endl;
732 comboBox->setItemIcon(index, e->action()->icon());
733 comboBox->setItemText(index, ::DropAmpersands(e->action()->text()));
734 if (QStandardItemModel *model = qobject_cast<QStandardItemModel *>(comboBox->model())) {
735 QStandardItem *item = model->item(index);
736 item->setEnabled(e->action()->isEnabled());
737 }
738
739 // The checked action may have become unchecked so
740 // make sure the item corresponding to the checked action is selected.
741 comboBox->setCurrentIndex(newItem);
742 } else if (event->type() == QEvent::ActionRemoved) {
743 QActionEvent *const e = static_cast<QActionEvent *>(event);
744
745 const int index = comboBox->findData(QVariant::fromValue(e->action()));
746 const int newItem = ::TrueCurrentItem(this);
747 // qCDebug(KWidgetsAddonsLog) << "KSelectAction::eventFilter(ActionRemoved)"
748 // << " comboBox: ptr=" << comboBox
749 // << " currentItem=" << comboBox->currentIndex ()
750 // << " delete action index=" << index
751 // << " new: currentItem=" << newItem
752 // << endl;
753 comboBox->removeItem(index);
754
755 // Removing an item from a combobox can change the current item so
756 // make sure the item corresponding to the checked action is selected.
757 comboBox->setCurrentIndex(newItem);
758 }
759
760 comboBox->blockSignals(blocked);
761
762 return false /*propagate event*/;
763 }
764
765 // END
766
767 #include "moc_kselectaction.cpp"
768