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