Warning, file /frameworks/kwidgetsaddons/src/kacceleratormanager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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"