File indexing completed on 2024-04-28 15:31:56

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2002 Matthias Hölzer-Klüpfel <mhk@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kacceleratormanager.h"
0009 #include "kacceleratormanager_p.h"
0010 
0011 #include <QApplication>
0012 #include <QCheckBox>
0013 #include <QComboBox>
0014 #include <QDockWidget>
0015 #include <QGroupBox>
0016 #include <QLabel>
0017 #include <QLineEdit>
0018 #include <QList>
0019 #include <QMainWindow>
0020 #include <QMenuBar>
0021 #include <QMetaProperty>
0022 #include <QObject>
0023 #include <QPushButton>
0024 #include <QRadioButton>
0025 #include <QStackedWidget>
0026 #include <QTabBar>
0027 #include <QTextEdit>
0028 #include <QWidget>
0029 
0030 #include "common_helpers_p.h"
0031 #include "loggingcategory.h"
0032 
0033 /*********************************************************************
0034 
0035  class Item - helper class containing widget information
0036 
0037  This class stores information about the widgets the need accelerators,
0038  as well as about their relationship.
0039 
0040  *********************************************************************/
0041 
0042 bool KAcceleratorManagerPrivate::programmers_mode = false;
0043 QString KAcceleratorManagerPrivate::changed_string;
0044 QString KAcceleratorManagerPrivate::added_string;
0045 QString KAcceleratorManagerPrivate::removed_string;
0046 QMap<QWidget *, int> KAcceleratorManagerPrivate::ignored_widgets;
0047 QStringList KAcceleratorManagerPrivate::standardNames;
0048 
0049 void KAcceleratorManagerPrivate::addStandardActionNames(const QStringList &list)
0050 {
0051     standardNames.append(list);
0052 }
0053 
0054 bool KAcceleratorManagerPrivate::standardName(const QString &str)
0055 {
0056     return standardNames.contains(str);
0057 }
0058 
0059 KAcceleratorManagerPrivate::Item::~Item()
0060 {
0061     if (m_children) {
0062         while (!m_children->isEmpty()) {
0063             delete m_children->takeFirst();
0064         }
0065         delete m_children;
0066     }
0067 }
0068 
0069 void KAcceleratorManagerPrivate::Item::addChild(Item *item)
0070 {
0071     if (!m_children) {
0072         m_children = new ItemList;
0073     }
0074 
0075     m_children->append(item);
0076 }
0077 
0078 void KAcceleratorManagerPrivate::manage(QWidget *widget)
0079 {
0080     if (!widget) {
0081         qCDebug(KWidgetsAddonsLog) << "null pointer given to manage";
0082         return;
0083     }
0084 
0085     if (KAcceleratorManagerPrivate::ignored_widgets.contains(widget)) {
0086         return;
0087     }
0088 
0089     if (qobject_cast<QMenu *>(widget)) {
0090         // create a popup accel manager that can deal with dynamic menus
0091         KPopupAccelManager::manage(static_cast<QMenu *>(widget));
0092         return;
0093     }
0094 
0095     Item *root = new Item;
0096 
0097     QString used;
0098     manageWidget(widget, root, used);
0099     calculateAccelerators(root, used);
0100     delete root;
0101 }
0102 
0103 void KAcceleratorManagerPrivate::calculateAccelerators(Item *item, QString &used)
0104 {
0105     if (!item->m_children) {
0106         return;
0107     }
0108 
0109     // collect the contents
0110     KAccelStringList contents;
0111     contents.reserve(item->m_children->size());
0112     for (Item *it : std::as_const(*item->m_children)) {
0113         contents << it->m_content;
0114     }
0115 
0116     // find the right accelerators
0117     KAccelManagerAlgorithm::findAccelerators(contents, used);
0118 
0119     // write them back into the widgets
0120     int cnt = -1;
0121     for (Item *it : std::as_const(*item->m_children)) {
0122         cnt++;
0123 
0124         QDockWidget *dock = qobject_cast<QDockWidget *>(it->m_widget);
0125         if (dock) {
0126             if (checkChange(contents[cnt])) {
0127                 dock->setWindowTitle(contents[cnt].accelerated());
0128             }
0129             continue;
0130         }
0131         QTabBar *tabBar = qobject_cast<QTabBar *>(it->m_widget);
0132         if (tabBar) {
0133             if (checkChange(contents[cnt])) {
0134                 tabBar->setTabText(it->m_index, contents[cnt].accelerated());
0135             }
0136             continue;
0137         }
0138         QMenuBar *menuBar = qobject_cast<QMenuBar *>(it->m_widget);
0139         if (menuBar) {
0140             if (it->m_index >= 0) {
0141                 QAction *maction = menuBar->actions()[it->m_index];
0142                 if (maction) {
0143                     checkChange(contents[cnt]);
0144                     maction->setText(contents[cnt].accelerated());
0145                 }
0146                 continue;
0147             }
0148         }
0149 
0150         // we possibly reserved an accel, but we won't set it as it looks silly
0151         QGroupBox *groupBox = qobject_cast<QGroupBox *>(it->m_widget);
0152         if (groupBox && !groupBox->isCheckable()) {
0153             continue;
0154         }
0155 
0156         int tprop = it->m_widget->metaObject()->indexOfProperty("text");
0157         if (tprop != -1) {
0158             if (checkChange(contents[cnt])) {
0159                 it->m_widget->setProperty("text", contents[cnt].accelerated());
0160             }
0161         } else {
0162             tprop = it->m_widget->metaObject()->indexOfProperty("title");
0163             if (tprop != -1 && checkChange(contents[cnt])) {
0164                 it->m_widget->setProperty("title", contents[cnt].accelerated());
0165             }
0166         }
0167     }
0168 
0169     // calculate the accelerators for the children
0170     for (Item *it : std::as_const(*item->m_children)) {
0171         if (it->m_widget && it->m_widget->isVisibleTo(item->m_widget)) {
0172             calculateAccelerators(it, used);
0173         }
0174     }
0175 }
0176 
0177 void KAcceleratorManagerPrivate::traverseChildren(QWidget *widget, Item *item, QString &used)
0178 {
0179     // we are only interested in direct child widgets
0180     const QList<QWidget *> childList = widget->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
0181     for (QWidget *w : childList) {
0182         if (!w->isVisibleTo(widget) || (w->isTopLevel() && qobject_cast<QMenu *>(w) == nullptr)) {
0183             continue;
0184         }
0185 
0186         if (KAcceleratorManagerPrivate::ignored_widgets.contains(w)) {
0187             continue;
0188         }
0189 
0190         manageWidget(w, item, used);
0191     }
0192 }
0193 
0194 void KAcceleratorManagerPrivate::manageWidget(QWidget *w, Item *item, QString &used)
0195 {
0196     // If the widget has any action whose shortcuts contain keystrokes in the
0197     // form of Alt+X we need to mark X as used, otherwise we may assign it as accelerator
0198     // and there will be a conflict when trying to use it
0199     const QList<QAction *> widgetActions = w->actions();
0200     for (QAction *action : widgetActions) {
0201         const QList<QKeySequence> actionShortcuts = action->shortcuts();
0202         for (const QKeySequence &sequence : actionShortcuts) {
0203             const QString sequenceAsText = sequence.toString(QKeySequence::PortableText);
0204             const QStringList splitSequence = sequenceAsText.split(QStringLiteral(", "));
0205             for (const QString &shortcut : splitSequence) {
0206                 if (shortcut.length() == 5 && shortcut.startsWith(QStringLiteral("Alt+"))) {
0207                     used.append(shortcut.right(1));
0208                 }
0209             }
0210         }
0211     }
0212 
0213     // first treat the special cases
0214 
0215     QTabBar *tabBar = qobject_cast<QTabBar *>(w);
0216     if (tabBar) {
0217         manageTabBar(tabBar, item);
0218         return;
0219     }
0220 
0221     QStackedWidget *wds = qobject_cast<QStackedWidget *>(w);
0222     if (wds) {
0223         QWidgetStackAccelManager::manage(wds);
0224         // return;
0225     }
0226 
0227     QDockWidget *dock = qobject_cast<QDockWidget *>(w);
0228     if (dock) {
0229         // QWidgetStackAccelManager::manage( wds );
0230         manageDockWidget(dock, item);
0231     }
0232 
0233     QMenu *popupMenu = qobject_cast<QMenu *>(w);
0234     if (popupMenu) {
0235         // create a popup accel manager that can deal with dynamic menus
0236         KPopupAccelManager::manage(popupMenu);
0237         return;
0238     }
0239 
0240     QStackedWidget *wdst = qobject_cast<QStackedWidget *>(w);
0241     if (wdst) {
0242         QWidgetStackAccelManager::manage(wdst);
0243         // return;
0244     }
0245 
0246     QMenuBar *menuBar = qobject_cast<QMenuBar *>(w);
0247     if (menuBar) {
0248         manageMenuBar(menuBar, item);
0249         return;
0250     }
0251 
0252     if (qobject_cast<QComboBox *>(w) || qobject_cast<QLineEdit *>(w) //
0253         || w->inherits("Q3TextEdit") //
0254         || qobject_cast<QTextEdit *>(w) //
0255         || qobject_cast<QAbstractSpinBox *>(w) //
0256         || w->inherits("KMultiTabBar") //
0257         || w->inherits("qdesigner_internal::TextPropertyEditor")) {
0258         return;
0259     }
0260 
0261     if (w->inherits("KUrlRequester")) {
0262         traverseChildren(w, item, used);
0263         return;
0264     }
0265 
0266     // now treat 'ordinary' widgets
0267     QLabel *label = qobject_cast<QLabel *>(w);
0268     if (label) {
0269         if (!label->buddy()) {
0270             return;
0271         } else {
0272             if (label->textFormat() == Qt::RichText //
0273                 || (label->textFormat() == Qt::AutoText && Qt::mightBeRichText(label->text()))) {
0274                 return;
0275             }
0276         }
0277     }
0278 
0279     if (w->focusPolicy() != Qt::NoFocus || label || qobject_cast<QGroupBox *>(w) || qobject_cast<QRadioButton *>(w)) {
0280         QString content;
0281         QVariant variant;
0282         int tprop = w->metaObject()->indexOfProperty("text");
0283         if (tprop != -1) {
0284             QMetaProperty p = w->metaObject()->property(tprop);
0285             if (p.isValid() && p.isWritable()) {
0286                 variant = p.read(w);
0287             } else {
0288                 tprop = -1;
0289             }
0290         }
0291 
0292         if (tprop == -1) {
0293             tprop = w->metaObject()->indexOfProperty("title");
0294             if (tprop != -1) {
0295                 QMetaProperty p = w->metaObject()->property(tprop);
0296                 if (p.isValid() && p.isWritable()) {
0297                     variant = p.read(w);
0298                 }
0299             }
0300         }
0301 
0302         if (variant.isValid()) {
0303             content = variant.toString();
0304         }
0305 
0306         if (!content.isEmpty()) {
0307             Item *i = new Item;
0308             i->m_widget = w;
0309 
0310             // put some more weight on the usual action elements
0311             int weight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
0312             if (qobject_cast<QPushButton *>(w) || qobject_cast<QCheckBox *>(w) || qobject_cast<QRadioButton *>(w) || qobject_cast<QLabel *>(w)) {
0313                 weight = KAccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT;
0314             }
0315 
0316             // don't put weight on non-checkable group boxes,
0317             // as usually the contents are more important
0318             QGroupBox *groupBox = qobject_cast<QGroupBox *>(w);
0319             if (groupBox) {
0320                 if (groupBox->isCheckable()) {
0321                     weight = KAccelManagerAlgorithm::CHECKABLE_GROUP_BOX_WEIGHT;
0322                 } else {
0323                     weight = KAccelManagerAlgorithm::GROUP_BOX_WEIGHT;
0324                 }
0325             }
0326 
0327             i->m_content = KAccelString(content, weight);
0328             item->addChild(i);
0329         }
0330     }
0331     traverseChildren(w, item, used);
0332 }
0333 
0334 void KAcceleratorManagerPrivate::manageTabBar(QTabBar *bar, Item *item)
0335 {
0336     // ignore QTabBar for QDockWidgets, because QDockWidget on its title change
0337     // also updates its tabbar entry, so on the next run of KCheckAccelerators
0338     // this looks like a conflict and triggers a new reset of the shortcuts -> endless loop
0339     QWidget *parentWidget = bar->parentWidget();
0340     if (parentWidget) {
0341         QMainWindow *mainWindow = qobject_cast<QMainWindow *>(parentWidget);
0342         // TODO: find better hints that this is a QTabBar for QDockWidgets
0343         if (mainWindow) { // && (mainWindow->layout()->indexOf(bar) != -1)) QMainWindowLayout lacks proper support
0344             return;
0345         }
0346     }
0347 
0348     for (int i = 0; i < bar->count(); i++) {
0349         QString content = bar->tabText(i);
0350         if (content.isEmpty()) {
0351             continue;
0352         }
0353 
0354         Item *it = new Item;
0355         item->addChild(it);
0356         it->m_widget = bar;
0357         it->m_index = i;
0358         it->m_content = KAccelString(content);
0359     }
0360 }
0361 
0362 void KAcceleratorManagerPrivate::manageDockWidget(QDockWidget *dock, Item *item)
0363 {
0364     // As of Qt 4.4.3 setting a shortcut to a QDockWidget has no effect,
0365     // because a QDockWidget does not grab it, even while displaying an underscore
0366     // in the title for the given shortcut letter.
0367     // Still it is useful to set the shortcut, because if QDockWidgets are tabbed,
0368     // the tab automatically gets the same text as the QDockWidget title, including the shortcut.
0369     // And for the QTabBar the shortcut does work, it gets grabbed as usual.
0370     // Having the QDockWidget without a shortcut and resetting the tab text with a title including
0371     // the shortcut does not work, the tab text is instantly reverted to the QDockWidget title
0372     // (see also manageTabBar()).
0373     // All in all QDockWidgets and shortcuts are a little broken for now.
0374     QString content = dock->windowTitle();
0375     if (content.isEmpty()) {
0376         return;
0377     }
0378 
0379     Item *it = new Item;
0380     item->addChild(it);
0381     it->m_widget = dock;
0382     it->m_content = KAccelString(content, KAccelManagerAlgorithm::STANDARD_ACCEL);
0383 }
0384 
0385 void KAcceleratorManagerPrivate::manageMenuBar(QMenuBar *mbar, Item *item)
0386 {
0387     QAction *maction;
0388     QString s;
0389 
0390     for (int i = 0; i < mbar->actions().count(); ++i) {
0391         maction = mbar->actions()[i];
0392         if (!maction) {
0393             continue;
0394         }
0395 
0396         // nothing to do for separators
0397         if (maction->isSeparator()) {
0398             continue;
0399         }
0400 
0401         s = maction->text();
0402         if (!s.isEmpty()) {
0403             Item *it = new Item;
0404             item->addChild(it);
0405             it->m_content = KAccelString(s,
0406                                          KAccelManagerAlgorithm::MENU_TITLE_WEIGHT); // menu titles are important, so raise the weight
0407 
0408             it->m_widget = mbar;
0409             it->m_index = i;
0410         }
0411 
0412         // have a look at the popup as well, if present
0413         if (maction->menu()) {
0414             KPopupAccelManager::manage(maction->menu());
0415         }
0416     }
0417 }
0418 
0419 /*********************************************************************
0420 
0421  class KAcceleratorManager - main entry point
0422 
0423  This class is just here to provide a clean public API...
0424 
0425  *********************************************************************/
0426 
0427 void KAcceleratorManager::manage(QWidget *widget, bool programmers_mode)
0428 {
0429     KAcceleratorManagerPrivate::changed_string.clear();
0430     KAcceleratorManagerPrivate::added_string.clear();
0431     KAcceleratorManagerPrivate::removed_string.clear();
0432     KAcceleratorManagerPrivate::programmers_mode = programmers_mode;
0433     KAcceleratorManagerPrivate::manage(widget);
0434 }
0435 
0436 void KAcceleratorManager::last_manage(QString &added, QString &changed, QString &removed)
0437 {
0438     added = KAcceleratorManagerPrivate::added_string;
0439     changed = KAcceleratorManagerPrivate::changed_string;
0440     removed = KAcceleratorManagerPrivate::removed_string;
0441 }
0442 
0443 /*********************************************************************
0444 
0445  class KAccelString - a string with weighted characters
0446 
0447  *********************************************************************/
0448 
0449 KAccelString::KAccelString(const QString &input, int initialWeight)
0450     : m_pureText(input)
0451     , m_weight()
0452 {
0453     m_orig_accel = m_pureText.indexOf(QLatin1String("(!)&"));
0454     if (m_orig_accel != -1) {
0455         m_pureText.remove(m_orig_accel, 4);
0456     }
0457 
0458     m_orig_accel = m_pureText.indexOf(QLatin1String("(&&)"));
0459     if (m_orig_accel != -1) {
0460         m_pureText.replace(m_orig_accel, 4, QStringLiteral("&"));
0461     }
0462 
0463     m_origText = m_pureText;
0464 
0465     const int tabPos = m_pureText.indexOf(QLatin1Char('\t'));
0466     if (tabPos != -1) {
0467         m_pureText.truncate(tabPos);
0468     }
0469 
0470     m_orig_accel = m_accel = stripAccelerator(m_pureText);
0471 
0472     if (initialWeight == -1) {
0473         initialWeight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
0474     }
0475 
0476     calculateWeights(initialWeight);
0477 
0478     // dump();
0479 }
0480 
0481 QString KAccelString::accelerated() const
0482 {
0483     QString result = m_origText;
0484     if (result.isEmpty()) {
0485         return result;
0486     }
0487 
0488     if (KAcceleratorManagerPrivate::programmers_mode) {
0489         if (m_accel != m_orig_accel) {
0490             int oa = m_orig_accel;
0491 
0492             if (m_accel >= 0) {
0493                 result.insert(m_accel, QLatin1String("(!)&"));
0494                 if (m_accel < m_orig_accel) {
0495                     oa += 4;
0496                 }
0497             }
0498             if (m_orig_accel >= 0) {
0499                 result.replace(oa, 1, QStringLiteral("(&&)"));
0500             }
0501         }
0502     } else {
0503         if (m_accel >= 0 && m_orig_accel != m_accel) {
0504             if (m_orig_accel != -1) {
0505                 result.remove(m_orig_accel, 1);
0506             }
0507             result.insert(m_accel, QLatin1Char('&'));
0508         }
0509     }
0510     return result;
0511 }
0512 
0513 QChar KAccelString::accelerator() const
0514 {
0515     if ((m_accel < 0) || (m_accel > m_pureText.length())) {
0516         return QChar();
0517     }
0518 
0519     return m_pureText[m_accel].toLower();
0520 }
0521 
0522 void KAccelString::calculateWeights(int initialWeight)
0523 {
0524     m_weight.resize(m_pureText.length());
0525 
0526     int pos = 0;
0527     bool start_character = true;
0528 
0529     while (pos < m_pureText.length()) {
0530         QChar c = m_pureText[pos];
0531 
0532         int weight = initialWeight + 1;
0533 
0534         // add special weight to first character
0535         if (pos == 0) {
0536             weight += KAccelManagerAlgorithm::FIRST_CHARACTER_EXTRA_WEIGHT;
0537         }
0538 
0539         // add weight to word beginnings
0540         if (start_character) {
0541             weight += KAccelManagerAlgorithm::WORD_BEGINNING_EXTRA_WEIGHT;
0542             start_character = false;
0543         }
0544 
0545         // add decreasing weight to left characters
0546         if (pos < 50) {
0547             weight += (50 - pos);
0548         }
0549 
0550         // try to preserve the wanted accelerators
0551         if (pos == accel()) {
0552             weight += KAccelManagerAlgorithm::WANTED_ACCEL_EXTRA_WEIGHT;
0553             // qCDebug(KWidgetsAddonsLog) << "wanted " << m_pureText << " " << KAcceleratorManagerPrivate::standardName(m_origText);
0554             if (KAcceleratorManagerPrivate::standardName(m_origText)) {
0555                 weight += KAccelManagerAlgorithm::STANDARD_ACCEL;
0556             }
0557         }
0558 
0559         // skip non typeable characters
0560         if (!c.isLetterOrNumber()) {
0561             weight = 0;
0562             start_character = true;
0563         }
0564 
0565         m_weight[pos] = weight;
0566 
0567         ++pos;
0568     }
0569 }
0570 
0571 int KAccelString::stripAccelerator(QString &text)
0572 {
0573     // Note: this code is derived from QAccel::shortcutKey
0574     int p = 0;
0575 
0576     while (p >= 0) {
0577         p = text.indexOf(QLatin1Char('&'), p) + 1;
0578 
0579         if (p <= 0 || p >= text.length()) {
0580             break;
0581         }
0582 
0583         if (text[p] != QLatin1Char('&')) {
0584             QChar c = text[p];
0585             if (c.isPrint()) {
0586                 text.remove(p - 1, 1);
0587                 return p - 1;
0588             }
0589         }
0590 
0591         p++;
0592     }
0593 
0594     return -1;
0595 }
0596 
0597 int KAccelString::maxWeight(int &index, const QString &used) const
0598 {
0599     int max = 0;
0600     index = -1;
0601 
0602     for (int pos = 0; pos < m_pureText.length(); ++pos) {
0603         if (used.indexOf(m_pureText[pos], 0, Qt::CaseInsensitive) == -1 && m_pureText[pos].toLatin1() != 0) {
0604             if (m_weight[pos] > max) {
0605                 max = m_weight[pos];
0606                 index = pos;
0607             }
0608         }
0609     }
0610 
0611     return max;
0612 }
0613 
0614 void KAccelString::dump()
0615 {
0616     QString s;
0617     for (int i = 0; i < m_weight.count(); ++i) {
0618         s += QStringLiteral("%1(%2) ").arg(pure()[i]).arg(m_weight[i]);
0619     }
0620     qCDebug(KWidgetsAddonsLog) << "s " << s;
0621 }
0622 
0623 /*********************************************************************
0624 
0625  findAccelerators - the algorithm determining the new accelerators
0626 
0627  The algorithm is very crude:
0628 
0629    * each character in each widget text is assigned a weight
0630    * the character with the highest weight over all is picked
0631    * that widget is removed from the list
0632    * the weights are recalculated
0633    * the process is repeated until no more accelerators can be found
0634 
0635  The algorithm has some advantages:
0636 
0637    * it favors 'nice' accelerators (first characters in a word, etc.)
0638    * it is quite fast, O(N²)
0639    * it is easy to understand :-)
0640 
0641  The disadvantages:
0642 
0643    * it does not try to find as many accelerators as possible
0644 
0645  TODO:
0646 
0647  * The result is always correct, but not necessarily optimal. Perhaps
0648    it would be a good idea to add another algorithm with higher complexity
0649    that gets used when this one fails, i.e. leaves widgets without
0650    accelerators.
0651 
0652  * The weights probably need some tweaking so they make more sense.
0653 
0654  *********************************************************************/
0655 
0656 void KAccelManagerAlgorithm::findAccelerators(KAccelStringList &result, QString &used)
0657 {
0658     KAccelStringList accel_strings = result;
0659 
0660     // initially remove all accelerators
0661     for (KAccelStringList::Iterator it = result.begin(), total = result.end(); it != total; ++it) {
0662         (*it).setAccel(-1);
0663     }
0664 
0665     // pick the highest bids
0666     for (int cnt = 0; cnt < accel_strings.count(); ++cnt) {
0667         int max = 0;
0668         int index = -1;
0669         int accel = -1;
0670 
0671         // find maximum weight
0672         for (int i = 0; i < accel_strings.count(); ++i) {
0673             int a;
0674             int m = accel_strings[i].maxWeight(a, used);
0675             if (m > max) {
0676                 max = m;
0677                 index = i;
0678                 accel = a;
0679             }
0680         }
0681 
0682         // stop if no more accelerators can be found
0683         if (index < 0) {
0684             return;
0685         }
0686 
0687         // insert the accelerator
0688         if (accel >= 0) {
0689             result[index].setAccel(accel);
0690             used.append(result[index].accelerator());
0691         }
0692 
0693         // make sure we don't visit this one again
0694         accel_strings[index] = KAccelString();
0695     }
0696 }
0697 
0698 /*********************************************************************
0699 
0700  class KPopupAccelManager - managing QMenu widgets dynamically
0701 
0702  *********************************************************************/
0703 
0704 KPopupAccelManager::KPopupAccelManager(QMenu *popup)
0705     : QObject(popup)
0706     , m_popup(popup)
0707     , m_count(-1)
0708 {
0709     aboutToShow(); // do one check and then connect to show
0710     connect(popup, &QMenu::aboutToShow, this, &KPopupAccelManager::aboutToShow);
0711 }
0712 
0713 void KPopupAccelManager::aboutToShow()
0714 {
0715     // Note: we try to be smart and avoid recalculating the accelerators
0716     // whenever possible. Unfortunately, there is no way to know if an
0717     // item has been added or removed, so we can not do much more than
0718     // to compare the items each time the menu is shown :-(
0719 
0720     if (m_count != m_popup->actions().count()) {
0721         findMenuEntries(m_entries);
0722         calculateAccelerators();
0723         m_count = m_popup->actions().count();
0724     } else {
0725         KAccelStringList entries;
0726         findMenuEntries(entries);
0727         if (entries != m_entries) {
0728             m_entries = entries;
0729             calculateAccelerators();
0730         }
0731     }
0732 }
0733 
0734 void KPopupAccelManager::calculateAccelerators()
0735 {
0736     // find the new accelerators
0737     QString used;
0738     KAccelManagerAlgorithm::findAccelerators(m_entries, used);
0739 
0740     // change the menu entries
0741     setMenuEntries(m_entries);
0742 }
0743 
0744 void KPopupAccelManager::findMenuEntries(KAccelStringList &list)
0745 {
0746     QString s;
0747 
0748     list.clear();
0749 
0750     // read out the menu entries
0751     const auto menuActions = m_popup->actions();
0752     for (QAction *maction : menuActions) {
0753         if (maction->isSeparator()) {
0754             continue;
0755         }
0756 
0757         s = maction->text();
0758 
0759         // in full menus, look at entries with global accelerators last
0760         int weight = 50;
0761         if (s.contains(QLatin1Char('\t'))) {
0762             weight = 0;
0763         }
0764 
0765         list.append(KAccelString(s, weight));
0766 
0767         // have a look at the popup as well, if present
0768         if (maction->menu()) {
0769             KPopupAccelManager::manage(maction->menu());
0770         }
0771     }
0772 }
0773 
0774 // Duplicated from qaction.cpp
0775 static QString copy_of_qt_strippedText(QString s)
0776 {
0777     s.remove(QLatin1String("..."));
0778     for (int i = 0; i < s.size(); ++i) {
0779         if (s.at(i) == QLatin1Char('&')) {
0780             s.remove(i, 1);
0781         }
0782     }
0783     return s.trimmed();
0784 }
0785 
0786 void KPopupAccelManager::setMenuEntries(const KAccelStringList &list)
0787 {
0788     uint cnt = 0;
0789     const auto menuActions = m_popup->actions();
0790     for (QAction *maction : menuActions) {
0791         if (maction->isSeparator()) {
0792             continue;
0793         }
0794 
0795         QString iconText = maction->iconText();
0796         const QString oldText = maction->text();
0797 
0798         // Check if iconText was generated by Qt. In that case ignore it (no support for CJK accelerators) and set it from the text.
0799         if (iconText == copy_of_qt_strippedText(oldText)) {
0800             iconText = removeAcceleratorMarker(oldText);
0801 
0802             // ensure we don't re-add the ... that Qt removes per default
0803             iconText.remove(QLatin1String("..."));
0804 
0805             if (iconText != maction->iconText()) {
0806                 maction->setIconText(iconText);
0807             }
0808         }
0809 
0810         if (KAcceleratorManagerPrivate::checkChange(list[cnt])) {
0811             maction->setText(list[cnt].accelerated());
0812         }
0813         cnt++;
0814     }
0815 }
0816 
0817 void KPopupAccelManager::manage(QMenu *popup)
0818 {
0819     // don't add more than one manager to a popup
0820     if (popup->findChild<KPopupAccelManager *>(QString()) == nullptr) {
0821         new KPopupAccelManager(popup);
0822     }
0823 }
0824 
0825 void QWidgetStackAccelManager::manage(QStackedWidget *stack)
0826 {
0827     if (stack->findChild<QWidgetStackAccelManager *>(QString()) == nullptr) {
0828         new QWidgetStackAccelManager(stack);
0829     }
0830 }
0831 
0832 QWidgetStackAccelManager::QWidgetStackAccelManager(QStackedWidget *stack)
0833     : QObject(stack)
0834     , m_stack(stack)
0835 {
0836     currentChanged(stack->currentIndex()); // do one check and then connect to show
0837     connect(stack, &QStackedWidget::currentChanged, this, &QWidgetStackAccelManager::currentChanged);
0838 }
0839 
0840 bool QWidgetStackAccelManager::eventFilter(QObject *watched, QEvent *e)
0841 {
0842     if (e->type() == QEvent::Show && qApp->activeWindow()) {
0843         KAcceleratorManager::manage(qApp->activeWindow());
0844         watched->removeEventFilter(this);
0845     }
0846     return false;
0847 }
0848 
0849 void QWidgetStackAccelManager::currentChanged(int child)
0850 {
0851     if (child < 0 || child >= static_cast<QStackedWidget *>(parent())->count()) {
0852         // NOTE: QStackedWidget emits currentChanged(-1) when it is emptied
0853         return;
0854     }
0855 
0856     static_cast<QStackedWidget *>(parent())->widget(child)->installEventFilter(this);
0857 }
0858 
0859 void KAcceleratorManager::setNoAccel(QWidget *widget)
0860 {
0861     KAcceleratorManagerPrivate::ignored_widgets[widget] = 1;
0862 }
0863 
0864 void KAcceleratorManager::addStandardActionNames(const QStringList &names)
0865 {
0866     KAcceleratorManagerPrivate::addStandardActionNames(names);
0867 }
0868 
0869 #include "moc_kacceleratormanager_p.cpp"