kdecore Library API Documentation

kcheckaccelerators.cpp

00001 /* This file is part of the KDE libraries
00002     Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org)
00003     Copyright (C) 1998, 1999, 2000 KDE Team
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License as published by the Free Software Foundation; either
00008     version 2 of the License, or (at your option) any later version.
00009 
00010     This library is distributed in the hope that it will be useful,
00011     but WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     Library General Public License for more details.
00014 
00015     You should have received a copy of the GNU Library General Public License
00016     along with this library; see the file COPYING.LIB.  If not, write to
00017     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018     Boston, MA 02111-1307, USA.
00019         */
00020 
00021 // $Id: kcheckaccelerators.cpp,v 1.5 2002/10/19 14:55:33 coolo Exp $
00022 
00023 #define INCLUDE_MENUITEM_DEF
00024 #include <qmenudata.h>
00025 
00026 #include "config.h"
00027 
00028 #include "kcheckaccelerators.h"
00029 
00030 #include <qpopupmenu.h>
00031 #include <qapplication.h>
00032 #include <qdialog.h>
00033 #include <qlayout.h>
00034 #include <qtextview.h>
00035 #include <qobjectlist.h>
00036 #include <qmenubar.h>
00037 #include <qtabbar.h>
00038 #include <qpushbutton.h>
00039 #include <qmetaobject.h>
00040 #include <qcheckbox.h>
00041 
00042 #include <kconfig.h>
00043 #include <kglobal.h>
00044 #include <kshortcut.h>
00045 #include <klocale.h>
00046 
00047 /*
00048 
00049  HOWTO:
00050 
00051  This class allows translators (and application developers) to check for accelerator
00052  conflicts in menu and widgets. Put the following in your kdeglobals (or the config
00053  file for the application you're testing):
00054 
00055  [Development]
00056  CheckAccelerators=F12
00057  AutoCheckAccelerators=false
00058  AlwaysShowCheckAccelerators=false
00059 
00060  The checking can be either manual or automatic. To perform manual check, press
00061  the keyboard shortcut set to 'CheckAccelerators' (here F12). If automatic checking
00062  is enabled by setting 'AutoCheckAccelerators' to true, check will be performed every
00063  time the GUI changes. It's possible that in certain cases the check will be
00064  done also when no visible changes in the GUI happen or the check won't be done
00065  even if the GUI changed (in the latter case, use manual check ). Automatic
00066  checks can be anytime disabled by the checkbox in the dialog presenting
00067  the results of the check. If you set 'AlwaysShowCheckAccelerators' to true,
00068  the dialog will be shown even if the automatic check didn't find any conflicts,
00069  and all submenus will be shown, even those without conflicts.
00070 
00071  The dialog first lists the name of the window, then all results for all menus
00072  (if the window has a menubar) and then result for all controls in the active
00073  window (if there are any checkboxes etc.). For every submenu and all controls
00074  there are shown all conflicts grouped by accelerator, and a list of all used
00075  accelerators.
00076 */
00077 
00078 KCheckAccelerators::KCheckAccelerators( QObject* parent )
00079     : QObject( parent, "kapp_accel_filter" ), block( false )
00080 {
00081     parent->installEventFilter( this );
00082     KConfigGroupSaver saver( KGlobal::config(), "Development" );
00083     QString sKey = KGlobal::config()->readEntry( "CheckAccelerators" ).stripWhiteSpace();
00084     if( !sKey.isEmpty() ) {
00085       KShortcut cuts( sKey );
00086       if( cuts.count() > 0 )
00087         key = cuts.seq(0).qt();
00088     }
00089     alwaysShow = KGlobal::config()->readBoolEntry( "AlwaysShowCheckAccelerators", false );
00090     autoCheck = KGlobal::config()->readBoolEntry( "AutoCheckAccelerators", true );
00091     connect( &autoCheckTimer, SIGNAL( timeout()), SLOT( autoCheckSlot()));
00092 }
00093 
00094 bool KCheckAccelerators::eventFilter( QObject * , QEvent * e) {
00095     if ( block )
00096         return false;
00097     if ( e->type() == QEvent::Accel ) {
00098         if ( ( static_cast<QKeyEvent *>(e) )->key() == key ) {
00099             block = true;
00100         checkAccelerators( false );
00101         block = false;
00102         ( static_cast<QKeyEvent *>(e) )->accept();
00103         return true;
00104     }
00105     }
00106     if( autoCheck
00107         && ( e->type() == QEvent::ChildInserted || e->type() == QEvent::ChildRemoved )) {
00108         autoCheckTimer.start( 100, true ); // 100 ms
00109     }
00110     return false;
00111 }
00112 
00113 void KCheckAccelerators::autoCheckSlot()
00114     {
00115     if( block || QWidget::mouseGrabber() || QWidget::keyboardGrabber() || QApplication::activePopupWidget()) {
00116         autoCheckTimer.start( 100, true );
00117         return;
00118     }
00119     block = true;
00120     checkAccelerators( true );
00121     block = false;
00122     }
00123 
00124 void KCheckAccelerators::findAccel( const QString& item, const QString &txt, AccelMap &accels ) {
00125     QChar c;
00126     int search_pos = 0;
00127     for(;;) {
00128         int i = txt.find( "&", search_pos );
00129         if ( i == -1 )
00130             return;
00131         c = txt[ i + 1 ];
00132         if ( !c.isNull() && c != '&')
00133             break;
00134         search_pos = i + 2; // search also after '&&'
00135     }
00136     c = c.lower();
00137     AccelMap::Iterator it = accels.find( c );
00138     AccelInfo info;
00139     info.item  = item;
00140     info.string = txt;
00141     if ( it == accels.end() ) {
00142         AccelInfoList list;
00143         list.append( info );
00144         accels.insert( c, list );
00145     } else {
00146         AccelInfoList &list = it.data();
00147         list.append( info );
00148     }
00149 }
00150 
00151 void KCheckAccelerators::checkMenuData( const QString& prefix, QMenuData* m ) {
00152     AccelMap accels;
00153     QMenuItem* mi;
00154     int i;
00155     QString s;
00156     for ( i = 0; i < (int) m->count(); i++ ) {
00157         mi = m->findItem( m->idAt( i ) );
00158         s = mi->text();
00159         if ( s.contains( '\t' ) )
00160             s = s.left( s.find( '\t' ) );
00161     findAccel( prefix.isEmpty() ? s : prefix + "/" + s, s, accels );
00162     }
00163 
00164     menuAccels[ prefix ] = accels;
00165 
00166     for ( i = 0; i < (int) m->count(); i++ ) {
00167         mi = m->findItem( m->idAt( i ) );
00168         if ( mi->popup() ) {
00169             s = mi->text();
00170         if ( s.contains( '\t' ) )
00171             s = s.left( s.find( '\t' ) );
00172         checkMenuData( prefix.isEmpty() ? s : prefix + "/" + s, mi->popup());
00173     }
00174     }
00175 }
00176 
00177 void KCheckAccelerators::checkMenuData( QMenuData* m ) {
00178     checkMenuData( "", m );
00179 }
00180 
00181 void KCheckAccelerators::checkAccelerators( bool automatic ) {
00182     QWidget* actWin = qApp->activeWindow();
00183     if ( !actWin )
00184         return;
00185     QMap<QChar, AccelInfoList > accels;
00186     QObjectList *l = actWin->queryList( "QWidget" );
00187     if ( !l )
00188         return;
00189     QMenuBar* mbar = 0;
00190     QObject *p;
00191     for ( QObject *o = l->first(); o; o = l->next() ) {
00192         if ( ( static_cast<QWidget *>(o) )->isVisibleTo( actWin ) ) {
00193             QWidget *w = static_cast<QWidget *>(o);
00194         if ( w->inherits( "QMenuBar" ) ) {
00195             mbar = static_cast<QMenuBar *>(w);
00196                 for( unsigned int i = 0;
00197                      i < mbar->count();
00198                      ++i )
00199                     findAccel( w->className(), mbar->text( mbar->idAt( i )), accels );
00200             }
00201         if (w->inherits("QLineEdit") || w->inherits("QComboBox") || w->inherits("QTextEdit") || w->inherits("QTextView") )
00202         continue;
00203 
00204         // Skip widgets that are children of non-visible objects
00205         p = w->parent();
00206         while ( p && p->inherits("QWidget") && static_cast<QWidget *>(p)->isVisible() )
00207         p = p->parent();
00208         if ( p )
00209         continue;
00210 
00211         QMetaObject *mo = w->metaObject();
00212         const QMetaProperty* text = mo->property( mo->findProperty( "text", TRUE ), TRUE );
00213         const QMetaProperty* title = mo->property( mo->findProperty( "title", TRUE ), TRUE );
00214         if ( text )
00215             findAccel( w->className(), w->property( "text" ).toString(), accels );
00216         if ( title )
00217             findAccel( w->className(), w->property( "title" ).toString(), accels );
00218 
00219             if ( w->inherits( "QTabBar" ) ) {
00220                 QTabBar *tbar = static_cast<QTabBar *>(w);
00221         for ( int i = 0; i < tbar->count(); i++ )
00222             findAccel( tbar->className(), tbar->tabAt( i )->text(), accels );
00223         }
00224     }
00225     }
00226     delete l;
00227 
00228     QString s;
00229 
00230     bool was_clash = false;
00231     int num_clashes = 0;
00232     QString used;
00233     for ( QMap<QChar,AccelInfoList>::Iterator it = accels.begin(); it != accels.end(); ++it  ) {
00234         AccelInfoList list = it.data();
00235         if( used.isEmpty())
00236             used = it.key();
00237         else {
00238             used += ", ";
00239             used += it.key();
00240         }
00241 
00242         if ( list.count() <= 1 )
00243             continue;
00244 
00245     if ( ++num_clashes == 1 ) {
00246         s += "<table border>";
00247         s += "<tr><th>" + i18n( "Accel" ) + "</th><th>" + i18n( "String" ) + "</th><th>" + i18n( "Widget" ) + "</th></tr>";
00248     }
00249     AccelInfoList::Iterator ait = list.begin();
00250     s += "<tr><td rowspan=" + QString::number( list.count() ) + "><large><b>" + it.key() + "</b></large></td>";
00251     s += "<td>";
00252     s += (*ait).string;
00253     s += "</td><td>";
00254     s += (*ait).item;
00255     s += "</td></tr>";
00256 
00257     for ( ait++; ait != list.end(); ++ait ) {
00258         s += "<tr><td>";
00259         s += (*ait).string;
00260         s += "</td><td>";
00261         s += (*ait).item;
00262         s += "</td></tr>";
00263     }
00264     }
00265     if ( num_clashes  ) {
00266         s += "</table>";
00267         s.prepend( "<h3>" + i18n( "One clash detected", "%n clashes detected", num_clashes ) + "</h3>" );
00268     } else {
00269         s += "<h3>" + i18n( "No clashes detected" ) + "</h3>";
00270     }
00271 
00272     s += "<h3>" + i18n( "Used accelerators:" ) + "</h3> " + ( used.isEmpty() ? i18n( "None" ) : used );
00273     was_clash |= ( num_clashes > 0 );
00274 
00275     if ( mbar ) {
00276     checkMenuData( mbar );
00277         QString s2;
00278         for( QMap<QString,AccelMap>::Iterator mit = menuAccels.begin(); mit != menuAccels.end(); ++mit ) {
00279             if( mit.key().isEmpty()) // don't list the menubar itself, it's already handled
00280                 continue;           //  together with other controls, do only submenus here
00281             num_clashes = 0;
00282             used = "";
00283             QString m;
00284         for ( AccelMap::Iterator it = (*mit).begin(); it != (*mit).end(); ++it  ) {
00285             AccelInfoList list = it.data();
00286                 if( used.isEmpty())
00287                     used = it.key();
00288                 else {
00289                     used += ", ";
00290                     used += it.key();
00291                 }
00292             if ( list.count() <= 1 )
00293                 continue;
00294 
00295                 if ( ++num_clashes == 1 ) {
00296                 m += "<table border>";
00297                 m += "<tr><th>" + i18n( "Accel" ) + "</th><th>" + i18n( "Menu Item" ) + "</th></tr>";
00298             }
00299             AccelInfoList::Iterator ait = list.begin();
00300                 m += "<tr><td rowspan=" + QString::number( list.count() ) + "><large><b>" + it.key() + "</b></large></td>";
00301                 m += "<td>";
00302             m += (*ait).item;
00303             m += "</td></tr>";
00304 
00305             for ( ait++; ait != list.end(); ++ait ) {
00306             m += "<tr><td>";
00307                 m += (*ait).item;
00308             m += "</td></tr>";
00309             }
00310         }
00311             if ( num_clashes  ) {
00312                 m += "</table>";
00313                 m.prepend( "<h3>" + i18n( "One clash detected", "%n clashes detected", num_clashes ) + "</h3>" );
00314             }
00315 
00316             if( num_clashes || alwaysShow ) {
00317                 m.prepend( "<h2>" + i18n( "Submenu" ) + " " + mit.key() + "</h2>" );
00318                 m += "<h3>" + i18n( "Used accelerators:" ) + "</h3> " + ( used.isEmpty() ? i18n( "None" ) : used );
00319             }
00320             s2 += m;
00321             was_clash |= ( num_clashes > 0 );
00322         }
00323 
00324         if( s2.isEmpty())
00325             s2 = "<h3>" + i18n( "No clashes detected" ) + "</h3>";
00326     s2.prepend( "<h2>" + i18n( "Menu" ) + "</h2>" );
00327     s2 += "<h2>" + i18n( "Other control elements" ) + "</h2>";
00328     s.prepend( s2 );
00329     }
00330 
00331     if( automatic && !alwaysShow && !was_clash )
00332         return;
00333 
00334     s.prepend( QString("<h2><em>") + actWin->caption() + "<em></h2>" );
00335 
00336     QDialog dlg( actWin, "kapp_accel_check_dlg", true );
00337     dlg.setCaption( i18n( "Dr. Klash' Accelerator Diagnosis" ));
00338     dlg.resize( 500, 460 );
00339     QVBoxLayout* layout = new QVBoxLayout( &dlg, 11, 6 );
00340     layout->setAutoAdd( TRUE );
00341     QTextView* view = new QTextView( &dlg );
00342     QCheckBox* disableAutoCheck = NULL;
00343     if( automatic )
00344         disableAutoCheck = new QCheckBox( i18n( "&Disable automatic checking" ), &dlg );
00345     QPushButton* btnClose = new QPushButton( i18n( "&Close" ), &dlg );
00346     btnClose->setDefault( true );
00347     connect( btnClose, SIGNAL( clicked() ), &dlg, SLOT( close() ) );
00348     view->setText( s );
00349     view->setFocus();
00350     dlg.exec();
00351     if( disableAutoCheck != NULL && disableAutoCheck->isChecked())
00352         autoCheck = false;
00353     // dlg will be destroyed before returning
00354 }
00355 
00356 #include "kcheckaccelerators.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.0.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Oct 8 12:20:39 2003 by doxygen 1.2.18 written by Dimitri van Heesch, © 1997-2001