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