kaccelmanager.cpp Source File (kdeui Library)

| # kdeui Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

# kaccelmanager.cpp

    00001 /*  This file is part of the KDE project
    00002     Copyright (C) 2002 Matthias Hölzer-Klüpfel <mhk@kde.org>
    00003
    00004     This library is free software; you can redistribute it and/or
    00005     modify it under the terms of the GNU Library General Public
    00006     License as published by the Free Software Foundation; either
    00007     version 2 of the License, or (at your option) any later version.
    00008
    00009     This library is distributed in the hope that it will be useful,
    00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
    00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00012     Library General Public License for more details.
    00013
    00014     You should have received a copy of the GNU Library General Public License
    00015     along with this library; see the file COPYING.LIB.  If not, write to
    00016     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00017     Boston, MA 02111-1307, USA.
    00018 */
    00019
    00020
    00021 #include <qwidget.h>
    00022 #include <qobjectlist.h>
    00023 #include <qapplication.h>
    00024 #include <qpopupmenu.h>
    00025 #include <qmenubar.h>
    00026 #include <qmemarray.h>
    00027 #include <qmainwindow.h>
    00028 #include <qtabbar.h>
    00029 #include <qwidgetstack.h>
    00030 #include <qlabel.h>
    00031 #include <qptrlist.h>
    00032
    00033 #include <kstdaction.h>
    00034 #include <kstaticdeleter.h>
    00035 #include <kdebug.h>
    00036
    00037
    00038 #include "kaccelmanager_private.h"
    00039
    00040
    00041 #include "kaccelmanager.h"
    00042
    00043 // Default control weight
    00044 const int KAccelManagerAlgorithm::DEFAULT_WEIGHT = 50;
    00045 // Additional weight for first character in string
    00046 const int KAccelManagerAlgorithm::FIRST_CHARACTER_EXTRA_WEIGHT = 50;
    00047 // Additional weight for the beginning of a word
    00048 const int KAccelManagerAlgorithm::WORD_BEGINNING_EXTRA_WEIGHT = 50;
    00049 // Additional weight for the dialog buttons (large, we basically never want these reassigned)
    00050 const int KAccelManagerAlgorithm::DIALOG_BUTTON_EXTRA_WEIGHT = 300;
    00051 // Additional weight for a 'wanted' accelerator
    00052 const int KAccelManagerAlgorithm::WANTED_ACCEL_EXTRA_WEIGHT = 150;
    00053 // Default weight for an 'action' widget (ie, pushbuttons)
    00054 const int KAccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT = 50;
    00055 // Default weight for group boxes (low priority)
    00056 const int KAccelManagerAlgorithm::GROUP_BOX_WEIGHT = 0;
    00057 // Default weight for menu titles
    00058 const int KAccelManagerAlgorithm::MENU_TITLE_WEIGHT = 250;
    00059
    00060
    00061 /*********************************************************************
    00062
    00063  class Item - helper class containing widget information
    00064
    00065  This class stores information about the widgets the need accelerators,
    00066  as well as about their relationship.
    00067
    00068  *********************************************************************/
    00069
    00070
    00071
    00072 /*********************************************************************
    00073
    00074  class KAcceleratorManagerPrivate - internal helper class
    00075
    00076  This class does all the work to find accelerators for a hierarchy of
    00077  widgets.
    00078
    00079  *********************************************************************/
    00080
    00081
    00082 class KAcceleratorManagerPrivate
    00083 {
    00084 public:
    00085
    00086     static void manage(QWidget *widget);
    00087     static bool programmers_mode;
    00088     static bool standardName(const QString &str);
    00089
    00090 private:
    00091   class Item;
    00092   typedef QPtrList<Item> ItemList;
    00093
    00094   static void traverseChildren(QWidget *widget, Item *item);
    00095
    00096   static void manageMenuBar(QMenuBar *mbar, Item *item);
    00097   static void manageTabBar(QTabBar *bar, Item *item);
    00098
    00099   static void calculateAccelerators(Item *item, QString &used);
    00100
    00101   class Item
    00102   {
    00103   public:
    00104
    00105     Item() : m_widget(0), m_children(0), m_index(-1) {};
    00106     ~Item();
    00107
    00108     void addChild(Item *item);
    00109
    00110     QWidget       *m_widget;
    00111     KAccelString  m_content;
    00112     ItemList      *m_children;
    00113     int           m_index;
    00114
    00115   };
    00116 };
    00117
    00118
    00119 bool KAcceleratorManagerPrivate::programmers_mode = false;
    00120 static QStringList *kaccmp_sns = 0;
    00121 static KStaticDeleter<QStringList> kaccmp_sns_d;
    00122
    00123 bool KAcceleratorManagerPrivate::standardName(const QString &str)
    00124 {
    00125     if (!kaccmp_sns)
    00126         kaccmp_sns =  kaccmp_sns_d.setObject(new QStringList(KStdAction::stdNames()));
    00127     return kaccmp_sns->contains(str);
    00128 }
    00129
    00130 KAcceleratorManagerPrivate::Item::~Item()
    00131 {
    00132   delete m_children;
    00133 }
    00134
    00135
    00136 void KAcceleratorManagerPrivate::Item::addChild(Item *item)
    00137 {
    00138     if (!m_children)
    00139       m_children = new ItemList;
    00140
    00141     m_children->append(item);
    00142 }
    00143
    00144 void KAcceleratorManagerPrivate::manage(QWidget *widget)
    00145 {
    00146 /*    widget = qApp->activeWindow();
    00147     if ( !widget )
    00148         return;
    00149 */
    00150   if (widget->inherits("QPopupMenu"))
    00151   {
    00152     // create a popup accel manager that can deal with dynamic menues
    00153     KPopupAccelManager::manage(static_cast<QPopupMenu*>(widget));
    00154     return;
    00155   }
    00156
    00157   Item *root = new Item;
    00158
    00159   traverseChildren(widget, root);
    00160
    00161   QString used;
    00162   calculateAccelerators(root, used);
    00163   delete root;
    00164 }
    00165
    00166
    00167 void KAcceleratorManagerPrivate::calculateAccelerators(Item *item, QString &used)
    00168 {
    00169   if (!item->m_children)
    00170     return;
    00171
    00172   // collect the contents
    00173   KAccelStringList contents;
    00174   for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next()) {
    00175       contents << it->m_content;
    00176   }
    00177
    00178   // find the right accelerators
    00179   KAccelManagerAlgorithm::findAccelerators(contents, used);
    00180
    00181   // write them back into the widgets
    00182   int cnt = -1;
    00183   for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next())
    00184   {
    00185     cnt++;
    00186
    00187     if (it->m_widget->inherits("QTabBar"))
    00188     {
    00189       QTabBar *bar = static_cast<QTabBar*>(it->m_widget);
    00190       bar->tabAt(it->m_index)->setText(contents[cnt].accelerated());
    00191
    00192       continue;
    00193     }
    00194     if (it->m_widget->inherits("QMenuBar"))
    00195     {
    00196       QMenuBar *bar = static_cast<QMenuBar*>(it->m_widget);
    00197       if (it->m_index >= 0)
    00198       {
    00199         QMenuItem *mitem = bar->findItem(bar->idAt(it->m_index));
    00200         if (mitem)
    00201           mitem->setText(contents[cnt].accelerated());
    00202       }
    00203       continue;
    00204     }
    00205     if (!it->m_widget->setProperty("text", contents[cnt].accelerated()))
    00206       it->m_widget->setProperty("title", contents[cnt].accelerated());
    00207   }
    00208
    00209   // calculate the accelerators for the children
    00210   for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next()) {
    00211       if (it->m_widget && it->m_widget->isVisibleTo( item->m_widget ))
    00212           calculateAccelerators(it, used);
    00213   }
    00214 }
    00215
    00216
    00217 void KAcceleratorManagerPrivate::traverseChildren(QWidget *widget, Item *item)
    00218 {
    00219   QObjectList *childList = widget->queryList("QWidget", 0, false, false);
    00220   for ( QObject *it = childList->first(); it; it = childList->next() )
    00221   {
    00222     QWidget *w = static_cast<QWidget*>(it);
    00223
    00224     if ( !w->isVisibleTo( widget ) )
    00225         continue;
    00226
    00227     // first treat the special cases
    00228
    00229     if (w->inherits("QTabBar"))
    00230     {
    00231       manageTabBar(static_cast<QTabBar*>(w), item);
    00232       continue;
    00233     }
    00234
    00235     if (w->inherits("QPopupMenu"))
    00236     {
    00237       // create a popup accel manager that can deal with dynamic menues
    00238       KPopupAccelManager::manage(static_cast<QPopupMenu*>(w));
    00239       continue;
    00240     }
    00241
    00242     if (w->inherits("QMenuBar"))
    00243     {
    00244       manageMenuBar(static_cast<QMenuBar*>(w), item);
    00245       continue;
    00246     }
    00247
    00248     if (w->inherits("QComboBox") || w->inherits("QLineEdit") || w->inherits("QTextEdit") || w->inherits("QTextView"))
    00249         continue;
    00250
    00251     // now treat 'ordinary' widgets
    00252     if (w->isFocusEnabled() || (w->inherits("QLabel") && static_cast<QLabel*>(w)->buddy()) || w->inherits("QGroupBox"))
    00253     {
    00254       QString content;
    00255       QVariant variant = w->property("text");
    00256       if (variant.isValid())
    00257           content = variant.toString();
    00258
    00259       if (content.isEmpty())
    00260       {
    00261           variant = w->property("title");
    00262           if (variant.isValid())
    00263               content = variant.toString();
    00264       }
    00265
    00266       if (!content.isEmpty())
    00267       {
    00268           Item *i = new Item;
    00269           i->m_widget = w;
    00270
    00271           // put some more weight on the usual action elements
    00272           int weight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
    00273           if (w->inherits("QPushButton") || w->inherits("QCheckBox") || w->inherits("QRadioButton") || w->inherits("QLabel"))
    00274               weight = KAccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT;
    00275
    00276           // don't put weight on group boxes, as usally the contents are more important
    00277           if (w->inherits("QGroupBox"))
    00278               weight = KAccelManagerAlgorithm::GROUP_BOX_WEIGHT;
    00279
    00280           // put a lot of extra weight on the KDialogBaseButton's
    00281           if (w->inherits("KDialogBaseButton"))
    00282               weight += KAccelManagerAlgorithm::DIALOG_BUTTON_EXTRA_WEIGHT;
    00283
    00284           i->m_content = KAccelString(content, weight);
    00285           item->addChild(i);
    00286       }
    00287     }
    00288
    00289     traverseChildren(w, item);
    00290   }
    00291   delete childList;
    00292 }
    00293
    00294
    00295 void KAcceleratorManagerPrivate::manageTabBar(QTabBar *bar, Item *item)
    00296 {
    00297   for (int i=0; i<bar->count(); i++)
    00298   {
    00299     QString content = bar->tabAt(i)->text();
    00300     if (content.isEmpty())
    00301       continue;
    00302
    00303     Item *it = new Item;
    00304     item->addChild(it);
    00305     it->m_widget = bar;
    00306     it->m_index = i;
    00307     it->m_content = KAccelString(content);
    00308   }
    00309 }
    00310
    00311
    00312 void KAcceleratorManagerPrivate::manageMenuBar(QMenuBar *mbar, Item *item)
    00313 {
    00314   QMenuItem *mitem;
    00315   QString s;
    00316
    00317   for (uint i=0; i<mbar->count(); ++i)
    00318   {
    00319     mitem = mbar->findItem(mbar->idAt(i));
    00320     if (!mitem)
    00321       continue;
    00322
    00323     // nothing to do for separators
    00324     if (mitem->isSeparator())
    00325       continue;
    00326
    00327     s = mitem->text();
    00328     if (!s.isEmpty())
    00329     {
    00330       Item *it = new Item;
    00331       item->addChild(it);
    00332       it->m_content = KAccelString(s, KAccelManagerAlgorithm::MENU_TITLE_WEIGHT); // menu titles are important, so raise the weight
    00333       it->m_widget = mbar;
    00334       it->m_index = i;
    00335     }
    00336
    00337     // have a look at the popup as well, if present
    00338     if (mitem->popup())
    00339       KPopupAccelManager::manage(mitem->popup());
    00340   }
    00341 }
    00342
    00343
    00344 /*********************************************************************
    00345
    00346  class KAcceleratorManager - main entry point
    00347
    00348  This class is just here to provide a clean public API...
    00349
    00350  *********************************************************************/
    00351
    00352 void KAcceleratorManager::manage(QWidget *widget)
    00353 {
    00354     KAcceleratorManager::manage(widget, false);
    00355 }
    00356
    00357 void KAcceleratorManager::manage(QWidget *widget, bool programmers_mode)
    00358 {
    00359     KAcceleratorManagerPrivate::programmers_mode = programmers_mode;
    00360     KAcceleratorManagerPrivate::manage(widget);
    00361 }
    00362
    00363 /*********************************************************************
    00364
    00365  class KAccelString - a string with weighted characters
    00366
    00367  *********************************************************************/
    00368
    00369 KAccelString::KAccelString(const QString &input, int initialWeight)
    00370   : m_pureText(input), m_weight()
    00371 {
    00372     orig_accel = m_pureText.find("(!)&");
    00373     m_pureText.replace(orig_accel, 4, "");
    00374     orig_accel = m_pureText.find("(&&)");
    00375     if (orig_accel != -1)
    00376         m_pureText.replace(orig_accel, 4, "&");
    00377     orig_accel = m_accel = stripAccelerator(m_pureText);
    00378
    00379     kdDebug(125) << input << " " << orig_accel << " " << m_accel << " " << m_pureText << endl;
    00380     if (initialWeight == -1)
    00381         initialWeight = KAccelManagerAlgorithm::DEFAULT_WEIGHT;
    00382
    00383     calculateWeights(initialWeight);
    00384
    00385     dump();
    00386 }
    00387
    00388
    00389 QString KAccelString::accelerated() const
    00390 {
    00391   QString result = m_pureText;
    00392   if (result.isEmpty())
    00393       return result;
    00394
    00395   if (KAcceleratorManagerPrivate::programmers_mode)
    00396   {
    00397       int oa = orig_accel;
    00398
    00399       if (m_accel >= 0) {
    00400           if (m_accel != orig_accel) {
    00401               result.insert(m_accel, "(!)&");
    00402               if (m_accel < orig_accel)
    00403                   oa += 4;
    00404           } else {
    00405               result.insert(m_accel, "&");
    00406               if (m_accel < orig_accel)
    00407                   oa++;
    00408           }
    00409       }
    00410
    00411       if (m_accel != orig_accel && orig_accel >= 0)
    00412           result.insert(oa, "(&&)");
    00413   } else {
    00414       if (m_accel >= 0)
    00415           result.insert(m_accel, "&");
    00416   }
    00417
    00418   return result;
    00419 }
    00420
    00421
    00422 QChar KAccelString::accelerator() const
    00423 {
    00424   if ((m_accel < 0) || (m_accel > (int)m_pureText.length()))
    00425     return QChar();
    00426
    00427   return m_pureText[m_accel].lower();
    00428 }
    00429
    00430
    00431 void KAccelString::calculateWeights(int initialWeight)
    00432 {
    00433   m_weight.resize(m_pureText.length());
    00434
    00435   uint pos = 0;
    00436   bool start_character = true;
    00437
    00438   while (pos<m_pureText.length())
    00439   {
    00440     QChar c = m_pureText[pos];
    00441
    00442     int weight = initialWeight+1;
    00443
    00444     // add special weight to first character
    00445     if (pos == 0)
    00446       weight += KAccelManagerAlgorithm::FIRST_CHARACTER_EXTRA_WEIGHT;
    00447
    00448     // add weight to word beginnings
    00449     if (start_character)
    00450     {
    00451       weight += KAccelManagerAlgorithm::WORD_BEGINNING_EXTRA_WEIGHT;
    00452       start_character = false;
    00453     }
    00454
    00455     // add decreasing weight to left characters
    00456     if (pos < 50)
    00457       weight += (50-pos);
    00458
    00459     // try to preserve the wanted accelarators
    00460     if ((int)pos == accel())
    00461       weight += KAccelManagerAlgorithm::WANTED_ACCEL_EXTRA_WEIGHT;
    00462
    00463     // skip non typeable characters
    00464     if (!c.isLetterOrNumber())
    00465     {
    00466       weight = 0;
    00467       start_character = true;
    00468     }
    00469
    00470     m_weight[pos] = weight;
    00471
    00472     ++pos;
    00473   }
    00474 }
    00475
    00476
    00477 int KAccelString::stripAccelerator(QString &text)
    00478 {
    00479   // Note: this code is derived from QAccel::shortcutKey
    00480   int p = 0;
    00481
    00482   while (p >= 0)
    00483   {
    00484     p = text.find('&', p)+1;
    00485
    00486     if (p <= 0 || p >= (int)text.length())
    00487       return -1;
    00488
    00489     if (text[p] != '&')
    00490     {
    00491       QChar c = text[p];
    00492       if (c.isPrint())
    00493       {
    00494         text.remove(p-1,1);
    00495         return p-1;
    00496       }
    00497     }
    00498
    00499     p++;
    00500   }
    00501
    00502   return -1;
    00503 }
    00504
    00505
    00506 int KAccelString::maxWeight(int &index, const QString &used)
    00507 {
    00508   int max = 0;
    00509   index = -1;
    00510
    00511   for (uint pos=0; pos<m_pureText.length(); ++pos)
    00512     if (!used.contains(m_pureText[pos].lower()))
    00513       if (m_weight[pos] > max)
    00514       {
    00515         max = m_weight[pos];
    00516         index = pos;
    00517       }
    00518
    00519   return max;
    00520 }
    00521
    00522
    00523 void KAccelString::dump()
    00524 {
    00525   QString s;
    00526   for (uint i=0; i<m_weight.count(); ++i)
    00527     s += QString("%1(%2)").arg(pure()[i]).arg(m_weight[i]);
    00528 }
    00529
    00530
    00531 /*********************************************************************
    00532
    00533  findAccelerators - the algorithm determining the new accelerators
    00534
    00535  The algorithm is very crude:
    00536
    00537    * each character in each widget text is assigned a weight
    00538    * the character with the highest weight over all is picked
    00539    * that widget is removed from the list
    00540    * the weights are recalculated
    00541    * the process is repeated until no more accelerators can be found
    00542
    00543  The algorithm has some advantages:
    00544
    00545    * it favours 'nice' accelerators (first characters in a word, etc.)
    00546    * it is quite fast, O(N²)
    00547    * it is easy to understand :-)
    00548
    00549  The disadvantages:
    00550
    00551    * it does not try to find as many accelerators as possible
    00552
    00553  TODO:
    00554
    00555  * The result is always correct, but not neccesarily optimal. Perhaps
    00556    it would be a good idea to add another algorithm with higher complexity
    00557    that gets used when this one fails, i.e. leaves widgets without
    00558    accelerators.
    00559
    00560  * The weights probably need some tweaking so they make more sense.
    00561
    00562  *********************************************************************/
    00563
    00564 void KAccelManagerAlgorithm::findAccelerators(KAccelStringList &result, QString &used)
    00565 {
    00566   KAccelStringList accel_strings = result;
    00567
    00568   // initally remove all accelerators
    00569   for (KAccelStringList::Iterator it = result.begin(); it != result.end(); ++it)
    00570     (*it).setAccel(-1);
    00571
    00572   // pick the highest bids
    00573   for (uint cnt=0; cnt<accel_strings.count(); ++cnt)
    00574   {
    00575     int max = 0, index = -1, accel = -1;
    00576
    00577     // find maximum weight
    00578     for (uint i=0; i<accel_strings.count(); ++i)
    00579     {
    00580       int a;
    00581       int m = accel_strings[i].maxWeight(a, used);
    00582       if (m>max)
    00583       {
    00584         max = m;
    00585         index = i;
    00586         accel = a;
    00587       }
    00588     }
    00589
    00590     // stop if no more accelerators can be found
    00591     if (index < 0)
    00592       return;
    00593
    00594     // insert the accelerator
    00595     if (accel >= 0)
    00596     {
    00597       result[index].setAccel(accel);
    00598       used.append(result[index].accelerator());
    00599     }
    00600
    00601     // make sure we don't visit this one again
    00602     accel_strings[index] = KAccelString();
    00603   }
    00604 }
    00605
    00606
    00607 /*********************************************************************
    00608
    00609  class KPopupAccelManager - managing QPopupMenu widgets dynamically
    00610
    00611  *********************************************************************/
    00612
    00613 KPopupAccelManager::KPopupAccelManager(QPopupMenu *popup)
    00614   : QObject(popup), m_popup(popup), m_count(-1)
    00615 {
    00616   connect(popup, SIGNAL(aboutToShow()), SLOT(aboutToShow()));
    00617 }
    00618
    00619
    00620 void KPopupAccelManager::aboutToShow()
    00621 {
    00622   // Note: we try to be smart and avoid recalculating the accelerators
    00623   // whenever possible. Unfortunately, there is no way to know if an
    00624   // item has been added or removed, so we can not do much more than
    00625   // to compare the items each time the menu is shown :-(
    00626
    00627   if (m_count != (int)m_popup->count())
    00628   {
    00629     findMenuEntries(m_entries);
    00630     calculateAccelerators();
    00631     m_count = m_popup->count();
    00632   }
    00633   else
    00634   {
    00635     KAccelStringList entries;
    00636     findMenuEntries(entries);
    00637     if (entries != m_entries)
    00638     {
    00639       m_entries = entries;
    00640       calculateAccelerators();
    00641     }
    00642   }
    00643 }
    00644
    00645
    00646 void KPopupAccelManager::calculateAccelerators()
    00647 {
    00648   // find the new accelerators
    00649   QString used;
    00650   KAccelManagerAlgorithm::findAccelerators(m_entries, used);
    00651
    00652   // change the menu entries
    00653   setMenuEntries(m_entries);
    00654 }
    00655
    00656
    00657 void KPopupAccelManager::findMenuEntries(KAccelStringList &list)
    00658 {
    00659   QMenuItem *mitem;
    00660   QString s;
    00661
    00662   list.clear();
    00663
    00664   // read out the menu entries
    00665   for (uint i=0; i<m_popup->count(); i++)
    00666   {
    00667     mitem = m_popup->findItem(m_popup->idAt(i));
    00668     if (mitem->isSeparator())
    00669       continue;
    00670
    00671     s = mitem->text();
    00672
    00673     // in full menues, look at entries with global accelerators last
    00674     int weight = 50;
    00675     if (s.contains('\t'))
    00676       weight = 0;
    00677
    00678     if (KAcceleratorManagerPrivate::standardName(s))
    00679         weight += 300;
    00680
    00681     list.append(KAccelString(s, weight));
    00682   }
    00683 }
    00684
    00685
    00686 void KPopupAccelManager::setMenuEntries(const KAccelStringList &list)
    00687 {
    00688   QMenuItem *mitem;
    00689
    00690   uint cnt = 0;
    00691   for (uint i=0; i<m_popup->count(); i++)
    00692   {
    00693     mitem = m_popup->findItem(m_popup->idAt(i));
    00694     if (mitem->isSeparator())
    00695       continue;
    00696
    00697     mitem->setText(list[cnt++].accelerated());
    00698   }
    00699 }
    00700
    00701
    00702 void KPopupAccelManager::manage(QPopupMenu *popup)
    00703 {
    00704   // don't add more than one manager to a popup
    00705   if (popup->child(0, "KPopupAccelManager", false) == 0 )
    00706     new KPopupAccelManager(popup);
    00707 }
    00708
    00709
    00710 #include "kaccelmanager_private.moc"

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 12:55:51 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001