/* This file is part of the KDE project Copyright (C) 2002 Matthias Hölzer-Klüpfel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kaccelmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kaccelmanager_private.h" #include "../kdeui/kstdaction_p.h" /********************************************************************* class Item - helper class containing widget information This class stores information about the widgets the need accelerators, as well as about their relationship. *********************************************************************/ /********************************************************************* class KAcceleratorManagerPrivate - internal helper class This class does all the work to find accelerators for a hierarchy of widgets. *********************************************************************/ class KAcceleratorManagerPrivate { public: static void manage(QWidget *widget); static bool programmers_mode; static bool standardName(const QString &str); static bool checkChange(const KAccelString &as) { QString t2 = as.accelerated(); QString t1 = as.originalText(); if (t1 != t2) { if (as.accel() == -1) { removed_string += "" + QStyleSheet::escape(t1) + ""; } else if (as.originalAccel() == -1) { added_string += "" + QStyleSheet::escape(t2) + ""; } else { changed_string += "" + QStyleSheet::escape(t1) + ""; changed_string += "" + QStyleSheet::escape(t2) + ""; } return true; } return false; } static QString changed_string; static QString added_string; static QString removed_string; static QMap ignored_widgets; private: class Item; public: typedef QPtrList ItemList; private: static void traverseChildren(QWidget *widget, Item *item); static void manageWidget(QWidget *widget, Item *item); static void manageMenuBar(QMenuBar *mbar, Item *item); static void manageTabBar(QTabBar *bar, Item *item); static void calculateAccelerators(Item *item, QString &used); class Item { public: Item() : m_widget(0), m_children(0), m_index(-1) {} ~Item(); void addChild(Item *item); QWidget *m_widget; KAccelString m_content; ItemList *m_children; int m_index; }; }; bool KAcceleratorManagerPrivate::programmers_mode = false; QString KAcceleratorManagerPrivate::changed_string; QString KAcceleratorManagerPrivate::added_string; QString KAcceleratorManagerPrivate::removed_string; static QStringList *kaccmp_sns = 0; static KStaticDeleter kaccmp_sns_d; QMap KAcceleratorManagerPrivate::ignored_widgets; bool KAcceleratorManagerPrivate::standardName(const QString &str) { if (!kaccmp_sns) kaccmp_sns_d.setObject(kaccmp_sns, new QStringList(KStdAction::internal_stdNames())); return kaccmp_sns->contains(str); } KAcceleratorManagerPrivate::Item::~Item() { delete m_children; } void KAcceleratorManagerPrivate::Item::addChild(Item *item) { if (!m_children) { m_children = new ItemList; m_children->setAutoDelete(true); } m_children->append(item); } void KAcceleratorManagerPrivate::manage(QWidget *widget) { if (!widget) { kdDebug(131) << "null pointer given to manage" << endl; return; } if (dynamic_cast(widget)) { // create a popup accel manager that can deal with dynamic menus KPopupAccelManager::manage(static_cast(widget)); return; } Item *root = new Item; manageWidget(widget, root); QString used; calculateAccelerators(root, used); delete root; } void KAcceleratorManagerPrivate::calculateAccelerators(Item *item, QString &used) { if (!item->m_children) return; // collect the contents KAccelStringList contents; for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next()) { contents << it->m_content; } // find the right accelerators KAccelManagerAlgorithm::findAccelerators(contents, used); // write them back into the widgets int cnt = -1; for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next()) { cnt++; QTabBar *tabBar = dynamic_cast(it->m_widget); if (tabBar) { if (checkChange(contents[cnt])) tabBar->tabAt(it->m_index)->setText(contents[cnt].accelerated()); continue; } QMenuBar *menuBar = dynamic_cast(it->m_widget); if (menuBar) { if (it->m_index >= 0) { QMenuItem *mitem = menuBar->findItem(menuBar->idAt(it->m_index)); if (mitem) { checkChange(contents[cnt]); mitem->setText(contents[cnt].accelerated()); } continue; } } // we possibly reserved an accel, but we won't set it as it looks silly if ( dynamic_cast( it->m_widget ) ) continue; // links look weird with ampersands if ( dynamic_cast( it->m_widget ) && it->m_widget->inherits("KURLLabel") ) continue; int tprop = it->m_widget->metaObject()->findProperty("text", true); if (tprop != -1) { if (checkChange(contents[cnt])) it->m_widget->setProperty("text", contents[cnt].accelerated()); } else { tprop = it->m_widget->metaObject()->findProperty("title", true); if (tprop != -1 && checkChange(contents[cnt])) it->m_widget->setProperty("title", contents[cnt].accelerated()); } } // calculate the accelerators for the children for (Item *it = item->m_children->first(); it != 0; it = item->m_children->next()) { if (it->m_widget && it->m_widget->isVisibleTo( item->m_widget ) ) calculateAccelerators(it, used); } } void KAcceleratorManagerPrivate::traverseChildren(QWidget *widget, Item *item) { QObjectList *childList = widget->queryList("QWidget", 0, false, false); for ( QObject *it = childList->first(); it; it = childList->next() ) { QWidget *w = static_cast(it); if ( !w->isVisibleTo( widget ) || ( w->isTopLevel() && dynamic_cast(w) == NULL ) ) continue; if ( KAcceleratorManagerPrivate::ignored_widgets.find( w ) != KAcceleratorManagerPrivate::ignored_widgets.end() ) continue; manageWidget(w, item); } delete childList; } void KAcceleratorManagerPrivate::manageWidget(QWidget *w, Item *item) { // first treat the special cases QTabBar *tabBar = dynamic_cast(w); if (tabBar) { manageTabBar(tabBar, item); return; } QWidgetStack *wds = dynamic_cast( w ); if ( wds ) { QWidgetStackAccelManager::manage( wds ); // return; } QPopupMenu *popupMenu = dynamic_cast(w); if (popupMenu) { // create a popup accel manager that can deal with dynamic menus KPopupAccelManager::manage(popupMenu); return; } QWidgetStack *wdst = dynamic_cast( w ); if ( wdst ) { QWidgetStackAccelManager::manage( wdst ); // return; } QMenuBar *menuBar = dynamic_cast(w); if (menuBar) { manageMenuBar(menuBar, item); return; } if (dynamic_cast(w) || dynamic_cast(w) || dynamic_cast(w) || dynamic_cast(w) || dynamic_cast(w) || w->qt_cast( "KMultiTabBar" )) return; // now treat 'ordinary' widgets QLabel *label = dynamic_cast(w); if ( label ) { if ( !label->buddy() ) label = 0; else { if ( label->textFormat() == Qt::RichText || ( label->textFormat() == Qt::AutoText && QStyleSheet::mightBeRichText( label->text() ) ) ) label = 0; } } if (w->isFocusEnabled() || label || dynamic_cast(w) || dynamic_cast( w )) { QString content; QVariant variant; int tprop = w->metaObject()->findProperty("text", true); if (tprop != -1) { const QMetaProperty* p = w->metaObject()->property( tprop, true ); if ( p && p->isValid() ) w->qt_property( tprop, 1, &variant ); else tprop = -1; } if (tprop == -1) { tprop = w->metaObject()->findProperty("title", true); if (tprop != -1) { const QMetaProperty* p = w->metaObject()->property( tprop, true ); if ( p && p->isValid() ) w->qt_property( tprop, 1, &variant ); } } if (variant.isValid()) content = variant.toString(); if (!content.isEmpty()) { Item *i = new Item; i->m_widget = w; // put some more weight on the usual action elements int weight = KAccelManagerAlgorithm::DEFAULT_WEIGHT; if (dynamic_cast(w) || dynamic_cast(w) || dynamic_cast(w) || dynamic_cast(w)) weight = KAccelManagerAlgorithm::ACTION_ELEMENT_WEIGHT; // don't put weight on group boxes, as usually the contents are more important if (dynamic_cast(w)) weight = KAccelManagerAlgorithm::GROUP_BOX_WEIGHT; // put a lot of extra weight on the KDialogBaseButton's if (w->inherits("KDialogBaseButton")) weight += KAccelManagerAlgorithm::DIALOG_BUTTON_EXTRA_WEIGHT; i->m_content = KAccelString(content, weight); item->addChild(i); } } traverseChildren(w, item); } void KAcceleratorManagerPrivate::manageTabBar(QTabBar *bar, Item *item) { for (int i=0; icount(); i++) { QString content = bar->tabAt(i)->text(); if (content.isEmpty()) continue; Item *it = new Item; item->addChild(it); it->m_widget = bar; it->m_index = i; it->m_content = KAccelString(content); } } void KAcceleratorManagerPrivate::manageMenuBar(QMenuBar *mbar, Item *item) { QMenuItem *mitem; QString s; for (uint i=0; icount(); ++i) { mitem = mbar->findItem(mbar->idAt(i)); if (!mitem) continue; // nothing to do for separators if (mitem->isSeparator()) continue; s = mitem->text(); if (!s.isEmpty()) { Item *it = new Item; item->addChild(it); it->m_content = KAccelString(s, // menu titles are important, so raise the weight KAccelManagerAlgorithm::MENU_TITLE_WEIGHT); it->m_widget = mbar; it->m_index = i; } // have a look at the popup as well, if present if (mitem->popup()) KPopupAccelManager::manage(mitem->popup()); } } /********************************************************************* class KAcceleratorManager - main entry point This class is just here to provide a clean public API... *********************************************************************/ void KAcceleratorManager::manage(QWidget *widget) { KAcceleratorManager::manage(widget, false); } void KAcceleratorManager::manage(QWidget *widget, bool programmers_mode) { kdDebug(131) << "KAcceleratorManager::manage\n"; KAcceleratorManagerPrivate::changed_string = QString::null; KAcceleratorManagerPrivate::added_string = QString::null; KAcceleratorManagerPrivate::removed_string = QString::null; KAcceleratorManagerPrivate::programmers_mode = programmers_mode; KAcceleratorManagerPrivate::manage(widget); } void KAcceleratorManager::last_manage(QString &added, QString &changed, QString &removed) { added = KAcceleratorManagerPrivate::added_string; changed = KAcceleratorManagerPrivate::changed_string; removed = KAcceleratorManagerPrivate::removed_string; } /********************************************************************* class KAccelString - a string with weighted characters *********************************************************************/ KAccelString::KAccelString(const QString &input, int initialWeight) : m_pureText(input), m_weight() { m_orig_accel = m_pureText.find("(!)&"); if (m_orig_accel != -1) m_pureText.remove(m_orig_accel, 4); m_orig_accel = m_pureText.find("(&&)"); if (m_orig_accel != -1) m_pureText.replace(m_orig_accel, 4, "&"); m_origText = m_pureText; if (m_pureText.contains('\t')) m_pureText = m_pureText.left(m_pureText.find('\t')); m_orig_accel = m_accel = stripAccelerator(m_pureText); if (initialWeight == -1) initialWeight = KAccelManagerAlgorithm::DEFAULT_WEIGHT; calculateWeights(initialWeight); // dump(); } QString KAccelString::accelerated() const { QString result = m_origText; if (result.isEmpty()) return result; if (KAcceleratorManagerPrivate::programmers_mode) { if (m_accel != m_orig_accel) { int oa = m_orig_accel; if (m_accel >= 0) { result.insert(m_accel, "(!)&"); if (m_accel < m_orig_accel) oa += 4; } if (m_orig_accel >= 0) result.replace(oa, 1, "(&&)"); } } else { if (m_accel >= 0 && m_orig_accel != m_accel) { result.remove(m_orig_accel, 1); result.insert(m_accel, "&"); } } return result; } QChar KAccelString::accelerator() const { if ((m_accel < 0) || (m_accel > (int)m_pureText.length())) return QChar(); return m_pureText[m_accel].lower(); } void KAccelString::calculateWeights(int initialWeight) { m_weight.resize(m_pureText.length()); uint pos = 0; bool start_character = true; while (pos= 0) { p = text.find('&', p)+1; if (p <= 0 || p >= (int)text.length()) return -1; if (text[p] != '&') { QChar c = text[p]; if (c.isPrint()) { text.remove(p-1,1); return p-1; } } p++; } return -1; } int KAccelString::maxWeight(int &index, const QString &used) { int max = 0; index = -1; for (uint pos=0; pos max) { max = m_weight[pos]; index = pos; } return max; } void KAccelString::dump() { QString s; for (uint i=0; imax) { max = m; index = i; accel = a; } } // stop if no more accelerators can be found if (index < 0) return; // insert the accelerator if (accel >= 0) { result[index].setAccel(accel); used.append(result[index].accelerator()); } // make sure we don't visit this one again accel_strings[index] = KAccelString(); } } /********************************************************************* class KPopupAccelManager - managing QPopupMenu widgets dynamically *********************************************************************/ KPopupAccelManager::KPopupAccelManager(QPopupMenu *popup) : QObject(popup), m_popup(popup), m_count(-1) { aboutToShow(); // do one check and then connect to show connect(popup, SIGNAL(aboutToShow()), SLOT(aboutToShow())); } void KPopupAccelManager::aboutToShow() { // Note: we try to be smart and avoid recalculating the accelerators // whenever possible. Unfortunately, there is no way to know if an // item has been added or removed, so we can not do much more than // to compare the items each time the menu is shown :-( if (m_count != (int)m_popup->count()) { findMenuEntries(m_entries); calculateAccelerators(); m_count = m_popup->count(); } else { KAccelStringList entries; findMenuEntries(entries); if (entries != m_entries) { m_entries = entries; calculateAccelerators(); } } } void KPopupAccelManager::calculateAccelerators() { // find the new accelerators QString used; KAccelManagerAlgorithm::findAccelerators(m_entries, used); // change the menu entries setMenuEntries(m_entries); } void KPopupAccelManager::findMenuEntries(KAccelStringList &list) { QMenuItem *mitem; QString s; list.clear(); // read out the menu entries for (uint i=0; icount(); i++) { mitem = m_popup->findItem(m_popup->idAt(i)); if (mitem->isSeparator()) continue; s = mitem->text(); // in full menus, look at entries with global accelerators last int weight = 50; if (s.contains('\t')) weight = 0; list.append(KAccelString(s, weight)); // have a look at the popup as well, if present if (mitem->popup()) KPopupAccelManager::manage(mitem->popup()); } } void KPopupAccelManager::setMenuEntries(const KAccelStringList &list) { QMenuItem *mitem; uint cnt = 0; for (uint i=0; icount(); i++) { mitem = m_popup->findItem(m_popup->idAt(i)); if (mitem->isSeparator()) continue; if (KAcceleratorManagerPrivate::checkChange(list[cnt])) mitem->setText(list[cnt].accelerated()); cnt++; } } void KPopupAccelManager::manage(QPopupMenu *popup) { // don't add more than one manager to a popup if (popup->child(0, "KPopupAccelManager", false) == 0 ) new KPopupAccelManager(popup); } void QWidgetStackAccelManager::manage( QWidgetStack *stack ) { if ( stack->child( 0, "QWidgetStackAccelManager", false ) == 0 ) new QWidgetStackAccelManager( stack ); } QWidgetStackAccelManager::QWidgetStackAccelManager(QWidgetStack *stack) : QObject(stack), m_stack(stack) { aboutToShow(stack->visibleWidget()); // do one check and then connect to show connect(stack, SIGNAL(aboutToShow(QWidget *)), SLOT(aboutToShow(QWidget *))); } bool QWidgetStackAccelManager::eventFilter ( QObject * watched, QEvent * e ) { if ( e->type() == QEvent::Show && qApp->activeWindow() ) { KAcceleratorManager::manage( qApp->activeWindow() ); watched->removeEventFilter( this ); } return false; } void QWidgetStackAccelManager::aboutToShow(QWidget *child) { if (!child) { kdDebug(131) << "null pointer given to aboutToShow" << endl; return; } child->installEventFilter( this ); } void KAcceleratorManager::setNoAccel( QWidget *widget ) { KAcceleratorManagerPrivate::ignored_widgets[widget] = 1; } #include "kaccelmanager_private.moc"