libyui
3.0.10
|
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 }