libyui  3.10.0
YShortcutManager.cc
1 /*
2  Copyright (C) 2000-2012 Novell, Inc
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: YShortcutManager.cc
20 
21  Author: Stefan Hundhammer <sh@suse.de>
22 
23 /-*/
24 
25 
26 #define YUILogComponent "ui-shortcuts"
27 #include "YUILog.h"
28 
29 #include "YShortcutManager.h"
30 #include "YDialog.h"
31 #include "YDumbTab.h"
32 
33 using std::string;
34 
35 
36 // Threshold of widgets with valid shortcut characters below which no shortcut
37 // check is performed at all. This might regularly occur for languages that
38 // primarily use non-ASCII characters (Russian, Greek, Chinese, Japanese,
39 // Korean).
40 #define MIN_VALID_PERCENT 50
41 
42 // Return the number of elements of an array of any type
43 #define DIM( ARRAY ) ( (int) ( sizeof( ARRAY)/( sizeof( ARRAY[0] ) ) ) )
44 
45 
47  : _dialog( dialog )
48  , _conflictCount( 0 )
49  , _didCheck( false )
50 {
51  YUI_CHECK_PTR( _dialog );
52 }
53 
54 
56 {
58 }
59 
60 
61 void
63 {
64  yuiDebug() << "Checking keyboard shortcuts" << endl;
65 
68 
69  int validCount = 0;
70 
71  for ( unsigned i=0; i < _shortcutList.size(); i++ )
72  {
73  if ( _shortcutList[i]->hasValidShortcutChar() )
74  ++validCount;
75  }
76 
77  int validPercent = _shortcutList.size() > 0 ?
78  ( 100 * validCount ) / _shortcutList.size() : 0;
79 
80  if ( validPercent < MIN_VALID_PERCENT )
81  {
82  // No check at all if there are not enough widgets with valid shortcut
83  // characters ([A-Za-z0-9]). This might regularly occur for languages
84  // that primarily use non-ASCII characters (Russian, Greek, Chinese,
85  // Japanese, Korean).
86 
87  yuiWarning() << "Not enough widgets with valid shortcut characters - no check" << endl;
88  yuiDebug() << "Found " << validCount << " widgets with valid shortcut characters" << endl;
89  return;
90  }
91 
92 
93  // Initialize wanted character counters
94  for ( int i=0; i < DIM( _wanted ); i++ )
95  _wanted[i] = 0;
96 
97  // Initialize used character flags
98  for ( int i=0; i < DIM( _wanted ); i++ )
99  _used[i] = false;
100 
101  // Count wanted shortcuts
102  for ( unsigned i=0; i < _shortcutList.size(); i++ )
103  _wanted[ (int) _shortcutList[i]->preferred() ]++;
104 
105 
106  // Report errors
107 
108  _conflictCount = 0;
109 
110  for ( unsigned i=0; i < _shortcutList.size(); i++ )
111  {
112  YShortcut *shortcut = _shortcutList[i];
113 
114  if ( YShortcut::isValid( shortcut->preferred() ) )
115  {
116  if ( _wanted[ (int) shortcut->preferred() ] > 1 ) // shortcut char used more than once
117  {
118  shortcut->setConflict();
119  _conflictCount++;
120 
121  yuiDebug() << "Shortcut conflict: '" << shortcut->preferred()
122  << "' used for " << shortcut->widget()
123  << endl;
124  }
125  }
126  else // No or invalid shortcut
127  {
128  if ( shortcut->cleanShortcutString().length() > 0 )
129  {
130  shortcut->setConflict();
131  _conflictCount++;
132 
133  if ( ! shortcut->widget()->autoShortcut() )
134  {
135  yuiDebug() << "No valid shortcut for " << shortcut->widget() << endl;
136  }
137  }
138  }
139 
140  if ( ! shortcut->conflict() )
141  {
142  _used[ (int) shortcut->preferred() ] = true;
143  }
144  }
145 
146  _didCheck = true;
147 
148  if ( _conflictCount > 0 )
149  {
150  if ( autoResolve )
151  {
153  }
154  }
155  else
156  {
157  yuiDebug() << "No shortcut conflicts" << endl;
158  }
159 }
160 
161 
162 void
164 {
165  yuiDebug() << "Resolving shortcut conflicts" << endl;
166 
167  if ( ! _didCheck )
168  {
169  yuiError() << "Call checkShortcuts() first!" << endl;
170  return;
171  }
172 
173 
174  // Make a list of all shortcuts with conflicts
175 
176  YShortcutList conflictList;
177  _conflictCount = 0;
178 
179  for ( YShortcutListIterator it = _shortcutList.begin();
180  it != _shortcutList.end();
181  ++it )
182  {
183  if ( ( *it )->conflict() )
184  {
185  conflictList.push_back( *it );
186  _conflictCount++;
187  }
188  }
189 
190 
191  // Resolve each conflict
192 
193  while ( ! conflictList.empty() )
194  {
195  //
196  // Pick a conflict widget to resolve.
197  //
198 
199  // Wizard buttons have priority - check any of them first.
200  int prioIndex = findShortestWizardButton( conflictList );
201 
202  if ( prioIndex < 0 )
203  prioIndex = findShortestWidget( conflictList); // Find the shortest widget. Buttons have priority.
204 
205 
206  // Pick a new shortcut for this widget.
207 
208  YShortcut * shortcut = conflictList[ prioIndex ];
209  resolveConflict( shortcut );
210 
211  if ( shortcut->conflict() )
212  {
213  yuiWarning() << "Couldn't resolve shortcut conflict for " << shortcut->widget() << endl;
214  }
215 
216 
217  // Mark this particular conflict as resolved.
218 
219  conflictList.erase( conflictList.begin() + prioIndex );
220  }
221 
222  if ( _conflictCount > 0 )
223  {
224  yuiDebug() << _conflictCount << " shortcut conflict(s) left" << endl;
225  }
226 }
227 
228 
229 
230 void
232 {
233  // yuiDebug() << "Picking shortcut for " << shortcut->widget() << endl;
234 
235  char candidate = shortcut->preferred(); // This is always normalized, no need to normalize again.
236 
237  if ( ! YShortcut::isValid( candidate ) // Can't use this character - pick another one.
238  || _used[ (int) candidate ] )
239  {
240  candidate = 0; // Restart from scratch - forget the preferred character.
241  string str = shortcut->cleanShortcutString();
242 
243  for ( string::size_type pos = 0; pos < str.length(); pos++ ) // Search all the shortcut string.
244  {
245  char c = YShortcut::normalized( str[ pos ] );
246  // yuiDebug() << "Checking '" << c << "'" << endl;
247 
248  if ( YShortcut::isValid(c) && ! _used[ (int) c ] ) // Could we use this character?
249  {
250  if ( _wanted[ (int) c ] < _wanted[ (int) candidate ] // Is this a better choice than what we already have -
251  || ! YShortcut::isValid( candidate ) ) // or don't we have anything yet?
252  {
253  candidate = c; // Use this one.
254  // yuiDebug() << "Picking '" << c << "'" << endl;
255 
256  if ( _wanted[ (int) c ] == 0 ) // It doesn't get any better than this:
257  break; // Nobody wants this shortcut anyway.
258  }
259  }
260  }
261  }
262 
263  if ( YShortcut::isValid( candidate ) )
264  {
265  if ( candidate != shortcut->preferred() )
266  {
267  if ( shortcut->widget()->autoShortcut() )
268  {
269  yuiDebug() << "Automatically assigning shortcut '" << candidate
270  << "' to " << shortcut->widgetClass() << "(`opt(`autoShortcut ), \""
271  << shortcut->cleanShortcutString() << "\" )"
272  << endl;
273  }
274  else
275  {
276  yuiDebug() << "Reassigning shortcut '" << candidate
277  << "' to " << shortcut->widget()
278  << endl;
279  }
280  shortcut->setShortcut( candidate );
281  }
282  else
283  {
284  yuiDebug() << "Keeping preferred shortcut '" << candidate
285  << "' for " << shortcut->widget()
286  << endl;
287  }
288 
289  _used[ (int) candidate ] = true;
290  shortcut->setConflict( false );
291  }
292  else // No unique shortcut found
293  {
294  yuiWarning() << "Couldn't resolve shortcut conflict for "
295  << shortcut->widget()
296  << " - assigning no shortcut"
297  << endl;
298 
299  shortcut->clearShortcut();
300  shortcut->setConflict( false );
301  }
302 
303  _conflictCount--;
304 }
305 
306 
307 
308 int
309 YShortcutManager::findShortestWizardButton( const YShortcutList & conflictList )
310 {
311  int shortestIndex = -1;
312  int shortestLen = -1;
313 
314  for ( unsigned i=1; i < conflictList.size(); i++ )
315  {
316  if ( conflictList[i]->isWizardButton() )
317  {
318  if ( shortestLen < 0 ||
319  conflictList[i]->distinctShortcutChars() < shortestLen )
320  {
321  shortestIndex = i;
322  shortestLen = conflictList[i]->distinctShortcutChars();
323  }
324 
325  }
326  }
327 
328  return shortestIndex;
329 }
330 
331 
332 
333 unsigned
334 YShortcutManager::findShortestWidget( const YShortcutList & conflictList )
335 {
336  unsigned shortestIndex = 0;
337  int shortestLen = conflictList[ shortestIndex ]->distinctShortcutChars();
338 
339  for ( unsigned i=1; i < conflictList.size(); i++ )
340  {
341  int currentLen = conflictList[i]->distinctShortcutChars();
342 
343  if ( currentLen < shortestLen )
344  {
345  // Found an even shorter one
346 
347  shortestIndex = i;
348  shortestLen = currentLen;
349  }
350  else if ( currentLen == shortestLen )
351  {
352  if ( conflictList[i]->isButton() &&
353  ! conflictList[ shortestIndex ]->isButton() )
354  {
355  // Prefer a button over another widget with the same length
356 
357  shortestIndex = i;
358  shortestLen = currentLen;
359  }
360  }
361  }
362 
363  return shortestIndex;
364 }
365 
366 
367 
368 void
370 {
371  for ( unsigned i=0; i < _shortcutList.size(); i++ )
372  {
373  delete _shortcutList[i];
374  }
375 
376  _shortcutList.clear();
377 }
378 
379 
380 void
381 YShortcutManager::findShortcutWidgets( YWidgetListConstIterator begin,
382  YWidgetListConstIterator end )
383 {
384  for ( YWidgetListConstIterator it = begin; it != end; ++it )
385  {
386  YWidget * widget = *it;
387 
388  YDumbTab * dumbTab = dynamic_cast<YDumbTab *> (widget);
389 
390  if ( dumbTab )
391  {
392  for ( YItemConstIterator it = dumbTab->itemsBegin();
393  it != dumbTab->itemsEnd();
394  ++it )
395  {
396  YItemShortcut * shortcut = new YItemShortcut( dumbTab, *it );
397  _shortcutList.push_back( shortcut );
398  }
399  }
400  else if ( ! widget->shortcutString().empty() )
401  {
402  YShortcut * shortcut = new YShortcut( *it );
403  _shortcutList.push_back( shortcut );
404  }
405 
406  if ( widget->hasChildren() )
407  {
409  widget->childrenEnd() );
410  }
411  }
412 }
YShortcutManager::resolveAllConflicts
void resolveAllConflicts()
Resolve shortcut conflicts.
Definition: YShortcutManager.cc:163
YWidget
Abstract base class of all UI widgets.
Definition: YWidget.h:55
YShortcutManager::checkShortcuts
void checkShortcuts(bool autoResolve=true)
Check the keyboard shortcuts of all children of this dialog (not for sub-dialogs!).
Definition: YShortcutManager.cc:62
YShortcutManager::_dialog
YDialog * _dialog
The dialog this shortcut manager works on.
Definition: YShortcutManager.h:144
YShortcutManager::_shortcutList
YShortcutList _shortcutList
List of all the shortcuts in this dialog.
Definition: YShortcutManager.h:149
YShortcutManager::findShortestWizardButton
int findShortestWizardButton(const YShortcutList &conflictList)
Find the shortest wizard button in 'conflictList', if there is any.
Definition: YShortcutManager.cc:309
YWidget::childrenEnd
YWidgetListIterator childrenEnd() const
Return an interator that points after the last child.
Definition: YWidget.h:218
YShortcut::conflict
bool conflict()
Query the internal 'conflict' marker.
Definition: YShortcut.h:131
YShortcut::widgetClass
const char * widgetClass() const
Returns the textual representation of the widget class of the widget this shortcut data belongs to.
Definition: YShortcut.h:67
YShortcutManager::_used
bool _used[sizeof(char)<< 8]
Flags for used shortcut characters.
Definition: YShortcutManager.h:160
YSelectionWidget::itemsEnd
YItemIterator itemsEnd()
Return an iterator that points behind the last item.
Definition: YSelectionWidget.cc:296
YWidget::autoShortcut
bool autoShortcut() const
Returns 'true' if a keyboard shortcut should automatically be assigned to this widget - without compl...
Definition: YWidget.cc:312
YItemShortcut
Special case for widgets that can have multiple shortcuts based on items (like YDumbTab)
Definition: YShortcut.h:226
YShortcut::widget
YWidget * widget() const
Returns the YWidget this shortcut data belong to.
Definition: YShortcut.h:61
YShortcut::setShortcut
virtual void setShortcut(char newShortcut)
Set (override) the shortcut character.
Definition: YShortcut.cc:143
YShortcut::normalized
static char normalized(char c)
Return the normalized version of shortcut character 'c', i.e.
Definition: YShortcut.cc:301
YShortcut
Helper class for shortcut management: This class holds data about the shortcut for one single widget.
Definition: YShortcut.h:41
YShortcutManager::YShortcutManager
YShortcutManager(YDialog *dialog)
Constructor.
Definition: YShortcutManager.cc:46
YShortcutManager::resolveConflict
void resolveConflict(YShortcut *shortcut)
Pick a new shortcut character for 'shortcut' - one that isn't marked as used in the '_used' array.
Definition: YShortcutManager.cc:231
YShortcutManager::_conflictCount
int _conflictCount
Counter for shortcut conflicts.
Definition: YShortcutManager.h:166
YSelectionWidget::itemsBegin
YItemIterator itemsBegin()
Return an iterator that points to the first item.
Definition: YSelectionWidget.cc:283
YShortcutManager::_wanted
int _wanted[sizeof(char)<< 8]
Counters for wanted shortcut characters.
Definition: YShortcutManager.h:154
YShortcut::clearShortcut
void clearShortcut()
Clear the shortcut: Override the shortcut character with nothing.
Definition: YShortcut.cc:175
YShortcut::isValid
static bool isValid(char c)
Returns 'true' if 'c' is a valid shortcut character, i.e.
Definition: YShortcut.cc:291
YDumbTab
DumbTab: A very simple tab widget that can display and switch between a number of tabs,...
Definition: YDumbTab.h:41
YShortcut::setConflict
void setConflict(bool newConflictState=true)
Set or unset the internal 'conflict' marker.
Definition: YShortcut.h:136
YWidget::shortcutString
virtual std::string shortcutString() const
Get the string of this widget that holds the keyboard shortcut, if any.
Definition: YWidget.h:560
YShortcutManager::findShortcutWidgets
void findShortcutWidgets(YWidgetListConstIterator begin, YWidgetListConstIterator end)
Recursively search all widgets between iterators 'begin' and 'end' (not those of any sub-dialogs!...
Definition: YShortcutManager.cc:381
YWidget::hasChildren
bool hasChildren() const
Returns 'true' if this widget has any children.
Definition: YWidget.h:192
YShortcutManager::clearShortcutList
void clearShortcutList()
Delete all members of the internal shortcut list, then empty the list.
Definition: YShortcutManager.cc:369
YWidget::childrenBegin
YWidgetListIterator childrenBegin() const
Return an iterator that points to the first child or to childrenEnd() if there are no children.
Definition: YWidget.h:212
YShortcut::preferred
char preferred()
The preferred shortcut character, i.e.
Definition: YShortcut.cc:119
YShortcut::cleanShortcutString
std::string cleanShortcutString()
Returns the shortcut string ( from the widget's shortcut property ) without any "&" markers.
Definition: YShortcut.cc:93
YItemConstIterator
YItemCollection::const_iterator YItemConstIterator
Const iterator over YItemCollection.
Definition: YItem.h:42
YDialog
A window in the desktop environment.
Definition: YDialog.h:48
YShortcutManager::~YShortcutManager
virtual ~YShortcutManager()
Destructor.
Definition: YShortcutManager.cc:55
YShortcutManager::findShortestWidget
unsigned findShortestWidget(const YShortcutList &conflictList)
Find the shortest widget in 'conflictList'.
Definition: YShortcutManager.cc:334