Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 317201 | Differences between
and this patch

Collapse All | Expand All

(-)src/org/eclipse/e4/ui/bindings/internal/KeyAssistDialog.java (+557 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2011 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     IBM Corporation - initial API and implementation
10
 ******************************************************************************/
11
12
package org.eclipse.e4.ui.bindings.internal;
13
14
import java.util.ArrayList;
15
import java.util.Collection;
16
import java.util.Comparator;
17
import java.util.Iterator;
18
import java.util.List;
19
import java.util.TreeSet;
20
import org.eclipse.core.commands.ParameterizedCommand;
21
import org.eclipse.core.commands.common.CommandException;
22
import org.eclipse.core.commands.common.NotDefinedException;
23
import org.eclipse.e4.core.contexts.IEclipseContext;
24
import org.eclipse.e4.ui.bindings.EBindingService;
25
import org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher;
26
import org.eclipse.jface.bindings.Binding;
27
import org.eclipse.jface.bindings.keys.KeySequence;
28
import org.eclipse.jface.dialogs.Dialog;
29
import org.eclipse.jface.dialogs.PopupDialog;
30
import org.eclipse.jface.window.Window;
31
import org.eclipse.swt.SWT;
32
import org.eclipse.swt.graphics.Point;
33
import org.eclipse.swt.graphics.Rectangle;
34
import org.eclipse.swt.layout.GridData;
35
import org.eclipse.swt.layout.GridLayout;
36
import org.eclipse.swt.widgets.Composite;
37
import org.eclipse.swt.widgets.Control;
38
import org.eclipse.swt.widgets.Event;
39
import org.eclipse.swt.widgets.Label;
40
import org.eclipse.swt.widgets.Listener;
41
import org.eclipse.swt.widgets.Shell;
42
import org.eclipse.swt.widgets.Table;
43
import org.eclipse.swt.widgets.TableColumn;
44
import org.eclipse.swt.widgets.TableItem;
45
46
/**
47
 * <p>
48
 * A dialog displaying a list of key bindings. The dialog will execute a command if it is selected.
49
 * </p>
50
 * <p>
51
 * The methods on this class are not thread-safe and must be run from the UI thread.
52
 * </p>
53
 * 
54
 * @since 3.1
55
 */
56
public final class KeyAssistDialog extends PopupDialog {
57
58
	public static final String WINDOW_SHOW_KEY_ASSIST = "org.eclipse.ui.window.showKeyAssist"; //$NON-NLS-1$
59
60
	/**
61
	 * The data key for the binding stored on an SWT widget. The key is a fully-qualified name, but
62
	 * in reverse order. This is so that the equals method will detect misses faster.
63
	 */
64
	private static final String BINDING_KEY = "Binding.bindings.jface.eclipse.org"; //$NON-NLS-1$
65
66
	/**
67
	 * The value of <code>previousWidth</code> to set if there is no remembered width.
68
	 */
69
	private static final int NO_REMEMBERED_WIDTH = -1;
70
71
	/**
72
	 * The binding service for the associated workbench.
73
	 */
74
	private final EBindingService bindingService;
75
76
	/**
77
	 * The ordered list of command identifiers corresponding to the table.
78
	 */
79
	private final List<Binding> bindings = new ArrayList<Binding>();
80
81
	/**
82
	 * The table containing of the possible completions. This value is <code>null</code> until the
83
	 * dialog is created.
84
	 */
85
	private Table completionsTable = null;
86
87
	/**
88
	 * The width of the shell when it was previously open. This is only remembered until
89
	 * <code>clearRememberedState()</code> is called.
90
	 */
91
	private int previousWidth = NO_REMEMBERED_WIDTH;
92
93
	/**
94
	 * The key binding listener for the associated workbench.
95
	 */
96
	private final KeyBindingDispatcher workbenchKeyboard;
97
98
	/**
99
	 * A sorted map of conflicts or partial matches to be used when the dialog pops up.
100
	 * 
101
	 * @since 3.3
102
	 */
103
	private Collection<Binding> matches;
104
105
	private IEclipseContext context;
106
107
	/**
108
	 * Constructs a new instance of <code>KeyAssistDialog</code>. When the dialog is first
109
	 * constructed, it contains no widgets. The dialog is first created with no parent. If a parent
110
	 * is required, call <code>setParentShell()</code>. Also, between uses, it might be necessary to
111
	 * call <code>setParentShell()</code> as well.
112
	 * 
113
	 * @param context
114
	 *            The context in which this dialog is created; must not be <code>null</code>.
115
	 * @param associatedKeyboard
116
	 *            The key binding listener for the workbench; must not be <code>null</code>.
117
	 * @param associatedState
118
	 *            The key binding state associated with the workbench; must not be <code>null</code>
119
	 *            .
120
	 */
121
	public KeyAssistDialog(final IEclipseContext context,
122
			final KeyBindingDispatcher associatedKeyboard, final KeySequence associatedState) {
123
		super((Shell) null, PopupDialog.INFOPOPUP_SHELLSTYLE, true, false, false, false, null, null);
124
		//super(null, PopupDialog.INFOPOPUP_SHELLSTYLE, true, false, false, false, false, DIALOG_TITLE, getKeySequenceString()); //$NON-NLS-1$
125
126
		this.context = context;
127
		this.bindingService = this.context.get(EBindingService.class);
128
		this.workbenchKeyboard = associatedKeyboard;
129
	}
130
131
	/**
132
	 * Clears out the remembered state of the key assist dialog. This includes its width, as well as
133
	 * the selected binding.
134
	 */
135
	public final void clearRememberedState() {
136
		previousWidth = NO_REMEMBERED_WIDTH;
137
	}
138
139
	/**
140
	 * Closes this shell, but first remembers some state of the dialog. This way it will have a
141
	 * response if asked to open the dialog again or if asked to open the keys preference page. This
142
	 * does not remember the internal state.
143
	 * 
144
	 * @return Whether the shell was already closed.
145
	 */
146
	public final boolean close() {
147
		return close(false);
148
	}
149
150
	/**
151
	 * Closes this shell, but first remembers some state of the dialog. This way it will have a
152
	 * response if asked to open the dialog again or if asked to open the keys preference page.
153
	 * 
154
	 * @param rememberState
155
	 *            Whether the internal state should be remembered.
156
	 * @return Whether the shell was already closed.
157
	 */
158
	public final boolean close(final boolean rememberState) {
159
		return close(rememberState, true);
160
	}
161
162
	/**
163
	 * Closes this shell, but first remembers some state of the dialog. This way it will have a
164
	 * response if asked to open the dialog again or if asked to open the keys preference page.
165
	 * 
166
	 * @param rememberState
167
	 *            Whether the internal state should be remembered.
168
	 * @param resetState
169
	 *            Whether the state should be reset.
170
	 * @return Whether the shell was already closed.
171
	 */
172
	private final boolean close(final boolean rememberState, final boolean resetState) {
173
		final Shell shell = getShell();
174
		if (rememberState) {
175
176
			// Remember the previous width.
177
			final int widthToRemember;
178
			if ((shell != null) && (!shell.isDisposed())) {
179
				widthToRemember = getShell().getSize().x;
180
			} else {
181
				widthToRemember = NO_REMEMBERED_WIDTH;
182
			}
183
184
			// Remember the selected command name and key sequence.
185
			final Binding bindingToRemember;
186
			if ((completionsTable != null) && (!completionsTable.isDisposed())) {
187
				final int selectedIndex = completionsTable.getSelectionIndex();
188
				if (selectedIndex != -1) {
189
					final TableItem selectedItem = completionsTable.getItem(selectedIndex);
190
					bindingToRemember = (Binding) selectedItem.getData(BINDING_KEY);
191
				} else {
192
					bindingToRemember = null;
193
				}
194
			} else {
195
				bindingToRemember = null;
196
			}
197
198
			rememberState(widthToRemember, bindingToRemember);
199
			completionsTable = null;
200
		}
201
		matches = null;
202
		return super.close();
203
	}
204
205
	/**
206
	 * Sets the position for the dialog based on the position of the workbench window. The dialog is
207
	 * flush with the bottom right corner of the workbench window. However, the dialog will not
208
	 * appear outside of the display's client area.
209
	 * 
210
	 * @param size
211
	 *            The final size of the dialog; must not be <code>null</code>.
212
	 */
213
	private final void configureLocation(final Point size) {
214
		final Shell shell = getShell();
215
216
		final Shell workbenchWindowShell = (Shell) shell.getParent();
217
		final int xCoord;
218
		final int yCoord;
219
		if (workbenchWindowShell != null) {
220
			/*
221
			 * Position the shell at the bottom right corner of the workbench window
222
			 */
223
			final Rectangle workbenchWindowBounds = workbenchWindowShell.getBounds();
224
			xCoord = workbenchWindowBounds.x + workbenchWindowBounds.width - size.x - 10;
225
			yCoord = workbenchWindowBounds.y + workbenchWindowBounds.height - size.y - 10;
226
227
		} else {
228
			xCoord = 0;
229
			yCoord = 0;
230
231
		}
232
		final Rectangle bounds = new Rectangle(xCoord, yCoord, size.x, size.y);
233
		shell.setBounds(getConstrainedShellBounds(bounds));
234
	}
235
236
	/**
237
	 * Sets the size for the dialog based on its previous size. The width of the dialog is its
238
	 * previous width, if it exists. Otherwise, it is simply the packed width of the dialog. The
239
	 * maximum width is 40% of the workbench window's width. The dialog's height is the packed
240
	 * height of the dialog to a maximum of half the height of the workbench window.
241
	 * 
242
	 * @return The size of the dialog
243
	 */
244
	private final Point configureSize() {
245
		final Shell shell = getShell();
246
247
		// Get the packed size of the shell.
248
		shell.pack();
249
		final Point size = shell.getSize();
250
251
		// Use the previous width if appropriate.
252
		if ((previousWidth != NO_REMEMBERED_WIDTH) && (previousWidth > size.x)) {
253
			size.x = previousWidth;
254
		}
255
256
		// Enforce maximum sizing.
257
		final Shell workbenchWindowShell = (Shell) shell.getParent();
258
		if (workbenchWindowShell != null) {
259
			final Point workbenchWindowSize = workbenchWindowShell.getSize();
260
			final int maxWidth = workbenchWindowSize.x * 2 / 5;
261
			final int maxHeight = workbenchWindowSize.y / 2;
262
			if (size.x > maxWidth) {
263
				size.x = maxWidth;
264
			}
265
			if (size.y > maxHeight) {
266
				size.y = maxHeight;
267
			}
268
		}
269
270
		// Set the size for the shell.
271
		shell.setSize(size);
272
		return size;
273
	}
274
275
	/**
276
	 * Creates the content area for the key assistant. This creates a table and places it inside the
277
	 * composite. The composite will contain a list of all the key bindings.
278
	 * 
279
	 * @param parent
280
	 *            The parent composite to contain the dialog area; must not be <code>null</code>.
281
	 */
282
	protected final Control createDialogArea(final Composite parent) {
283
284
		// Create a composite for the dialog area.
285
		final Composite composite = new Composite(parent, SWT.NONE);
286
		final GridLayout compositeLayout = new GridLayout();
287
		compositeLayout.marginHeight = 0;
288
		compositeLayout.marginWidth = 0;
289
		composite.setLayout(compositeLayout);
290
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
291
		composite.setBackground(parent.getBackground());
292
293
		// Layout the partial matches.
294
		final Collection<Binding> bindings;
295
		// if we're going to display a list of conflicts or partial matches...
296
		if (matches != null) {
297
			bindings = matches;
298
		}
299
		// else just grab the entire list of active bindings
300
		else {
301
			bindings = getActiveBindings();
302
		}
303
304
		if (bindings == null || bindings.isEmpty()) {
305
			createEmptyDialogArea(composite);
306
		} else {
307
			createTableDialogArea(composite, bindings);
308
		}
309
		return composite;
310
	}
311
312
	/**
313
	 * Creates an empty dialog area with a simple message saying there were no matches. This is used
314
	 * if no partial matches could be found. This should not really ever happen, but might be
315
	 * possible if the commands are changing while waiting for this dialog to open.
316
	 * 
317
	 * @param parent
318
	 *            The parent composite for the dialog area; must not be <code>null</code>.
319
	 */
320
	private final void createEmptyDialogArea(final Composite parent) {
321
		final Label noMatchesLabel = new Label(parent, SWT.NULL);
322
		noMatchesLabel.setText("No matches"); //$NON-NLS-1$
323
		noMatchesLabel.setLayoutData(new GridData(GridData.FILL_BOTH));
324
		noMatchesLabel.setBackground(parent.getBackground());
325
	}
326
327
	/**
328
	 * Creates a dialog area with a table of the partial matches for the current key binding state.
329
	 * The table will be either the minimum width, or <code>previousWidth</code> if it is not
330
	 * <code>NO_REMEMBERED_WIDTH</code>.
331
	 * 
332
	 * @param parent
333
	 *            The parent composite for the dialog area; must not be <code>null</code>.
334
	 * @param partialMatches
335
	 *            The lexicographically sorted map of partial matches for the current state; must
336
	 *            not be <code>null</code> or empty.
337
	 */
338
	private final void createTableDialogArea(final Composite parent,
339
			final Collection<Binding> partialMatches) {
340
		// Layout the table.
341
		completionsTable = new Table(parent, SWT.FULL_SELECTION | SWT.SINGLE);
342
		final GridData gridData = new GridData(GridData.FILL_BOTH);
343
		completionsTable.setLayoutData(gridData);
344
		completionsTable.setBackground(parent.getBackground());
345
		completionsTable.setLinesVisible(true);
346
347
		// Initialize the columns and rows.
348
		bindings.clear();
349
		final TableColumn columnCommandName = new TableColumn(completionsTable, SWT.LEFT, 0);
350
		final TableColumn columnKeySequence = new TableColumn(completionsTable, SWT.LEFT, 1);
351
		final Iterator<Binding> itemsItr = partialMatches.iterator();
352
		while (itemsItr.hasNext()) {
353
			final Binding binding = itemsItr.next();
354
			final String sequence = binding.getTriggerSequence().format();
355
			final ParameterizedCommand command = binding.getParameterizedCommand();
356
			try {
357
				final String[] text = { command.getName(), sequence };
358
				final TableItem item = new TableItem(completionsTable, SWT.NULL);
359
				item.setText(text);
360
				item.setData(BINDING_KEY, binding);
361
				bindings.add(binding);
362
			} catch (NotDefinedException e) {
363
				// Not much to do, but this shouldn't really happen.
364
			}
365
		}
366
367
		Dialog.applyDialogFont(parent);
368
		columnKeySequence.pack();
369
		if (previousWidth != NO_REMEMBERED_WIDTH) {
370
			columnKeySequence.setWidth(previousWidth);
371
		}
372
		columnCommandName.pack();
373
		if (completionsTable.getItems().length > 0) {
374
			completionsTable.setSelection(0);
375
		}
376
377
		/*
378
		 * If you double-click on the table, it should execute the selected command.
379
		 */
380
		completionsTable.addListener(SWT.DefaultSelection, new Listener() {
381
			public final void handleEvent(final Event event) {
382
				executeKeyBinding(event);
383
			}
384
		});
385
	}
386
387
	/**
388
	 * Handles the default selection event on the table of possible completions. This attempts to
389
	 * execute the given command.
390
	 */
391
	private final void executeKeyBinding(final Event trigger) {
392
		final int selectionIndex = completionsTable.getSelectionIndex();
393
		// Try to execute the corresponding command.
394
		if (selectionIndex >= 0) {
395
			final Binding binding = bindings.get(selectionIndex);
396
			try {
397
				// workbenchKeyboard.updateShellKludge(null);
398
				workbenchKeyboard.executeCommand(binding.getParameterizedCommand(), trigger);
399
			} catch (final CommandException e) {
400
				// WorkbenchPlugin.log(binding.getParameterizedCommand().toString(), e);
401
				// TODO we probably need to log something here.
402
				System.err.println(binding.getParameterizedCommand().toString() + " : " + e); //$NON-NLS-1$
403
			}
404
		}
405
	}
406
407
	private final Collection<Binding> getActiveBindings() {
408
409
		Iterator<Binding> iter, matchesIter;
410
		Binding binding, bindingToAdd;
411
		Collection<Binding> matchesForCommand;
412
		Collection<Binding> activeBindings = bindingService.getActiveBindings();
413
		Collection<Binding> conflictBindings = bindingService.getAllConflicts();
414
		Collection<Binding> sortedMatches = new TreeSet<Binding>(new Comparator<Binding>() {
415
			public int compare(Binding binding1, Binding binding2) {
416
				final ParameterizedCommand cmdA = binding1.getParameterizedCommand();
417
				final ParameterizedCommand cmdB = binding2.getParameterizedCommand();
418
				int result = 0;
419
				try {
420
					result = cmdA.getName().compareTo(cmdB.getName());
421
				} catch (NotDefinedException e) {
422
					// whaaa?
423
				}
424
				return result;
425
			}
426
		});
427
428
		// if the active scheme is not the default scheme then we should clean up the active
429
		// bindings list... if we find multiple bindings for the same command and they are for
430
		// different schemes, then we need to handle which one should be displayed in the dialog
431
		if (activeBindings != null) {
432
			iter = activeBindings.iterator();
433
			while (iter.hasNext()) {
434
				binding = iter.next();
435
				matchesForCommand = bindingService
436
						.getBindingsFor(binding.getParameterizedCommand());
437
				// if there is more than one match, then look for a binding that does not belong to
438
				// the default scheme. If they all belong to the default scheme or they all do NOT
439
				// belong to the default scheme, then arbitrarily choose one
440
				if (matchesForCommand != null && matchesForCommand.size() > 1) {
441
					bindingToAdd = null;
442
443
					matchesIter = matchesForCommand.iterator();
444
					while (matchesIter.hasNext()) {
445
						bindingToAdd = matchesIter.next();
446
						if (!bindingToAdd.getSchemeId().equals(EBindingService.DEFAULT_SCHEME_ID)) {
447
							sortedMatches.add(bindingToAdd);
448
							break;
449
						}
450
					}
451
					// if they're all the same, arbitrarily choose one
452
					if (bindingToAdd != null) {
453
						sortedMatches.add(bindingToAdd);
454
					}
455
				}
456
				// if there is only one match, then just add it
457
				else if (matchesForCommand != null && matchesForCommand.size() == 1) {
458
					sortedMatches.addAll(matchesForCommand);
459
				}
460
			}
461
		}
462
		if (conflictBindings != null) {
463
			iter = conflictBindings.iterator();
464
			while (iter.hasNext()) {
465
				binding = iter.next();
466
				sortedMatches.add(binding);
467
			}
468
		}
469
		return sortedMatches;
470
	}
471
472
	/**
473
	 * Opens this dialog. This method can be called multiple times on the same dialog. This only
474
	 * opens the dialog if there is no remembered state; if there is remembered state, then it tries
475
	 * to open the preference page instead.
476
	 * 
477
	 * @return The return code from this dialog.
478
	 */
479
	public final int open() {
480
		// If the dialog is already open, dispose the shell and recreate it.
481
		final Shell shell = getShell();
482
		if (shell != null) {
483
			close(false, false);
484
			return Window.OK;
485
		}
486
		create();
487
488
		// Configure the size and location.
489
		final Point size = configureSize();
490
		configureLocation(size);
491
492
		// Call the super method.
493
		return super.open();
494
	}
495
496
	/**
497
	 * Opens this dialog with the list of bindings for the user to select from.
498
	 * 
499
	 * @return The return code from this dialog.
500
	 * @since 3.3
501
	 */
502
	public final int open(Collection<Binding> bindings) {
503
		matches = new TreeSet<Binding>(new Comparator<Binding>() {
504
			public final int compare(final Binding a, final Binding b) {
505
				final Binding bindingA = a;
506
				final Binding bindingB = b;
507
				final ParameterizedCommand commandA = bindingA.getParameterizedCommand();
508
				final ParameterizedCommand commandB = bindingB.getParameterizedCommand();
509
				try {
510
					return commandA.getName().compareTo(commandB.getName());
511
				} catch (final NotDefinedException e) {
512
					// should not happen
513
					return 0;
514
				}
515
			}
516
		});
517
		matches.addAll(bindings);
518
519
		// If the dialog is already open, dispose the shell and recreate it.
520
		final Shell shell = getShell();
521
		if (shell != null) {
522
			close(false, false);
523
			return Window.OK;
524
		}
525
		create();
526
527
		// Configure the size and location.
528
		final Point size = configureSize();
529
		configureLocation(size);
530
531
		// Call the super method.
532
		return super.open();
533
	}
534
535
	/**
536
	 * Remembers the current state of this dialog.
537
	 * 
538
	 * @param previousWidth
539
	 *            The previous width of the dialog.
540
	 * @param binding
541
	 *            The binding to remember, may be <code>null</code> if none.
542
	 */
543
	private final void rememberState(final int previousWidth, final Binding binding) {
544
		this.previousWidth = previousWidth;
545
	}
546
547
	/**
548
	 * Exposing this within the keys package.
549
	 * 
550
	 * @param newParentShell
551
	 *            The new parent shell; this value may be <code>null</code> if there is to be no
552
	 *            parent.
553
	 */
554
	public final void setParentShell(final Shell newParentShell) {
555
		super.setParentShell(newParentShell);
556
	}
557
}
(-)src/org/eclipse/e4/ui/bindings/keys/KeyBindingDispatcher.java (-36 / +94 lines)
Lines 25-30 Link Here
25
import org.eclipse.e4.core.di.annotations.Optional;
25
import org.eclipse.e4.core.di.annotations.Optional;
26
import org.eclipse.e4.core.services.log.Logger;
26
import org.eclipse.e4.core.services.log.Logger;
27
import org.eclipse.e4.ui.bindings.EBindingService;
27
import org.eclipse.e4.ui.bindings.EBindingService;
28
import org.eclipse.e4.ui.bindings.internal.KeyAssistDialog;
28
import org.eclipse.jface.bindings.Binding;
29
import org.eclipse.jface.bindings.Binding;
29
import org.eclipse.jface.bindings.keys.KeySequence;
30
import org.eclipse.jface.bindings.keys.KeySequence;
30
import org.eclipse.jface.bindings.keys.KeyStroke;
31
import org.eclipse.jface.bindings.keys.KeyStroke;
Lines 37-42 Link Here
37
import org.eclipse.swt.widgets.Display;
38
import org.eclipse.swt.widgets.Display;
38
import org.eclipse.swt.widgets.Event;
39
import org.eclipse.swt.widgets.Event;
39
import org.eclipse.swt.widgets.Listener;
40
import org.eclipse.swt.widgets.Listener;
41
import org.eclipse.swt.widgets.Shell;
40
import org.eclipse.swt.widgets.Text;
42
import org.eclipse.swt.widgets.Text;
41
import org.eclipse.swt.widgets.Widget;
43
import org.eclipse.swt.widgets.Widget;
42
44
Lines 49-54 Link Here
49
 * </p>
51
 * </p>
50
 */
52
 */
51
public class KeyBindingDispatcher {
53
public class KeyBindingDispatcher {
54
55
	private KeyAssistDialog keyAssistDialog = null;
56
52
	/**
57
	/**
53
	 * A display filter for handling key bindings. This filter can either be enabled or disabled. If
58
	 * A display filter for handling key bindings. This filter can either be enabled or disabled. If
54
	 * disabled, the filter does not process incoming events. The filter starts enabled.
59
	 * disabled, the filter does not process incoming events. The filter starts enabled.
Lines 120-127 Link Here
120
	 * @return The set of nearly matching key strokes. It is never <code>null</code>, but may be
125
	 * @return The set of nearly matching key strokes. It is never <code>null</code>, but may be
121
	 *         empty.
126
	 *         empty.
122
	 */
127
	 */
123
	public static List generatePossibleKeyStrokes(Event event) {
128
	public static List<KeyStroke> generatePossibleKeyStrokes(Event event) {
124
		final List keyStrokes = new ArrayList(3);
129
		final List<KeyStroke> keyStrokes = new ArrayList<KeyStroke>(3);
125
130
126
		/*
131
		/*
127
		 * If this is not a keyboard event, then there are no key strokes. This can happen if we are
132
		 * If this is not a keyboard event, then there are no key strokes. This can happen if we are
Lines 170-176 Link Here
170
	 *            <code>null</code>.
175
	 *            <code>null</code>.
171
	 * @return <code>true</code> if the key is an out-of-order key; <code>false</code> otherwise.
176
	 * @return <code>true</code> if the key is an out-of-order key; <code>false</code> otherwise.
172
	 */
177
	 */
173
	private static boolean isOutOfOrderKey(List keyStrokes) {
178
	private static boolean isOutOfOrderKey(List<KeyStroke> keyStrokes) {
174
		// Compare to see if one of the possible key strokes is out of order.
179
		// Compare to see if one of the possible key strokes is out of order.
175
		final KeyStroke[] outOfOrderKeyStrokes = outOfOrderKeys.getKeyStrokes();
180
		final KeyStroke[] outOfOrderKeyStrokes = outOfOrderKeys.getKeyStrokes();
176
		final int outOfOrderKeyStrokesLength = outOfOrderKeyStrokes.length;
181
		final int outOfOrderKeyStrokesLength = outOfOrderKeyStrokes.length;
Lines 236-243 Link Here
236
	 * command manager. If there is a handler and it is enabled, then it tries the actual execution.
241
	 * command manager. If there is a handler and it is enabled, then it tries the actual execution.
237
	 * Execution failures are logged. When this method completes, the key binding state is reset.
242
	 * Execution failures are logged. When this method completes, the key binding state is reset.
238
	 * 
243
	 * 
239
	 * @param binding
244
	 * @param parameterizedCommand
240
	 *            The binding that should be executed; should not be <code>null</code>.
245
	 *            The command that should be executed; should not be <code>null</code>.
241
	 * @param trigger
246
	 * @param trigger
242
	 *            The triggering event; may be <code>null</code>.
247
	 *            The triggering event; may be <code>null</code>.
243
	 * @return <code>true</code> if there was a handler; <code>false</code> otherwise.
248
	 * @return <code>true</code> if there was a handler; <code>false</code> otherwise.
Lines 252-276 Link Here
252
		// Reset the key binding state (close window, clear status line, etc.)
257
		// Reset the key binding state (close window, clear status line, etc.)
253
		resetState(false);
258
		resetState(false);
254
259
255
		// Dispatch to the handler.
256
		final EHandlerService handlerService = getHandlerService();
260
		final EHandlerService handlerService = getHandlerService();
257
		final Command command = parameterizedCommand.getCommand();
261
		final Command command = parameterizedCommand.getCommand();
262
263
		// if the key assist dialog is open and we're trying to execute a command, then close it
264
		if (keyAssistDialog != null) {
265
			if (!parameterizedCommand.getId().equals("org.eclipse.ui.window.showKeyAssist")) { //$NON-NLS-1$
266
				final Shell shell = keyAssistDialog.getShell();
267
				if (shell != null && !shell.isDisposed() && shell.isVisible()) {
268
					keyAssistDialog.close(true);
269
				}
270
			}
271
		}
272
258
		final boolean commandDefined = command.isDefined();
273
		final boolean commandDefined = command.isDefined();
259
		final boolean commandEnabled = handlerService.canExecute(parameterizedCommand);
274
		boolean commandEnabled;
260
		boolean commandHandled = HandlerServiceImpl.lookUpHandler(context, command.getId()) != null;
275
		boolean commandHandled;
276
277
		commandEnabled = handlerService.canExecute(parameterizedCommand);
278
		commandHandled = HandlerServiceImpl.lookUpHandler(context, command.getId()) != null;
261
279
262
		if (!commandEnabled && commandHandled && commandDefined) {
280
		if (!commandEnabled && commandHandled && commandDefined) {
281
			if (keyAssistDialog != null) {
282
				keyAssistDialog.clearRememberedState();
283
			}
263
			return true;
284
			return true;
264
		}
285
		}
265
		try {
286
		if (commandEnabled) {
266
			handlerService.executeHandler(parameterizedCommand);
287
			try {
267
		} catch (final Exception e) {
288
				handlerService.executeHandler(parameterizedCommand);
268
			commandHandled = false;
289
			} catch (final Exception e) {
269
			if (logger != null) {
290
				commandHandled = false;
270
				logger.error(e);
291
				if (logger != null) {
292
					logger.error(e);
293
				}
294
			}
295
			/*
296
			 * Now that the command has executed (and had the opportunity to use the remembered
297
			 * state of the dialog), it is safe to delete that information.
298
			 */
299
			if (keyAssistDialog != null) {
300
				keyAssistDialog.clearRememberedState();
271
			}
301
			}
272
		}
302
		}
273
274
		return (commandDefined && commandHandled);
303
		return (commandDefined && commandHandled);
275
	}
304
	}
276
305
Lines 300-306 Link Here
300
		}
329
		}
301
330
302
		// Allow special key out-of-order processing.
331
		// Allow special key out-of-order processing.
303
		List keyStrokes = generatePossibleKeyStrokes(event);
332
		List<KeyStroke> keyStrokes = generatePossibleKeyStrokes(event);
304
		if (isOutOfOrderKey(keyStrokes)) {
333
		if (isOutOfOrderKey(keyStrokes)) {
305
			Widget widget = event.widget;
334
			Widget widget = event.widget;
306
			if ((event.character == SWT.DEL)
335
			if ((event.character == SWT.DEL)
Lines 335-341 Link Here
335
					widget.addListener(SWT.KeyDown, outOfOrderListener);
364
					widget.addListener(SWT.KeyDown, outOfOrderListener);
336
					outOfOrderListener.setActive(event.time);
365
					outOfOrderListener.setActive(event.time);
337
				}
366
				}
338
339
			}
367
			}
340
368
341
			/*
369
			/*
Lines 398-404 Link Here
398
	 * @param sequence
426
	 * @param sequence
399
	 *            The new key sequence for the state; should not be <code>null</code>.
427
	 *            The new key sequence for the state; should not be <code>null</code>.
400
	 */
428
	 */
401
	private void incrementState(KeySequence sequence) {
429
	private void incrementState(final KeySequence sequence) {
402
		state = sequence;
430
		state = sequence;
403
		// Record the starting time.
431
		// Record the starting time.
404
		startTime = System.currentTimeMillis();
432
		startTime = System.currentTimeMillis();
Lines 408-414 Link Here
408
			public void run() {
436
			public void run() {
409
				if ((System.currentTimeMillis() > (myStartTime - DELAY))
437
				if ((System.currentTimeMillis() > (myStartTime - DELAY))
410
						&& (startTime == myStartTime)) {
438
						&& (startTime == myStartTime)) {
411
					openMultiKeyAssistShell();
439
					Collection<Binding> partialMatches = bindingService.getPartialMatches(sequence);
440
					openKeyAssistShell(partialMatches);
441
					resetState(false);
412
				}
442
				}
413
			}
443
			}
414
		});
444
		});
Lines 421-430 Link Here
421
	 * executions.
451
	 * executions.
422
	 */
452
	 */
423
	public final void openMultiKeyAssistShell() {
453
	public final void openMultiKeyAssistShell() {
424
		// TODO: we need some kind of multi binding assistence.
454
		if (keyAssistDialog == null) {
455
			keyAssistDialog = new KeyAssistDialog(context, this, state);
456
		}
457
		if (keyAssistDialog.getShell() == null) {
458
			keyAssistDialog.setParentShell(getDisplay().getActiveShell());
459
		}
460
		keyAssistDialog.open();
461
425
		resetState(true);
462
		resetState(true);
426
	}
463
	}
427
464
465
	public final void openKeyAssistShell(final Collection<Binding> bindings) {
466
		if (keyAssistDialog == null) {
467
			keyAssistDialog = new KeyAssistDialog(context, this, state);
468
		}
469
		if (keyAssistDialog.getShell() == null) {
470
			keyAssistDialog.setParentShell(getDisplay().getActiveShell());
471
		}
472
		keyAssistDialog.open(bindings);
473
	}
474
428
	/**
475
	/**
429
	 * Determines whether the key sequence partially matches on of the active key bindings.
476
	 * Determines whether the key sequence partially matches on of the active key bindings.
430
	 * 
477
	 * 
Lines 447-460 Link Here
447
		return getBindingService().isPerfectMatch(keySequence);
494
		return getBindingService().isPerfectMatch(keySequence);
448
	}
495
	}
449
496
450
	public boolean press(List potentialKeyStrokes, Event event) {
497
	/**
498
	 * @param potentialKeyStrokes
499
	 * @param event
500
	 * @return
501
	 */
502
	public boolean press(List<KeyStroke> potentialKeyStrokes, Event event) {
451
		KeySequence errorSequence = null;
503
		KeySequence errorSequence = null;
452
		// Collection errorMatch = null;
504
		// Collection errorMatch = null;
453
505
454
		KeySequence sequenceBeforeKeyStroke = state;
506
		KeySequence sequenceBeforeKeyStroke = state;
455
		for (Iterator iterator = potentialKeyStrokes.iterator(); iterator.hasNext();) {
507
		for (Iterator<KeyStroke> iterator = potentialKeyStrokes.iterator(); iterator.hasNext();) {
456
			KeySequence sequenceAfterKeyStroke = KeySequence.getInstance(sequenceBeforeKeyStroke,
508
			KeySequence sequenceAfterKeyStroke = KeySequence.getInstance(sequenceBeforeKeyStroke,
457
					(KeyStroke) iterator.next());
509
					iterator.next());
458
			if (isPartialMatch(sequenceAfterKeyStroke)) {
510
			if (isPartialMatch(sequenceAfterKeyStroke)) {
459
				incrementState(sequenceAfterKeyStroke);
511
				incrementState(sequenceAfterKeyStroke);
460
				return true;
512
				return true;
Lines 467-491 Link Here
467
					return true;
519
					return true;
468
				}
520
				}
469
521
470
				// } else if ((keyAssistDialog != null)
522
			} else if ((keyAssistDialog != null)
471
				// && (keyAssistDialog.getShell() != null)
523
					&& (keyAssistDialog.getShell() != null)
472
				// && ((event.keyCode == SWT.ARROW_DOWN) || (event.keyCode == SWT.ARROW_UP)
524
					&& ((event.keyCode == SWT.ARROW_DOWN) || (event.keyCode == SWT.ARROW_UP)
473
				// || (event.keyCode == SWT.ARROW_LEFT)
525
							|| (event.keyCode == SWT.ARROW_LEFT)
474
				// || (event.keyCode == SWT.ARROW_RIGHT) || (event.keyCode == SWT.CR)
526
							|| (event.keyCode == SWT.ARROW_RIGHT) || (event.keyCode == SWT.CR)
475
				// || (event.keyCode == SWT.PAGE_UP) || (event.keyCode == SWT.PAGE_DOWN))) {
527
							|| (event.keyCode == SWT.PAGE_UP) || (event.keyCode == SWT.PAGE_DOWN))) {
476
				// // We don't want to swallow keyboard navigation keys.
528
				// We don't want to swallow keyboard navigation keys.
477
				// return false;
529
				return false;
478
530
479
			} else {
531
			} else {
480
				Collection match = getBindingService().getConflictsFor(sequenceAfterKeyStroke);
532
				Collection<Binding> matches = getBindingService().getConflictsFor(
481
				if (match != null) {
533
						sequenceAfterKeyStroke);
534
				if (matches != null && !matches.isEmpty()) {
482
					errorSequence = sequenceAfterKeyStroke;
535
					errorSequence = sequenceAfterKeyStroke;
536
					openKeyAssistShell(matches);
483
				}
537
				}
484
			}
538
			}
485
		}
539
		}
