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