libyui  3.0.10
/usr/src/RPM/BUILD/libyui-3.0.10/src/YShortcutManager.cc
00001 /*
00002   Copyright (C) 2000-2012 Novell, Inc
00003   This library is free software; you can redistribute it and/or modify
00004   it under the terms of the GNU Lesser General Public License as
00005   published by the Free Software Foundation; either version 2.1 of the
00006   License, or (at your option) version 3.0 of the License. This library
00007   is distributed in the hope that it will be useful, but WITHOUT ANY
00008   WARRANTY; without even the implied warranty of MERCHANTABILITY or 
00009   FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
00010   License for more details. You should have received a copy of the GNU
00011   Lesser General Public License along with this library; if not, write
00012   to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
00013   Floor, Boston, MA 02110-1301 USA
00014 */
00015 
00016 
00017 /*-/
00018 
00019   File:         YShortcutManager.cc
00020 
00021   Author:       Stefan Hundhammer <sh@suse.de>
00022 
00023 /-*/
00024 
00025 
00026 #define YUILogComponent "ui-shortcuts"
00027 #include "YUILog.h"
00028 
00029 #include "YShortcutManager.h"
00030 #include "YDialog.h"
00031 #include "YDumbTab.h"
00032 
00033 
00034 // Threshold of widgets with valid shortcut characters below which no shortcut
00035 // check is performed at all. This might regularly occur for languages that
00036 // primarily use non-ASCII characters (Russian, Greek, Chinese, Japanese,
00037 // Korean).
00038 #define MIN_VALID_PERCENT       50
00039 
00040 // Return the number of elements of an array of any type
00041 #define DIM( ARRAY )    ( (int) ( sizeof( ARRAY)/( sizeof( ARRAY[0] ) ) ) )
00042 
00043 
00044 YShortcutManager::YShortcutManager( YDialog *dialog )
00045     : _dialog( dialog )
00046     , _conflictCount( 0 )
00047     , _didCheck( false )
00048 {
00049     YUI_CHECK_PTR( _dialog );
00050 }
00051 
00052 
00053 YShortcutManager::~YShortcutManager()
00054 {
00055     clearShortcutList();
00056 }
00057 
00058 
00059 void
00060 YShortcutManager::checkShortcuts( bool autoResolve )
00061 {
00062     yuiDebug() << "Checking keyboard shortcuts" << std::endl;
00063 
00064     clearShortcutList();
00065     findShortcutWidgets( _dialog->childrenBegin(), _dialog->childrenEnd() );
00066 
00067     int validCount = 0;
00068 
00069     for ( unsigned i=0; i < _shortcutList.size(); i++ )
00070     {
00071         if ( _shortcutList[i]->hasValidShortcutChar() )
00072             ++validCount;
00073     }
00074 
00075     int validPercent = _shortcutList.size() > 0 ?
00076         ( 100 * validCount ) / _shortcutList.size() : 0;
00077 
00078     if ( validPercent < MIN_VALID_PERCENT )
00079     {
00080         // No check at all if there are not enough widgets with valid shortcut
00081         // characters ([A-Za-z0-9]). This might regularly occur for languages
00082         // that primarily use non-ASCII characters (Russian, Greek, Chinese,
00083         // Japanese, Korean).
00084 
00085         yuiWarning() << "Not enough widgets with valid shortcut characters - no check" << std::endl;
00086         yuiDebug() << "Found " << validCount << " widgets with valid shortcut characters" << std::endl;
00087         return;
00088     }
00089 
00090 
00091     // Initialize wanted character counters
00092     for ( int i=0; i < DIM( _wanted ); i++ )
00093         _wanted[i] = 0;
00094 
00095     // Initialize used character flags
00096     for ( int i=0; i < DIM( _wanted ); i++ )
00097         _used[i] = false;
00098 
00099     // Count wanted shortcuts
00100     for ( unsigned i=0; i < _shortcutList.size(); i++ )
00101         _wanted[ (int) _shortcutList[i]->preferred() ]++;
00102 
00103 
00104     // Report errors
00105 
00106     _conflictCount = 0;
00107 
00108     for ( unsigned i=0; i < _shortcutList.size(); i++ )
00109     {
00110         YShortcut *shortcut = _shortcutList[i];
00111 
00112         if ( YShortcut::isValid( shortcut->preferred() ) )
00113         {
00114             if ( _wanted[ (int) shortcut->preferred() ] > 1 )   // shortcut char used more than once
00115             {
00116                 shortcut->setConflict();
00117                 _conflictCount++;
00118 
00119                 yuiDebug() << "Shortcut conflict: '" << shortcut->preferred()
00120                            << "' used for " << shortcut->widget()
00121                            << std::endl;
00122             }
00123         }
00124         else    // No or invalid shortcut
00125         {
00126             if ( shortcut->cleanShortcutString().length() > 0 )
00127             {
00128                 shortcut->setConflict();
00129                 _conflictCount++;
00130 
00131                 if ( ! shortcut->widget()->autoShortcut() )
00132                 {
00133                     yuiDebug() << "No valid shortcut for " << shortcut->widget() << std::endl;
00134                 }
00135             }
00136         }
00137 
00138         if ( ! shortcut->conflict() )
00139         {
00140             _used[ (int) shortcut->preferred() ] = true;
00141         }
00142     }
00143 
00144     _didCheck = true;
00145 
00146     if ( _conflictCount > 0 )
00147     {
00148         if ( autoResolve )
00149         {
00150             resolveAllConflicts();
00151         }
00152     }
00153     else
00154     {
00155         yuiDebug() << "No shortcut conflicts" << std::endl;
00156     }
00157 }
00158 
00159 
00160 void
00161 YShortcutManager::resolveAllConflicts()
00162 {
00163     yuiDebug() << "Resolving shortcut conflicts" << std::endl;
00164 
00165     if ( ! _didCheck )
00166     {
00167         yuiError() << "Call checkShortcuts() first!" << std::endl;
00168         return;
00169     }
00170 
00171 
00172     // Make a list of all shortcuts with conflicts
00173 
00174     YShortcutList conflictList;
00175     _conflictCount = 0;
00176 
00177     for ( YShortcutListIterator it = _shortcutList.begin();
00178           it != _shortcutList.end();
00179           ++it )
00180     {
00181         if ( ( *it )->conflict() )
00182         {
00183             conflictList.push_back( *it );
00184             _conflictCount++;
00185         }
00186     }
00187 
00188 
00189     // Resolve each conflict
00190 
00191     while ( ! conflictList.empty() )
00192     {
00193         //
00194         // Pick a conflict widget to resolve.
00195         //
00196 
00197         // Wizard buttons have priority - check any of them first.
00198         int prioIndex = findShortestWizardButton( conflictList );
00199 
00200         if ( prioIndex < 0 )
00201             prioIndex = findShortestWidget( conflictList); // Find the shortest widget. Buttons have priority.
00202 
00203 
00204         // Pick a new shortcut for this widget.
00205 
00206         YShortcut * shortcut = conflictList[ prioIndex ];
00207         resolveConflict( shortcut );
00208 
00209         if ( shortcut->conflict() )
00210         {
00211             yuiWarning() << "Couldn't resolve shortcut conflict for " << shortcut->widget() << std::endl;
00212         }
00213 
00214 
00215         // Mark this particular conflict as resolved.
00216 
00217         conflictList.erase( conflictList.begin() + prioIndex );
00218     }
00219 
00220     if ( _conflictCount > 0 )
00221     {
00222         yuiDebug() << _conflictCount <<  " shortcut conflict(s) left" << std::endl;
00223     }
00224 }
00225 
00226 
00227 
00228 void
00229 YShortcutManager::resolveConflict( YShortcut * shortcut )
00230 {
00231     // yuiDebug() << "Picking shortcut for " << shortcut->widget() << std::endl;
00232 
00233     char candidate = shortcut->preferred();                     // This is always normalized, no need to normalize again.
00234 
00235     if ( ! YShortcut::isValid( candidate )                      // Can't use this character - pick another one.
00236          || _used[ (int) candidate ] )
00237     {
00238         candidate = 0;                                          // Restart from scratch - forget the preferred character.
00239         std::string str = shortcut->cleanShortcutString();
00240 
00241         for ( std::string::size_type pos = 0; pos < str.length(); pos++ )       // Search all the shortcut string.
00242         {
00243             char c = YShortcut::normalized( str[ pos ] );
00244             // yuiDebug() << "Checking '" << c << "'" << std::endl;
00245 
00246             if ( YShortcut::isValid(c) && ! _used[ (int) c ] )  // Could we use this character?
00247             {
00248                 if ( _wanted[ (int) c ] < _wanted[ (int) candidate ]    // Is this a better choice than what we already have -
00249                      || ! YShortcut::isValid( candidate ) )             // or don't we have anything yet?
00250                 {
00251                     candidate = c;                      // Use this one.
00252                     // yuiDebug() << "Picking '" << c << "'" << std::endl;
00253 
00254                     if ( _wanted[ (int) c ] == 0 )      // It doesn't get any better than this:
00255                         break;                          // Nobody wants this shortcut anyway.
00256                 }
00257             }
00258         }
00259     }
00260 
00261     if ( YShortcut::isValid( candidate ) )
00262     {
00263         if ( candidate != shortcut->preferred() )
00264         {
00265             if ( shortcut->widget()->autoShortcut() )
00266             {
00267                 yuiDebug() << "Automatically assigning shortcut '" << candidate
00268                            << "' to " << shortcut->widgetClass() << "(`opt(`autoShortcut ), \""
00269                            << shortcut->cleanShortcutString() << "\" )"
00270                            << std::endl;
00271             }
00272             else
00273             {
00274                 yuiDebug() << "Reassigning shortcut '" << candidate
00275                            << "' to " << shortcut->widget()
00276                            << std::endl;
00277             }
00278             shortcut->setShortcut( candidate );
00279         }
00280         else
00281         {
00282             yuiDebug() << "Keeping preferred shortcut '" << candidate
00283                        << "' for " << shortcut->widget()
00284                        << std::endl;
00285         }
00286 
00287         _used[ (int) candidate ] = true;
00288         shortcut->setConflict( false );
00289     }
00290     else        // No unique shortcut found
00291     {
00292         yuiWarning() << "Couldn't resolve shortcut conflict for "
00293                      << shortcut->widget()
00294                      << " - assigning no shortcut"
00295                      << std::endl;
00296 
00297         shortcut->clearShortcut();
00298         shortcut->setConflict( false );
00299     }
00300 
00301     _conflictCount--;
00302 }
00303 
00304 
00305 
00306 int
00307 YShortcutManager::findShortestWizardButton( const YShortcutList & conflictList )
00308 {
00309     int shortestIndex = -1;
00310     int shortestLen   = -1;
00311 
00312     for ( unsigned i=1; i < conflictList.size(); i++ )
00313     {
00314         if ( conflictList[i]->isWizardButton() )
00315         {
00316             if ( shortestLen < 0 ||
00317                  conflictList[i]->distinctShortcutChars() < shortestLen )
00318             {
00319                 shortestIndex = i;
00320                 shortestLen   = conflictList[i]->distinctShortcutChars();
00321             }
00322 
00323         }
00324     }
00325 
00326     return shortestIndex;
00327 }
00328 
00329 
00330 
00331 unsigned
00332 YShortcutManager::findShortestWidget( const YShortcutList & conflictList )
00333 {
00334     unsigned shortestIndex = 0;
00335     int      shortestLen   = conflictList[ shortestIndex ]->distinctShortcutChars();
00336 
00337     for ( unsigned i=1; i < conflictList.size(); i++ )
00338     {
00339         int currentLen = conflictList[i]->distinctShortcutChars();
00340 
00341         if ( currentLen < shortestLen )
00342         {
00343             // Found an even shorter one
00344 
00345             shortestIndex = i;
00346             shortestLen   = currentLen;
00347         }
00348         else if ( currentLen == shortestLen )
00349         {
00350             if ( conflictList[i]->isButton() &&
00351                  ! conflictList[ shortestIndex ]->isButton() )
00352             {
00353                 // Prefer a button over another widget with the same length
00354 
00355                 shortestIndex = i;
00356                 shortestLen   = currentLen;
00357             }
00358         }
00359     }
00360 
00361     return shortestIndex;
00362 }
00363 
00364 
00365 
00366 void
00367 YShortcutManager::clearShortcutList()
00368 {
00369     for ( unsigned i=0; i < _shortcutList.size(); i++ )
00370     {
00371         delete _shortcutList[i];
00372     }
00373 
00374     _shortcutList.clear();
00375 }
00376 
00377 
00378 void
00379 YShortcutManager::findShortcutWidgets( YWidgetListConstIterator begin,
00380                                        YWidgetListConstIterator end )
00381 {
00382     for ( YWidgetListConstIterator it = begin; it != end; ++it )
00383     {
00384         YWidget * widget = *it;
00385 
00386         YDumbTab * dumbTab = dynamic_cast<YDumbTab *> (widget);
00387 
00388         if ( dumbTab )
00389         {
00390             for ( YItemConstIterator it = dumbTab->itemsBegin();
00391                   it != dumbTab->itemsEnd();
00392                   ++it )
00393             {
00394                 YItemShortcut * shortcut = new YItemShortcut( dumbTab, *it );
00395                 _shortcutList.push_back( shortcut );
00396             }
00397         }
00398         else if ( ! widget->shortcutString().empty() )
00399         {
00400             YShortcut * shortcut = new YShortcut( *it );
00401             _shortcutList.push_back( shortcut );
00402         }
00403 
00404         if ( widget->hasChildren() )
00405         {
00406             findShortcutWidgets( widget->childrenBegin(),
00407                                  widget->childrenEnd()   );
00408         }
00409     }
00410 }
 All Classes Functions Variables Enumerations Friends