1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2002 Matthias Hölzer-Klüpfel <mhk@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kacceleratormanager.h"
9#include "kacceleratormanager_p.h"
10
11#include <QApplication>
12#include <QCheckBox>
13#include <QComboBox>
14#include <QDockWidget>
15#include <QGroupBox>
16#include <QLabel>
17#include <QLineEdit>
18#include <QList>
19#include <QMainWindow>
20#include <QMenuBar>
21#include <QMetaProperty>
22#include <QObject>
23#include <QPushButton>
24#include <QRadioButton>
25#include <QStackedWidget>
26#include <QTabBar>
27#include <QTextEdit>
28#include <QWidget>
29
30#include "common_helpers_p.h"
31#include "loggingcategory.h"
32
33/*********************************************************************
34
35 class Item - helper class containing widget information
36
37 This class stores information about the widgets the need accelerators,
38 as well as about their relationship.
39
40 *********************************************************************/
41
42bool KAcceleratorManagerPrivate::programmers_mode = false;
43QString KAcceleratorManagerPrivate::changed_string;
44QString KAcceleratorManagerPrivate::added_string;
45QString KAcceleratorManagerPrivate::removed_string;
46QMap<QWidget *, int> KAcceleratorManagerPrivate::ignored_widgets;
47QStringList KAcceleratorManagerPrivate::standardNames;
48
49void KAcceleratorManagerPrivate::addStandardActionNames(const QStringList &list)
50{
51 standardNames.append(l: list);
52}
53
54bool KAcceleratorManagerPrivate::standardName(const QString &str)
55{
56 return standardNames.contains(str);
57}
58
59KAcceleratorManagerPrivate::Item::~Item()
60{
61 if (m_children) {
62 while (!m_children->isEmpty()) {
63 delete m_children->takeFirst();
64 }
65 delete m_children;
66 }
67}
68
69void KAcceleratorManagerPrivate::Item::addChild(Item *item)
70{
71 if (!m_children) {
72 m_children = new ItemList;
73 }
74
75 m_children->append(t: item);
76}
77
78void KAcceleratorManagerPrivate::manage(QWidget *widget)
79{
80 if (!widget) {
81 qCDebug(KWidgetsAddonsLog) << "null pointer given to manage";
82 return;
83 }
84
85 if (KAcceleratorManagerPrivate::ignored_widgets.contains(key: widget)) {
86 return;
87 }
88
89 if (qobject_cast<QMenu *>(object: widget)) {
90 // create a popup accel manager that can deal with dynamic menus
91 KPopupAccelManager::manage(popup: static_cast<QMenu *>(widget));
92 return;
93 }
94
95 Item *root = new Item;
96
97 QString used;
98 manageWidget(widget, item: root, used);
99 calculateAccelerators(item: root, used);
100 delete root;
101}
102
103void KAcceleratorManagerPrivate::calculateAccelerators(Item *item, QString &used)
104{
105 if (!item->m_children) {
106 return;
107 }
108
109 // collect the contents
110 KAccelStringList contents;
111 contents.reserve(asize: item->m_children->size());
112 for (Item *it : std::as_const(t&: *item->m_children)) {
113 contents << it->m_content;
114 }
115
116 // find the right accelerators
117 KAccelManagerAlgorithm::findAccelerators(result&: contents, used);
118
119 // write them back into the widgets
120 int cnt = -1;
121 for (Item *it : std::as_const(t&: *item->m_children)) {
122 cnt++;
123
124 QDockWidget *dock = qobject_cast<QDockWidget *>(object: it->m_widget);
125 if (dock) {
126 if (checkChange(as: contents[cnt])) {
127 dock->setWindowTitle(contents[cnt].accelerated());
128 }
129 continue;
130 }
131 QTabBar *tabBar = qobject_cast<QTabBar *>(object: it->m_widget);
132 if (tabBar) {
133 if (checkChange(as: contents[cnt])) {
134 tabBar->setTabText(index: it->m_index, text: contents[cnt].accelerated());
135 }
136 continue;
137 }
138 QMenuBar *menuBar = qobject_cast<QMenuBar *>(object: it->m_widget);
139 if (menuBar) {
140 if (it->m_index >= 0) {
141 QAction *maction = menuBar->actions()[it->m_index];
142 if (maction) {
143 checkChange(as: contents[cnt]);
144 maction->setText(contents[cnt].accelerated());
145 }
146 continue;
147 }
148 }
149
150 // we possibly reserved an accel, but we won't set it as it looks silly
151 QGroupBox *groupBox = qobject_cast<QGroupBox *>(object: it->m_widget);
152 if (groupBox && !groupBox->isCheckable()) {
153 continue;
154 }
155
156 int tprop = it->m_widget->metaObject()->indexOfProperty(name: "text");
157 if (tprop != -1) {
158 if (checkChange(as: contents[cnt])) {
159 it->m_widget->setProperty(name: "text", value: contents[cnt].accelerated());
160 }
161 } else {
162 tprop = it->m_widget->metaObject()->indexOfProperty(name: "title");
163 if (tprop != -1 && checkChange(as: contents[cnt])) {
164 it->m_widget->setProperty(name: "title", value: contents[cnt].accelerated());
165 }
166 }
167 }
168
169 // calculate the accelerators for the children
170 for (Item *it : std::as_const(t&: *item->m_children)) {
171 if (it->m_widget && it->m_widget->isVisibleTo(item->m_widget)) {
172 calculateAccelerators(item: it, used);
173 }
174 }
175}
176
177void KAcceleratorManagerPrivate::traverseChildren(QWidget *widget, Item *item, QString &used)
178{
179 // we are only interested in direct child widgets
180 const QList<QWidget *> childList = widget->findChildren<QWidget *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
181 for (QWidget *w : childList) {
182 if (!w->isVisibleTo(widget) || (w->isWindow() && qobject_cast<QMenu *>(object: w) == nullptr)) {
183 continue;
184 }
185
186 if (KAcceleratorManagerPrivate::ignored_widgets.contains(key: w)) {
187 continue;
188 }
189
190 manageWidget(widget: w, item, used);
191 }
192}
193
194void KAcceleratorManagerPrivate::manageWidget(QWidget *w, Item *item, QString &used)
195{
196 // If the widget has any action whose shortcuts contain keystrokes in the
197 // form of Alt+X we need to mark X as used, otherwise we may assign it as accelerator
198 // and there will be a conflict when trying to use it
199 const QList<QAction *> widgetActions = w->actions();
200 for (QAction *action : widgetActions) {
201 const QList<QKeySequence> actionShortcuts = action->shortcuts();
202 for (const QKeySequence &sequence : actionShortcuts) {
203 const QString sequenceAsText = sequence.toString(format: QKeySequence::PortableText);
204 const QStringList splitSequence = sequenceAsText.split(QStringLiteral(", "));
205 for (const QString &shortcut : splitSequence) {
206 if (shortcut.length() == 5 && shortcut.startsWith(QStringLiteral("Alt+"))) {
207 used.append(s: shortcut.right(n: 1));
208 }
209 }
210 }
211 }
212
213 // first treat the special cases
214
215 QTabBar *tabBar = qobject_cast<QTabBar *>(object: w);
216 if (tabBar) {
217 manageTabBar(bar: tabBar, item);
218 return;
219 }
220
221 QStackedWidget *wds = qobject_cast<QStackedWidget *>(object: w);
222 if (wds) {
223 QWidgetStackAccelManager::manage(popup: wds);
224 // return;
225 }
226
227 QDockWidget *dock = qobject_cast<QDockWidget *>(object: w);
228 if (dock) {
229 // QWidgetStackAccelManager::manage( wds );
230 manageDockWidget(dock, item);
231 }
232
233 QMenu *popupMenu = qobject_cast<QMenu *>(object: w);
234 if (popupMenu) {
235 // create a popup accel manager that can deal with dynamic menus
236 KPopupAccelManager::manage(popup: popupMenu);
237 return;
238 }
239
240 QStackedWidget *wdst = qobject_cast<QStackedWidget *>(object: w);
241 if (wdst) {
242 QWidgetStackAccelManager::manage(popup: wdst);
243 // return;
244 }
245
246 QMenuBar *menuBar = qobject_cast<QMenuBar *>(object: w);
247 if (menuBar) {
248 manageMenuBar(mbar: menuBar, item);
249 return;
250 }
251
252 if (qobject_cast<QComboBox *>(object: w) || qobject_cast<QLineEdit *>(object: w) //
253 || w->inherits(classname: "Q3TextEdit") //
254 || qobject_cast<QTextEdit *>(object: w) //
255 || qobject_cast<QAbstractSpinBox *>(object: w) //
256 || w->inherits(classname: "KMultiTabBar") //
257 || w->inherits(classname: "qdesigner_internal::TextPropertyEditor")) {
258 return;
259 }
260
261 if (w->inherits(classname: "KUrlRequester")) {
262 traverseChildren(widget: w, item, used);
263 return;
264 }
265
266 // now treat 'ordinary' widgets
267 QLabel *label = qobject_cast<QLabel *>(object: w);
268 if (label) {
269 if (!label->buddy()) {
270 return;
271 } else {
272 if (label->textFormat() == Qt::RichText //
273 || (label->textFormat() == Qt::AutoText && Qt::mightBeRichText(label->text()))) {
274 return;
275 }
276 }
277 }
278
279 if (w->focusPolicy() != Qt::NoFocus || label || qobject_cast<QGroupBox *>(object: w) || qobject_cast<QRadioButton *>(object: w)) {
280 QString content;
281 QVariant variant;
282 int tprop = w->metaObject()->indexOfProperty(name: "text");
283 if (tprop != -1) {
284 QMetaProperty p = w->metaObject()->property(index: tprop);
285 if (p.isValid() && p.isWritable()) {
286 variant = p.read(obj: w);
287 } else {
288 tprop = -1;
289 }
290 }
291
292 if (tprop == -1) {
293 tprop = w->metaObject()->indexOfProperty(name: "title");
294 if (tprop != -1) {
295 QMetaProperty p = w->metaObject()->property(index: tprop);
296 if (p.isValid() && p.isWritable()) {
297 variant = p.read(obj: w);
298 }
299 }
300 }
301
302 if (variant.isValid()) {
303 content = variant.toString();
304 }
305
306 if (!content.isEmpty()) {
307 Item *i = new Item;
308 i->m_widget = w;
309
310 // put some more weight on the usual action elements
311 int weight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
312 if (qobject_cast<QPushButton *>(object: w) || qobject_cast<QCheckBox *>(object: w) || qobject_cast<QRadioButton *>(object: w) || qobject_cast<QLabel *>(object: w)) {
313 weight = KAccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT;
314 }
315
316 // don't put weight on non-checkable group boxes,
317 // as usually the contents are more important
318 QGroupBox *groupBox = qobject_cast<QGroupBox *>(object: w);
319 if (groupBox) {
320 if (groupBox->isCheckable()) {
321 weight = KAccelManagerAlgorithm::CHECKABLE_GROUP_BOX_WEIGHT;
322 } else {
323 weight = KAccelManagerAlgorithm::GROUP_BOX_WEIGHT;
324 }
325 }
326
327 i->m_content = KAccelString(content, weight);
328 item->addChild(item: i);
329 }
330 }
331 traverseChildren(widget: w, item, used);
332}
333
334void KAcceleratorManagerPrivate::manageTabBar(QTabBar *bar, Item *item)
335{
336 // ignore QTabBar for QDockWidgets, because QDockWidget on its title change
337 // also updates its tabbar entry, so on the next run of KCheckAccelerators
338 // this looks like a conflict and triggers a new reset of the shortcuts -> endless loop
339 QWidget *parentWidget = bar->parentWidget();
340 if (parentWidget) {
341 QMainWindow *mainWindow = qobject_cast<QMainWindow *>(object: parentWidget);
342 // TODO: find better hints that this is a QTabBar for QDockWidgets
343 if (mainWindow) { // && (mainWindow->layout()->indexOf(bar) != -1)) QMainWindowLayout lacks proper support
344 return;
345 }
346 }
347
348 for (int i = 0; i < bar->count(); i++) {
349 QString content = bar->tabText(index: i);
350 if (content.isEmpty()) {
351 continue;
352 }
353
354 Item *it = new Item;
355 item->addChild(item: it);
356 it->m_widget = bar;
357 it->m_index = i;
358 it->m_content = KAccelString(content);
359 }
360}
361
362void KAcceleratorManagerPrivate::manageDockWidget(QDockWidget *dock, Item *item)
363{
364 // As of Qt 4.4.3 setting a shortcut to a QDockWidget has no effect,
365 // because a QDockWidget does not grab it, even while displaying an underscore
366 // in the title for the given shortcut letter.
367 // Still it is useful to set the shortcut, because if QDockWidgets are tabbed,
368 // the tab automatically gets the same text as the QDockWidget title, including the shortcut.
369 // And for the QTabBar the shortcut does work, it gets grabbed as usual.
370 // Having the QDockWidget without a shortcut and resetting the tab text with a title including
371 // the shortcut does not work, the tab text is instantly reverted to the QDockWidget title
372 // (see also manageTabBar()).
373 // All in all QDockWidgets and shortcuts are a little broken for now.
374 QString content = dock->windowTitle();
375 if (content.isEmpty()) {
376 return;
377 }
378
379 Item *it = new Item;
380 item->addChild(item: it);
381 it->m_widget = dock;
382 it->m_content = KAccelString(content, KAccelManagerAlgorithm::STANDARD_ACCEL);
383}
384
385void KAcceleratorManagerPrivate::manageMenuBar(QMenuBar *mbar, Item *item)
386{
387 QAction *maction;
388 QString s;
389
390 for (int i = 0; i < mbar->actions().count(); ++i) {
391 maction = mbar->actions()[i];
392 if (!maction) {
393 continue;
394 }
395
396 // nothing to do for separators
397 if (maction->isSeparator()) {
398 continue;
399 }
400
401 s = maction->text();
402 if (!s.isEmpty()) {
403 Item *it = new Item;
404 item->addChild(item: it);
405 it->m_content = KAccelString(s,
406 KAccelManagerAlgorithm::MENU_TITLE_WEIGHT); // menu titles are important, so raise the weight
407
408 it->m_widget = mbar;
409 it->m_index = i;
410 }
411
412 // have a look at the popup as well, if present
413 if (maction->menu()) {
414 KPopupAccelManager::manage(popup: maction->menu());
415 }
416 }
417}
418
419/*********************************************************************
420
421 class KAcceleratorManager - main entry point
422
423 This class is just here to provide a clean public API...
424
425 *********************************************************************/
426
427void KAcceleratorManager::manage(QWidget *widget, bool programmers_mode)
428{
429 KAcceleratorManagerPrivate::changed_string.clear();
430 KAcceleratorManagerPrivate::added_string.clear();
431 KAcceleratorManagerPrivate::removed_string.clear();
432 KAcceleratorManagerPrivate::programmers_mode = programmers_mode;
433 KAcceleratorManagerPrivate::manage(widget);
434}
435
436void KAcceleratorManager::last_manage(QString &added, QString &changed, QString &removed)
437{
438 added = KAcceleratorManagerPrivate::added_string;
439 changed = KAcceleratorManagerPrivate::changed_string;
440 removed = KAcceleratorManagerPrivate::removed_string;
441}
442
443/*********************************************************************
444
445 class KAccelString - a string with weighted characters
446
447 *********************************************************************/
448
449KAccelString::KAccelString(const QString &input, int initialWeight)
450 : m_pureText(input)
451 , m_weight()
452{
453 m_orig_accel = m_pureText.indexOf(s: QLatin1String("(!)&"));
454 if (m_orig_accel != -1) {
455 m_pureText.remove(i: m_orig_accel, len: 4);
456 }
457
458 m_orig_accel = m_pureText.indexOf(s: QLatin1String("(&&)"));
459 if (m_orig_accel != -1) {
460 m_pureText.replace(i: m_orig_accel, len: 4, QStringLiteral("&"));
461 }
462
463 m_origText = m_pureText;
464
465 const int tabPos = m_pureText.indexOf(c: QLatin1Char('\t'));
466 if (tabPos != -1) {
467 m_pureText.truncate(pos: tabPos);
468 }
469
470 m_orig_accel = m_accel = stripAccelerator(input&: m_pureText);
471
472 if (initialWeight == -1) {
473 initialWeight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
474 }
475
476 calculateWeights(initialWeight);
477
478 // dump();
479}
480
481QString KAccelString::accelerated() const
482{
483 QString result = m_origText;
484 if (result.isEmpty()) {
485 return result;
486 }
487
488 if (KAcceleratorManagerPrivate::programmers_mode) {
489 if (m_accel != m_orig_accel) {
490 int oa = m_orig_accel;
491
492 if (m_accel >= 0) {
493 result.insert(i: m_accel, s: QLatin1String("(!)&"));
494 if (m_accel < m_orig_accel) {
495 oa += 4;
496 }
497 }
498 if (m_orig_accel >= 0) {
499 result.replace(i: oa, len: 1, QStringLiteral("(&&)"));
500 }
501 }
502 } else {
503 if (m_accel >= 0 && m_orig_accel != m_accel) {
504 if (m_orig_accel != -1) {
505 result.remove(i: m_orig_accel, len: 1);
506 }
507 result.insert(i: m_accel, c: QLatin1Char('&'));
508 }
509 }
510 return result;
511}
512
513QChar KAccelString::accelerator() const
514{
515 if ((m_accel < 0) || (m_accel > m_pureText.length())) {
516 return QChar();
517 }
518
519 return m_pureText[m_accel].toLower();
520}
521
522void KAccelString::calculateWeights(int initialWeight)
523{
524 m_weight.resize(size: m_pureText.length());
525
526 int pos = 0;
527 bool start_character = true;
528
529 while (pos < m_pureText.length()) {
530 QChar c = m_pureText[pos];
531
532 int weight = initialWeight + 1;
533
534 // add special weight to first character
535 if (pos == 0) {
536 weight += KAccelManagerAlgorithm::FIRST_CHARACTER_EXTRA_WEIGHT;
537 }
538
539 // add weight to word beginnings
540 if (start_character) {
541 weight += KAccelManagerAlgorithm::WORD_BEGINNING_EXTRA_WEIGHT;
542 start_character = false;
543 }
544
545 // add decreasing weight to left characters
546 if (pos < 50) {
547 weight += (50 - pos);
548 }
549
550 // try to preserve the wanted accelerators
551 if (pos == accel()) {
552 weight += KAccelManagerAlgorithm::WANTED_ACCEL_EXTRA_WEIGHT;
553 // qCDebug(KWidgetsAddonsLog) << "wanted " << m_pureText << " " << KAcceleratorManagerPrivate::standardName(m_origText);
554 if (KAcceleratorManagerPrivate::standardName(str: m_origText)) {
555 weight += KAccelManagerAlgorithm::STANDARD_ACCEL;
556 }
557 }
558
559 // skip non typeable characters
560 if (!c.isLetterOrNumber()) {
561 weight = 0;
562 start_character = true;
563 }
564
565 m_weight[pos] = weight;
566
567 ++pos;
568 }
569}
570
571int KAccelString::stripAccelerator(QString &text)
572{
573 // Note: this code is derived from QAccel::shortcutKey
574 int p = 0;
575
576 while (p >= 0) {
577 p = text.indexOf(c: QLatin1Char('&'), from: p) + 1;
578
579 if (p <= 0 || p >= text.length()) {
580 break;
581 }
582
583 if (text[p] != QLatin1Char('&')) {
584 QChar c = text[p];
585 if (c.isPrint()) {
586 text.remove(i: p - 1, len: 1);
587 return p - 1;
588 }
589 }
590
591 p++;
592 }
593
594 return -1;
595}
596
597int KAccelString::maxWeight(int &index, const QString &used) const
598{
599 int max = 0;
600 index = -1;
601
602 for (int pos = 0; pos < m_pureText.length(); ++pos) {
603 if (used.indexOf(c: m_pureText[pos], from: 0, cs: Qt::CaseInsensitive) == -1 && m_pureText[pos].toLatin1() != 0) {
604 if (m_weight[pos] > max) {
605 max = m_weight[pos];
606 index = pos;
607 }
608 }
609 }
610
611 return max;
612}
613
614void KAccelString::dump()
615{
616 QString s;
617 for (int i = 0; i < m_weight.count(); ++i) {
618 s += QStringLiteral("%1(%2) ").arg(a: pure()[i]).arg(a: m_weight[i]);
619 }
620 qCDebug(KWidgetsAddonsLog) << "s " << s;
621}
622
623/*********************************************************************
624
625 findAccelerators - the algorithm determining the new accelerators
626
627 The algorithm is very crude:
628
629 * each character in each widget text is assigned a weight
630 * the character with the highest weight over all is picked
631 * that widget is removed from the list
632 * the weights are recalculated
633 * the process is repeated until no more accelerators can be found
634
635 The algorithm has some advantages:
636
637 * it favors 'nice' accelerators (first characters in a word, etc.)
638 * it is quite fast, O(N²)
639 * it is easy to understand :-)
640
641 The disadvantages:
642
643 * it does not try to find as many accelerators as possible
644
645 TODO:
646
647 * The result is always correct, but not necessarily optimal. Perhaps
648 it would be a good idea to add another algorithm with higher complexity
649 that gets used when this one fails, i.e. leaves widgets without
650 accelerators.
651
652 * The weights probably need some tweaking so they make more sense.
653
654 *********************************************************************/
655
656void KAccelManagerAlgorithm::findAccelerators(KAccelStringList &result, QString &used)
657{
658 KAccelStringList accel_strings = result;
659
660 // initially remove all accelerators
661 for (KAccelStringList::Iterator it = result.begin(), total = result.end(); it != total; ++it) {
662 (*it).setAccel(-1);
663 }
664
665 // pick the highest bids
666 for (int cnt = 0; cnt < accel_strings.count(); ++cnt) {
667 int max = 0;
668 int index = -1;
669 int accel = -1;
670
671 // find maximum weight
672 for (int i = 0; i < accel_strings.count(); ++i) {
673 int a;
674 int m = accel_strings[i].maxWeight(index&: a, used);
675 if (m > max) {
676 max = m;
677 index = i;
678 accel = a;
679 }
680 }
681
682 // stop if no more accelerators can be found
683 if (index < 0) {
684 return;
685 }
686
687 // insert the accelerator
688 if (accel >= 0) {
689 result[index].setAccel(accel);
690 used.append(c: result[index].accelerator());
691 }
692
693 // make sure we don't visit this one again
694 accel_strings[index] = KAccelString();
695 }
696}
697
698/*********************************************************************
699
700 class KPopupAccelManager - managing QMenu widgets dynamically
701
702 *********************************************************************/
703
704KPopupAccelManager::KPopupAccelManager(QMenu *popup)
705 : QObject(popup)
706 , m_popup(popup)
707 , m_count(-1)
708{
709 aboutToShow(); // do one check and then connect to show
710 connect(sender: popup, signal: &QMenu::aboutToShow, context: this, slot: &KPopupAccelManager::aboutToShow);
711}
712
713void KPopupAccelManager::aboutToShow()
714{
715 // Note: we try to be smart and avoid recalculating the accelerators
716 // whenever possible. Unfortunately, there is no way to know if an
717 // item has been added or removed, so we can not do much more than
718 // to compare the items each time the menu is shown :-(
719
720 if (m_count != m_popup->actions().count()) {
721 findMenuEntries(list&: m_entries);
722 calculateAccelerators();
723 m_count = m_popup->actions().count();
724 } else {
725 KAccelStringList entries;
726 findMenuEntries(list&: entries);
727 if (entries != m_entries) {
728 m_entries = entries;
729 calculateAccelerators();
730 }
731 }
732}
733
734void KPopupAccelManager::calculateAccelerators()
735{
736 // find the new accelerators
737 QString used;
738 KAccelManagerAlgorithm::findAccelerators(result&: m_entries, used);
739
740 // change the menu entries
741 setMenuEntries(m_entries);
742}
743
744void KPopupAccelManager::findMenuEntries(KAccelStringList &list)
745{
746 QString s;
747
748 list.clear();
749
750 // read out the menu entries
751 const auto menuActions = m_popup->actions();
752 for (QAction *maction : menuActions) {
753 if (maction->isSeparator()) {
754 continue;
755 }
756
757 s = maction->text();
758
759 // in full menus, look at entries with global accelerators last
760 int weight = 50;
761 if (s.contains(c: QLatin1Char('\t'))) {
762 weight = 0;
763 }
764
765 list.append(t: KAccelString(s, weight));
766
767 // have a look at the popup as well, if present
768 if (maction->menu()) {
769 KPopupAccelManager::manage(popup: maction->menu());
770 }
771 }
772}
773
774// Duplicated from qaction.cpp
775static QString copy_of_qt_strippedText(QString s)
776{
777 s.remove(s: QLatin1String("..."));
778 for (int i = 0; i < s.size(); ++i) {
779 if (s.at(i) == QLatin1Char('&')) {
780 s.remove(i, len: 1);
781 }
782 }
783 return s.trimmed();
784}
785
786void KPopupAccelManager::setMenuEntries(const KAccelStringList &list)
787{
788 uint cnt = 0;
789 const auto menuActions = m_popup->actions();
790 for (QAction *maction : menuActions) {
791 if (maction->isSeparator()) {
792 continue;
793 }
794
795 QString iconText = maction->iconText();
796 const QString oldText = maction->text();
797
798 // Check if iconText was generated by Qt. In that case ignore it (no support for CJK accelerators) and set it from the text.
799 if (iconText == copy_of_qt_strippedText(s: oldText)) {
800 iconText = removeAcceleratorMarker(label: oldText);
801
802 // ensure we don't re-add the ... that Qt removes per default
803 iconText.remove(s: QLatin1String("..."));
804
805 if (iconText != maction->iconText()) {
806 maction->setIconText(iconText);
807 }
808 }
809
810 if (KAcceleratorManagerPrivate::checkChange(as: list[cnt])) {
811 maction->setText(list[cnt].accelerated());
812 }
813 cnt++;
814 }
815}
816
817void KPopupAccelManager::manage(QMenu *popup)
818{
819 // don't add more than one manager to a popup
820 if (popup->findChild<KPopupAccelManager *>(aName: QString()) == nullptr) {
821 new KPopupAccelManager(popup);
822 }
823}
824
825void QWidgetStackAccelManager::manage(QStackedWidget *stack)
826{
827 if (stack->findChild<QWidgetStackAccelManager *>(aName: QString()) == nullptr) {
828 new QWidgetStackAccelManager(stack);
829 }
830}
831
832QWidgetStackAccelManager::QWidgetStackAccelManager(QStackedWidget *stack)
833 : QObject(stack)
834 , m_stack(stack)
835{
836 currentChanged(child: stack->currentIndex()); // do one check and then connect to show
837 connect(sender: stack, signal: &QStackedWidget::currentChanged, context: this, slot: &QWidgetStackAccelManager::currentChanged);
838}
839
840bool QWidgetStackAccelManager::eventFilter(QObject *watched, QEvent *e)
841{
842 if (e->type() == QEvent::Show && qApp->activeWindow()) {
843 KAcceleratorManager::manage(qApp->activeWindow());
844 watched->removeEventFilter(obj: this);
845 }
846 return false;
847}
848
849void QWidgetStackAccelManager::currentChanged(int child)
850{
851 if (child < 0 || child >= static_cast<QStackedWidget *>(parent())->count()) {
852 // NOTE: QStackedWidget emits currentChanged(-1) when it is emptied
853 return;
854 }
855
856 static_cast<QStackedWidget *>(parent())->widget(child)->installEventFilter(filterObj: this);
857}
858
859void KAcceleratorManager::setNoAccel(QWidget *widget)
860{
861 KAcceleratorManagerPrivate::ignored_widgets[widget] = 1;
862}
863
864void KAcceleratorManager::addStandardActionNames(const QStringList &names)
865{
866 KAcceleratorManagerPrivate::addStandardActionNames(list: names);
867}
868
869#include "moc_kacceleratormanager_p.cpp"
870

source code of kwidgetsaddons/src/kacceleratormanager.cpp