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

Return to bug 317201