486
487
		resetState(true);
540
		resetState(true);
488
		if (sequenceBeforeKeyStroke.isEmpty() && errorSequence != null) {
541
		if (sequenceBeforeKeyStroke.isEmpty() && errorSequence != null) {
542
			// TODO: open with the modified list for conflicts
489
			// openKeyAssistShell(errorMatch);
543
			// openKeyAssistShell(errorMatch);
490
		}
544
		}
491
		return !sequenceBeforeKeyStroke.isEmpty();
545
		return !sequenceBeforeKeyStroke.isEmpty();
Lines 508-514 Link Here
508
	 * @param event
562
	 * @param event
509
	 *            The event to process; must not be <code>null</code>.
563
	 *            The event to process; must not be <code>null</code>.
510
	 */
564
	 */
511
	void processKeyEvent(List keyStrokes, Event event) {
565
	void processKeyEvent(List<KeyStroke> keyStrokes, Event event) {
512
		// Dispatch the keyboard shortcut, if any.
566
		// Dispatch the keyboard shortcut, if any.
513
		boolean eatKey = false;
567
		boolean eatKey = false;
514
		if (!keyStrokes.isEmpty()) {
568
		if (!keyStrokes.isEmpty()) {
Lines 530-537 Link Here
530
		}
584
		}
531
	}
585
	}
532
586
533
	private void resetState(boolean b) {
587
	private void resetState(boolean clearRememberedState) {
588
		startTime = Long.MAX_VALUE;
534
		state = KeySequence.getInstance();
589
		state = KeySequence.getInstance();
590
		// if (keyAssistDialog != null && clearRememberedState) {
591
		// keyAssistDialog.clearRememberedState();
592
		// }
535
	}
593
	}
536
594
537
	final public KeySequence getBuffer() {
595
	final public KeySequence getBuffer() {

Return to bug 317201