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