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

source code of kwidgetsaddons/src/kacceleratormanager.cpp