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 }
YItemCollection::const_iterator YItemConstIterator
Const iterator over YItemCollection.
Definition: YItem.h:42
A window in the desktop environment.
Definition: YDialog.h:48
DumbTab: A very simple tab widget that can display and switch between a number of tabs,...
Definition: YDumbTab.h:41
Special case for widgets that can have multiple shortcuts based on items (like YDumbTab)
Definition: YShortcut.h:226
YItemIterator itemsEnd()
Return an iterator that points behind the last item.
YItemIterator itemsBegin()
Return an iterator that points to the first item.
YShortcutList _shortcutList
List of all the shortcuts in this dialog.
YDialog * _dialog
The dialog this shortcut manager works on.
int _conflictCount
Counter for shortcut conflicts.
bool _used[sizeof(char)<< 8]
Flags for used shortcut characters.
int _wanted[sizeof(char)<< 8]
Counters for wanted shortcut characters.
YShortcutManager(YDialog *dialog)
Constructor.
void checkShortcuts(bool autoResolve=true)
Check the keyboard shortcuts of all children of this dialog (not for sub-dialogs!).
virtual ~YShortcutManager()
Destructor.
void clearShortcutList()
Delete all members of the internal shortcut list, then empty the list.
void resolveConflict(YShortcut *shortcut)
Pick a new shortcut character for 'shortcut' - one that isn't marked as used in the '_used' array.
unsigned findShortestWidget(const YShortcutList &conflictList)
Find the shortest widget in 'conflictList'.
int findShortestWizardButton(const YShortcutList &conflictList)
Find the shortest wizard button in 'conflictList', if there is any.
void resolveAllConflicts()
Resolve shortcut conflicts.
void findShortcutWidgets(YWidgetListConstIterator begin, YWidgetListConstIterator end)
Recursively search all widgets between iterators 'begin' and 'end' (not those of any sub-dialogs!...
Helper class for shortcut management: This class holds data about the shortcut for one single widget.
Definition: YShortcut.h:41
char preferred()
The preferred shortcut character, i.e.
Definition: YShortcut.cc:119
static char normalized(char c)
Return the normalized version of shortcut character 'c', i.e.
Definition: YShortcut.cc:301
void setConflict(bool newConflictState=true)
Set or unset the internal 'conflict' marker.
Definition: YShortcut.h:136
const char * widgetClass() const
Returns the textual representation of the widget class of the widget this shortcut data belongs to.
Definition: YShortcut.h:67
YWidget * widget() const
Returns the YWidget this shortcut data belong to.
Definition: YShortcut.h:61
bool conflict()
Query the internal 'conflict' marker.
Definition: YShortcut.h:131
virtual void setShortcut(char newShortcut)
Set (override) the shortcut character.
Definition: YShortcut.cc:143
static bool isValid(char c)
Returns 'true' if 'c' is a valid shortcut character, i.e.
Definition: YShortcut.cc:291
void clearShortcut()
Clear the shortcut: Override the shortcut character with nothing.
Definition: YShortcut.cc:175
std::string cleanShortcutString()
Returns the shortcut string ( from the widget's shortcut property ) without any "&" markers.
Definition: YShortcut.cc:93
Abstract base class of all UI widgets.
Definition: YWidget.h:55
bool hasChildren() const
Returns 'true' if this widget has any children.
Definition: YWidget.h:192
bool autoShortcut() const
Returns 'true' if a keyboard shortcut should automatically be assigned to this widget - without compl...
Definition: YWidget.cc:312
YWidgetListIterator childrenBegin() const
Return an iterator that points to the first child or to childrenEnd() if there are no children.
Definition: YWidget.h:212
YWidgetListIterator childrenEnd() const
Return an interator that points after the last child.
Definition: YWidget.h:218
virtual std::string shortcutString() const
Get the string of this widget that holds the keyboard shortcut, if any.
Definition: YWidget.h:560