libyui  3.12.1
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 <shundhammer@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 #include "YItemSelector.h"
33 #include "YMenuBar.h"
34 
35 using std::string;
36 
37 
38 // Threshold of widgets with valid shortcut characters below which no shortcut
39 // check is performed at all. This might regularly occur for languages that
40 // primarily use non-ASCII characters (Russian, Greek, Chinese, Japanese,
41 // Korean).
42 #define MIN_VALID_PERCENT 50
43 
44 // Return the number of elements of an array of any type
45 #define DIM( ARRAY ) ( (int) ( sizeof( ARRAY)/( sizeof( ARRAY[0] ) ) ) )
46 
47 
49  : _dialog( dialog )
50  , _conflictCount( 0 )
51  , _didCheck( false )
52 {
53  YUI_CHECK_PTR( _dialog );
54 }
55 
56 
58 {
60 }
61 
62 
63 void
65 {
66  yuiDebug() << "Checking keyboard shortcuts" << endl;
67 
70 
71  int validCount = 0;
72 
73  for ( unsigned i=0; i < _shortcutList.size(); i++ )
74  {
75  if ( _shortcutList[i]->hasValidShortcutChar() )
76  ++validCount;
77  }
78 
79  int validPercent = _shortcutList.size() > 0 ?
80  ( 100 * validCount ) / _shortcutList.size() : 0;
81 
82  if ( validPercent < MIN_VALID_PERCENT )
83  {
84  // No check at all if there are not enough widgets with valid shortcut
85  // characters ([A-Za-z0-9]). This might regularly occur for languages
86  // that primarily use non-ASCII characters (Russian, Greek, Chinese,
87  // Japanese, Korean).
88 
89  yuiWarning() << "Not enough widgets with valid shortcut characters - no check" << endl;
90  yuiDebug() << "Found " << validCount << " widgets with valid shortcut characters" << endl;
91  return;
92  }
93 
94 
95  // Initialize wanted character counters
96  for ( int i=0; i < DIM( _wanted ); i++ )
97  _wanted[i] = 0;
98 
99  // Initialize used character flags
100  for ( int i=0; i < DIM( _wanted ); i++ )
101  _used[i] = false;
102 
103  // Count wanted shortcuts
104  for ( unsigned i=0; i < _shortcutList.size(); i++ )
105  _wanted[ (int) _shortcutList[i]->preferred() ]++;
106 
107 
108  // Report errors
109 
110  _conflictCount = 0;
111 
112  for ( unsigned i=0; i < _shortcutList.size(); i++ )
113  {
114  YShortcut *shortcut = _shortcutList[i];
115 
116  if ( YShortcut::isValid( shortcut->preferred() ) )
117  {
118  if ( _wanted[ (int) shortcut->preferred() ] > 1 ) // shortcut char used more than once
119  {
120  shortcut->setConflict();
121  _conflictCount++;
122 
123  yuiDebug() << "Shortcut conflict: '" << shortcut->preferred()
124  << "' used for " << shortcut->widget()
125  << endl;
126  }
127  }
128  else // No or invalid shortcut
129  {
130  if ( shortcut->cleanShortcutString().length() > 0 )
131  {
132  shortcut->setConflict();
133  _conflictCount++;
134 
135  if ( ! shortcut->widget()->autoShortcut() )
136  {
137  yuiDebug() << "No valid shortcut for " << shortcut->widget() << endl;
138  }
139  }
140  }
141 
142  if ( ! shortcut->conflict() )
143  {
144  _used[ (int) shortcut->preferred() ] = true;
145  }
146  }
147 
148  _didCheck = true;
149 
150  if ( _conflictCount > 0 )
151  {
152  if ( autoResolve )
153  {
155  }
156  }
157  else
158  {
159  yuiDebug() << "No shortcut conflicts" << endl;
160  }
161 }
162 
163 
164 void
166 {
167  yuiDebug() << "Resolving shortcut conflicts" << endl;
168 
169  if ( ! _didCheck )
170  {
171  yuiError() << "Call checkShortcuts() first!" << endl;
172  return;
173  }
174 
175 
176  // Make a list of all shortcuts with conflicts
177 
178  YShortcutList conflictList;
179  _conflictCount = 0;
180 
181  for ( YShortcutListIterator it = _shortcutList.begin();
182  it != _shortcutList.end();
183  ++it )
184  {
185  if ( ( *it )->conflict() )
186  {
187  conflictList.push_back( *it );
188  _conflictCount++;
189  }
190  }
191 
192 
193  // Resolve each conflict
194 
195  while ( ! conflictList.empty() )
196  {
197  //
198  // Pick a conflict widget to resolve.
199  //
200 
201  // Wizard buttons have priority - check any of them first.
202  int prioIndex = findShortestWizardButton( conflictList );
203 
204  if ( prioIndex < 0 )
205  prioIndex = findShortestWidget( conflictList); // Find the shortest widget. Buttons have priority.
206 
207 
208  // Pick a new shortcut for this widget.
209 
210  YShortcut * shortcut = conflictList[ prioIndex ];
211  resolveConflict( shortcut );
212 
213  if ( shortcut->conflict() )
214  {
215  yuiWarning() << "Couldn't resolve shortcut conflict for " << shortcut->widget() << endl;
216  }
217 
218 
219  // Mark this particular conflict as resolved.
220 
221  conflictList.erase( conflictList.begin() + prioIndex );
222  }
223 
224  if ( _conflictCount > 0 )
225  {
226  yuiDebug() << _conflictCount << " shortcut conflict(s) left" << endl;
227  }
228 }
229 
230 
231 
232 void
234 {
235  // yuiDebug() << "Picking shortcut for " << shortcut->widget() << endl;
236 
237  char candidate = shortcut->preferred(); // This is always normalized, no need to normalize again.
238 
239  if ( ! YShortcut::isValid( candidate ) // Can't use this character - pick another one.
240  || _used[ (int) candidate ] )
241  {
242  candidate = 0; // Restart from scratch - forget the preferred character.
243  string str = shortcut->cleanShortcutString();
244 
245  for ( string::size_type pos = 0; pos < str.length(); pos++ ) // Search all the shortcut string.
246  {
247  char c = YShortcut::normalized( str[ pos ] );
248  // yuiDebug() << "Checking '" << c << "'" << endl;
249 
250  if ( YShortcut::isValid(c) && ! _used[ (int) c ] ) // Could we use this character?
251  {
252  if ( _wanted[ (int) c ] < _wanted[ (int) candidate ] // Is this a better choice than what we already have -
253  || ! YShortcut::isValid( candidate ) ) // or don't we have anything yet?
254  {
255  candidate = c; // Use this one.
256  // yuiDebug() << "Picking '" << c << "'" << endl;
257 
258  if ( _wanted[ (int) c ] == 0 ) // It doesn't get any better than this:
259  break; // Nobody wants this shortcut anyway.
260  }
261  }
262  }
263  }
264 
265  if ( YShortcut::isValid( candidate ) )
266  {
267  if ( candidate != shortcut->preferred() )
268  {
269  if ( shortcut->widget()->autoShortcut() )
270  {
271  yuiDebug() << "Automatically assigning shortcut '" << candidate
272  << "' to " << shortcut->widgetClass() << "(`opt(`autoShortcut ), \""
273  << shortcut->cleanShortcutString() << "\" )"
274  << endl;
275  }
276  else
277  {
278  yuiDebug() << "Reassigning shortcut '" << candidate
279  << "' to " << shortcut->widget()
280  << endl;
281  }
282  shortcut->setShortcut( candidate );
283  }
284  else
285  {
286  yuiDebug() << "Keeping preferred shortcut '" << candidate
287  << "' for " << shortcut->widget()
288  << endl;
289  }
290 
291  _used[ (int) candidate ] = true;
292  shortcut->setConflict( false );
293  }
294  else // No unique shortcut found
295  {
296  yuiWarning() << "Couldn't resolve shortcut conflict for "
297  << shortcut->widget()
298  << " - assigning no shortcut"
299  << endl;
300 
301  shortcut->clearShortcut();
302  shortcut->setConflict( false );
303  }
304 
305  _conflictCount--;
306 }
307 
308 
309 
310 int
311 YShortcutManager::findShortestWizardButton( const YShortcutList & conflictList )
312 {
313  int shortestIndex = -1;
314  int shortestLen = -1;
315 
316  for ( unsigned i=1; i < conflictList.size(); i++ )
317  {
318  if ( conflictList[i]->isWizardButton() )
319  {
320  if ( shortestLen < 0 ||
321  conflictList[i]->distinctShortcutChars() < shortestLen )
322  {
323  shortestIndex = i;
324  shortestLen = conflictList[i]->distinctShortcutChars();
325  }
326 
327  }
328  }
329 
330  return shortestIndex;
331 }
332 
333 
334 
335 unsigned
336 YShortcutManager::findShortestWidget( const YShortcutList & conflictList )
337 {
338  unsigned shortestIndex = 0;
339  int shortestLen = conflictList[ shortestIndex ]->distinctShortcutChars();
340 
341  for ( unsigned i=1; i < conflictList.size(); i++ )
342  {
343  int currentLen = conflictList[i]->distinctShortcutChars();
344 
345  if ( currentLen < shortestLen )
346  {
347  // Found an even shorter one
348 
349  shortestIndex = i;
350  shortestLen = currentLen;
351  }
352  else if ( currentLen == shortestLen )
353  {
354  if ( conflictList[i]->isButton() &&
355  ! conflictList[ shortestIndex ]->isButton() )
356  {
357  // Prefer a button over another widget with the same length
358 
359  shortestIndex = i;
360  shortestLen = currentLen;
361  }
362  }
363  }
364 
365  return shortestIndex;
366 }
367 
368 
369 
370 void
372 {
373  for ( unsigned i=0; i < _shortcutList.size(); i++ )
374  {
375  delete _shortcutList[i];
376  }
377 
378  _shortcutList.clear();
379 }
380 
381 
382 /**
383  * Try casting to any YSelectionWidget that has no shortcut associated but it should consider any
384  * shortcut of any item (e.g., YDumbTab, YItemSelector, YMenuBar).
385  **/
386 YSelectionWidget * toSelectionWidget( YWidget * widget )
387 {
388  YSelectionWidget * selectionWidget = dynamic_cast<YDumbTab *> (widget);
389 
390  if ( ! selectionWidget )
391  selectionWidget = dynamic_cast<YItemSelector *> (widget);
392 
393  if ( ! selectionWidget )
394  selectionWidget = dynamic_cast<YMenuBar *> (widget);
395 
396  return selectionWidget;
397 }
398 
399 
400 void
401 YShortcutManager::findShortcutWidgets( YWidgetListConstIterator begin,
402  YWidgetListConstIterator end )
403 {
404  for ( YWidgetListConstIterator it = begin; it != end; ++it )
405  {
406  YWidget * widget = *it;
407 
408  YSelectionWidget * selectionWidget = toSelectionWidget( widget );
409 
410  if ( selectionWidget )
411  {
412  for ( YItemConstIterator it = selectionWidget->itemsBegin();
413  it != selectionWidget->itemsEnd();
414  ++it )
415  {
416  YItemShortcut * shortcut = new YItemShortcut( selectionWidget, *it );
417  _shortcutList.push_back( shortcut );
418  }
419  }
420  else if ( ! widget->shortcutString().empty() )
421  {
422  YShortcut * shortcut = new YShortcut( *it );
423  _shortcutList.push_back( shortcut );
424  }
425 
426  if ( widget->hasChildren() )
427  {
429  widget->childrenEnd() );
430  }
431  }
432 }
YShortcutManager::resolveAllConflicts
void resolveAllConflicts()
Resolve shortcut conflicts.
Definition: YShortcutManager.cc:165
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:64
YSelectionWidget
Base class for various kinds of multi-value widgets.
Definition: YSelectionWidget.h:46
YShortcutManager::_dialog
YDialog * _dialog
The dialog this shortcut manager works on.
Definition: YShortcutManager.h:144
YShortcutManager::_shortcutList
YShortcutList _shortcutList
List (owning) 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:311
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:135
YItemSelector
Scrollable item selector widget with not only a label for each item, but also a (possible multi-line)...
Definition: YItemSelector.h:44
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:68
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:303
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:238
YShortcut::widget
YWidget * widget() const
Returns the YWidget this shortcut data belong to.
Definition: YShortcut.h:62
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:48
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:233
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:290
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:140
YWidget::shortcutString
virtual std::string shortcutString() const
Get the string of this widget that holds the keyboard shortcut, if any.
Definition: YWidget.h:560
YMenuBar
A classical menu bar for pulldown menus.
Definition: YMenuBar.h:51
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:401
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:371
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:45
YDialog
A window in the desktop environment.
Definition: YDialog.h:48
YShortcutManager::~YShortcutManager
virtual ~YShortcutManager()
Destructor.
Definition: YShortcutManager.cc:57
YShortcutManager::findShortestWidget
unsigned findShortestWidget(const YShortcutList &conflictList)
Find the shortest widget in 'conflictList'.
Definition: YShortcutManager.cc:336