1 /*******************************************************************************
2  * Copyright (c) 2005, 2020 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Remy Chi Jian Suen <remy.suen@gmail.com> -
14  *     		Bug 186522 - [KeyBindings] New Keys preference page does not resort by binding with conflicts
15  *     		Bug 226342 - [KeyBindings] Keys preference page conflict table is hard to read
16  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 440810, 491393
17  *     Cornel Izbasa <cizbasa@info.uvt.ro> - Bug 442215
18  *     Christian Georgi (SAP SE) - Bug 540440
19  *******************************************************************************/
20 
21 package org.eclipse.ui.internal.keys;
22 
23 import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
24 
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.Map;
30 import org.eclipse.core.commands.Category;
31 import org.eclipse.core.commands.ParameterizedCommand;
32 import org.eclipse.core.commands.util.Tracing;
33 import org.eclipse.core.runtime.IStatus;
34 import org.eclipse.core.runtime.Status;
35 import org.eclipse.jface.action.LegacyActionTools;
36 import org.eclipse.jface.bindings.Binding;
37 import org.eclipse.jface.bindings.TriggerSequence;
38 import org.eclipse.jface.bindings.keys.KeySequence;
39 import org.eclipse.jface.bindings.keys.KeySequenceText;
40 import org.eclipse.jface.bindings.keys.KeyStroke;
41 import org.eclipse.jface.dialogs.IDialogConstants;
42 import org.eclipse.jface.dialogs.IDialogSettings;
43 import org.eclipse.jface.dialogs.MessageDialog;
44 import org.eclipse.jface.preference.IPreferenceStore;
45 import org.eclipse.jface.preference.PreferencePage;
46 import org.eclipse.jface.resource.DeviceResourceException;
47 import org.eclipse.jface.resource.ImageDescriptor;
48 import org.eclipse.jface.resource.JFaceResources;
49 import org.eclipse.jface.resource.LocalResourceManager;
50 import org.eclipse.jface.util.IPropertyChangeListener;
51 import org.eclipse.jface.viewers.ColumnWeightData;
52 import org.eclipse.jface.viewers.ComboViewer;
53 import org.eclipse.jface.viewers.IBaseLabelProvider;
54 import org.eclipse.jface.viewers.ISelection;
55 import org.eclipse.jface.viewers.IStructuredContentProvider;
56 import org.eclipse.jface.viewers.ITableLabelProvider;
57 import org.eclipse.jface.viewers.ITreeContentProvider;
58 import org.eclipse.jface.viewers.LabelProvider;
59 import org.eclipse.jface.viewers.StructuredSelection;
60 import org.eclipse.jface.viewers.TableLayout;
61 import org.eclipse.jface.viewers.TableViewer;
62 import org.eclipse.jface.viewers.TreeViewer;
63 import org.eclipse.jface.viewers.Viewer;
64 import org.eclipse.jface.viewers.ViewerComparator;
65 import org.eclipse.jface.window.Window;
66 import org.eclipse.swt.SWT;
67 import org.eclipse.swt.custom.BusyIndicator;
68 import org.eclipse.swt.events.FocusEvent;
69 import org.eclipse.swt.events.FocusListener;
70 import org.eclipse.swt.events.SelectionAdapter;
71 import org.eclipse.swt.events.SelectionEvent;
72 import org.eclipse.swt.events.SelectionListener;
73 import org.eclipse.swt.graphics.FontMetrics;
74 import org.eclipse.swt.graphics.GC;
75 import org.eclipse.swt.graphics.Image;
76 import org.eclipse.swt.graphics.Point;
77 import org.eclipse.swt.layout.GridData;
78 import org.eclipse.swt.layout.GridLayout;
79 import org.eclipse.swt.widgets.Button;
80 import org.eclipse.swt.widgets.Composite;
81 import org.eclipse.swt.widgets.Control;
82 import org.eclipse.swt.widgets.Group;
83 import org.eclipse.swt.widgets.Label;
84 import org.eclipse.swt.widgets.Menu;
85 import org.eclipse.swt.widgets.MenuItem;
86 import org.eclipse.swt.widgets.Table;
87 import org.eclipse.swt.widgets.TableColumn;
88 import org.eclipse.swt.widgets.Text;
89 import org.eclipse.swt.widgets.Tree;
90 import org.eclipse.swt.widgets.TreeColumn;
91 import org.eclipse.swt.widgets.TreeItem;
92 import org.eclipse.ui.IWorkbench;
93 import org.eclipse.ui.IWorkbenchPreferencePage;
94 import org.eclipse.ui.PlatformUI;
95 import org.eclipse.ui.commands.ICommandImageService;
96 import org.eclipse.ui.commands.ICommandService;
97 import org.eclipse.ui.dialogs.FilteredTree;
98 import org.eclipse.ui.internal.IPreferenceConstants;
99 import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
100 import org.eclipse.ui.internal.WorkbenchPlugin;
101 import org.eclipse.ui.internal.keys.model.BindingElement;
102 import org.eclipse.ui.internal.keys.model.BindingModel;
103 import org.eclipse.ui.internal.keys.model.CommonModel;
104 import org.eclipse.ui.internal.keys.model.ConflictModel;
105 import org.eclipse.ui.internal.keys.model.ContextElement;
106 import org.eclipse.ui.internal.keys.model.ContextModel;
107 import org.eclipse.ui.internal.keys.model.KeyController;
108 import org.eclipse.ui.internal.keys.model.ModelElement;
109 import org.eclipse.ui.internal.keys.model.SchemeElement;
110 import org.eclipse.ui.internal.keys.model.SchemeModel;
111 import org.eclipse.ui.internal.keys.show.ShowKeysToggleHandler;
112 import org.eclipse.ui.internal.keys.show.ShowKeysUI;
113 import org.eclipse.ui.internal.misc.Policy;
114 import org.eclipse.ui.internal.util.Util;
115 import org.eclipse.ui.keys.IBindingService;
116 
117 /**
118  * <p>
119  * A preference page that is capable of displaying and editing the bindings
120  * between commands and user input events. These are typically things like
121  * keyboard shortcuts.
122  * </p>
123  * <p>
124  * This preference page has four general types of methods. Create methods are
125  * called when the page is first made visible. They are responsible for creating
126  * all of the widgets, and laying them out within the preference page. Fill
127  * methods populate the contents of the widgets that contain collections of data
128  * from which items can be selected. The select methods respond to selection
129  * events from the user, such as a button press or a table selection. The update
130  * methods update the contents of various widgets based on the current state of
131  * the user interface. For example, the command name label will always try to
132  * match the current select in the binding table.
133  * </p>
134  * <p>
135  * Updated in 3.4 to work with a model backed by the real KeyBinding and
136  * ParameterizedCommand objects.
137  * </p>
138  *
139  * @since 3.2
140  */
141 public class NewKeysPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
142 
143 	private static boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;
144 
145 	private static final String TRACING_COMPONENT = "NewKeysPref"; //$NON-NLS-1$
146 
147 	public static final String TAG_DIALOG_SECTION = "org.eclipse.ui.preferences.keysPreferencePage"; //$NON-NLS-1$
148 
149 	private static final String TAG_FILTER_ACTION_SETS = "actionSetFilter"; //$NON-NLS-1$
150 
151 	private static final String TAG_FILTER_INTERNAL = "internalFilter"; //$NON-NLS-1$
152 
153 	private static final String TAG_FILTER_UNCAT = "uncategorizedFilter"; //$NON-NLS-1$
154 
155 	/**
156 	 * The number of items to show in the bindings table tree.
157 	 */
158 	private static final int ITEMS_TO_SHOW = 12;
159 
160 	private static final int COMMAND_NAME_COLUMN = 0;
161 	private static final int KEY_SEQUENCE_COLUMN = 1;
162 	private static final int CONTEXT_COLUMN = 2;
163 	private static final int CATEGORY_COLUMN = 3;
164 	private static final int USER_DELTA_COLUMN = 4;
165 	private static int NUM_OF_COLUMNS = USER_DELTA_COLUMN + 1;
166 
167 	private ComboViewer fSchemeCombo;
168 
169 	private CategoryPatternFilter fPatternFilter;
170 
171 	private CategoryFilterTree fFilteredTree;
172 
173 	private boolean fFilterActionSetContexts = true;
174 
175 	private boolean fFilterInternalContexts = true;
176 
177 	private KeyController keyController;
178 
179 	private Category fDefaultCategory;
180 
181 	private Label commandNameValueLabel;
182 
183 	private Text fBindingText;
184 
185 	private Text fDescriptionText;
186 
187 	private ComboViewer fWhenCombo;
188 
189 	private IBindingService fBindingService;
190 
191 	private KeySequenceText fKeySequenceText;
192 
193 	private TableViewer conflictViewer;
194 
195 	private Button fShowCommandKey;
196 	private Button fShowCommandKeyForMouseEvents;
197 
198 	private ICommandImageService commandImageService;
199 
200 	private ICommandService commandService;
201 
202 	private IWorkbench fWorkbench;
203 
204 	/**
205 	 * A FilteredTree that provides a combo which is used to organize and display
206 	 * elements in the tree according to the selected criteria.
207 	 *
208 	 */
209 	protected static class CategoryFilterTree extends FilteredTree {
210 
211 		private CategoryPatternFilter filter;
212 
213 		/**
214 		 * Constructor for PatternFilteredTree.
215 		 *
216 		 * @param parent
217 		 * @param treeStyle
218 		 * @param filter
219 		 */
CategoryFilterTree(Composite parent, int treeStyle, CategoryPatternFilter filter)220 		protected CategoryFilterTree(Composite parent, int treeStyle, CategoryPatternFilter filter) {
221 			super(parent, treeStyle, filter, true);
222 			this.filter = filter;
223 			setQuickSelectionMode(true);
224 		}
225 
filterCategories(boolean b)226 		public void filterCategories(boolean b) {
227 			filter.filterCategories(b);
228 			textChanged();
229 		}
230 
isFilteringCategories()231 		public boolean isFilteringCategories() {
232 			return filter.isFilteringCategories();
233 		}
234 	}
235 
236 	private final class BindingModelComparator extends ViewerComparator {
237 		private LinkedList<Integer> sortColumns = new LinkedList<>();
238 		private boolean ascending = true;
239 
BindingModelComparator()240 		public BindingModelComparator() {
241 			for (int i = 0; i < NUM_OF_COLUMNS; i++) {
242 				sortColumns.add(Integer.valueOf(i));
243 			}
244 		}
245 
getSortColumn()246 		public int getSortColumn() {
247 			return sortColumns.getFirst().intValue();
248 		}
249 
setSortColumn(int column)250 		public void setSortColumn(int column) {
251 			if (column == getSortColumn()) {
252 				return;
253 			}
254 			Integer sortColumn = Integer.valueOf(column);
255 			sortColumns.remove(sortColumn);
256 			sortColumns.addFirst(sortColumn);
257 		}
258 
259 		/**
260 		 * @return Returns the ascending.
261 		 */
isAscending()262 		public boolean isAscending() {
263 			return ascending;
264 		}
265 
266 		/**
267 		 * @param ascending The ascending to set.
268 		 */
setAscending(boolean ascending)269 		public void setAscending(boolean ascending) {
270 			this.ascending = ascending;
271 		}
272 
273 		@Override
compare(final Viewer viewer, final Object a, final Object b)274 		public int compare(final Viewer viewer, final Object a, final Object b) {
275 			int result = 0;
276 			Iterator<Integer> i = sortColumns.iterator();
277 			while (i.hasNext() && result == 0) {
278 				int column = i.next().intValue();
279 				result = compareColumn(viewer, a, b, column);
280 			}
281 			return ascending ? result : (-1) * result;
282 		}
283 
compareColumn(final Viewer viewer, final Object a, final Object b, final int columnNumber)284 		private int compareColumn(final Viewer viewer, final Object a, final Object b, final int columnNumber) {
285 			if (columnNumber == USER_DELTA_COLUMN) {
286 				return sortUser(a, b);
287 			}
288 			IBaseLabelProvider baseLabel = ((TreeViewer) viewer).getLabelProvider();
289 			if (baseLabel instanceof ITableLabelProvider) {
290 				ITableLabelProvider tableProvider = (ITableLabelProvider) baseLabel;
291 				String e1p = tableProvider.getColumnText(a, columnNumber);
292 				String e2p = tableProvider.getColumnText(b, columnNumber);
293 				if (e1p != null && e2p != null) {
294 					return getComparator().compare(e1p, e2p);
295 				}
296 			}
297 			return 0;
298 		}
299 
sortUser(final Object a, final Object b)300 		private int sortUser(final Object a, final Object b) {
301 			int typeA = ((BindingElement) a).getUserDelta().intValue();
302 			int typeB = ((BindingElement) b).getUserDelta().intValue();
303 			return typeA - typeB;
304 		}
305 
306 	}
307 
308 	private final class ResortColumn extends SelectionAdapter {
309 		private final BindingModelComparator comparator;
310 		private final TreeColumn treeColumn;
311 		private final TreeViewer viewer;
312 		private final int column;
313 
ResortColumn(BindingModelComparator comparator, TreeColumn treeColumn, TreeViewer viewer, int column)314 		private ResortColumn(BindingModelComparator comparator, TreeColumn treeColumn, TreeViewer viewer, int column) {
315 			this.comparator = comparator;
316 			this.treeColumn = treeColumn;
317 			this.viewer = viewer;
318 			this.column = column;
319 		}
320 
321 		@Override
widgetSelected(SelectionEvent e)322 		public void widgetSelected(SelectionEvent e) {
323 			if (comparator.getSortColumn() == column) {
324 				comparator.setAscending(!comparator.isAscending());
325 				viewer.getTree().setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
326 			} else {
327 				viewer.getTree().setSortColumn(treeColumn);
328 				comparator.setSortColumn(column);
329 			}
330 			try {
331 				viewer.getTree().setRedraw(false);
332 				viewer.refresh();
333 			} finally {
334 				viewer.getTree().setRedraw(true);
335 			}
336 		}
337 	}
338 
339 	private static class ListLabelProvider extends LabelProvider {
340 		@Override
getText(Object element)341 		public String getText(Object element) {
342 			return ((ModelElement) element).getName();
343 		}
344 	}
345 
346 	private class BindingElementLabelProvider extends LabelProvider implements ITableLabelProvider {
347 		/**
348 		 * A resource manager for this preference page.
349 		 */
350 		private final LocalResourceManager localResourceManager = new LocalResourceManager(
351 				JFaceResources.getResources());
352 
353 		@Override
dispose()354 		public final void dispose() {
355 			super.dispose();
356 			localResourceManager.dispose();
357 		}
358 
359 		@Override
getText(Object element)360 		public String getText(Object element) {
361 			String rc = getColumnText(element, 0);
362 			if (rc == null) {
363 				rc = super.getText(element);
364 			}
365 			StringBuilder buf = new StringBuilder(rc);
366 			for (int i = 1; i < USER_DELTA_COLUMN; i++) {
367 				String text = getColumnText(element, i);
368 				if (text != null) {
369 					buf.append(' ');
370 					buf.append(text);
371 				}
372 			}
373 			return buf.toString();
374 		}
375 
376 		@Override
getColumnText(Object element, int index)377 		public String getColumnText(Object element, int index) {
378 			BindingElement bindingElement = ((BindingElement) element);
379 			switch (index) {
380 			case COMMAND_NAME_COLUMN: // name
381 				return LegacyActionTools.removeMnemonics(bindingElement.getName());
382 			case KEY_SEQUENCE_COLUMN: // keys
383 				TriggerSequence seq = bindingElement.getTrigger();
384 				return seq == null ? Util.ZERO_LENGTH_STRING : seq.format();
385 			case CONTEXT_COLUMN: // when
386 				ModelElement context = bindingElement.getContext();
387 				return context == null ? Util.ZERO_LENGTH_STRING : context.getName();
388 			case CATEGORY_COLUMN: // category
389 				return bindingElement.getCategory();
390 			case USER_DELTA_COLUMN: // user
391 				if (bindingElement.getUserDelta().intValue() == Binding.USER) {
392 					if (bindingElement.getConflict().equals(Boolean.TRUE)) {
393 						return "CU"; //$NON-NLS-1$
394 					}
395 					return " U"; //$NON-NLS-1$
396 				}
397 				if (bindingElement.getConflict().equals(Boolean.TRUE)) {
398 					return "C "; //$NON-NLS-1$
399 				}
400 				return "  "; //$NON-NLS-1$
401 			}
402 			return null;
403 		}
404 
405 		@Override
getColumnImage(Object element, int index)406 		public Image getColumnImage(Object element, int index) {
407 			BindingElement be = (BindingElement) element;
408 			switch (index) {
409 			case COMMAND_NAME_COLUMN:
410 				final String commandId = be.getId();
411 				final ImageDescriptor imageDescriptor = commandImageService.getImageDescriptor(commandId);
412 				if (imageDescriptor == null) {
413 					return null;
414 				}
415 				try {
416 					return localResourceManager.createImage(imageDescriptor);
417 				} catch (final DeviceResourceException e) {
418 					final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
419 							+ commandId + '\'';
420 					final IStatus status = new Status(IStatus.ERROR, WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
421 					WorkbenchPlugin.log(message, status);
422 				}
423 				return null;
424 
425 			}
426 
427 			return null;
428 		}
429 	}
430 
431 	class ModelContentProvider implements ITreeContentProvider {
432 		@Override
getChildren(Object parentElement)433 		public Object[] getChildren(Object parentElement) {
434 			if (parentElement instanceof BindingModel) {
435 				return ((BindingModel) parentElement).getBindings().toArray();
436 			}
437 			if (parentElement instanceof ContextModel) {
438 				return ((ContextModel) parentElement).getContexts().toArray();
439 			}
440 			if (parentElement instanceof SchemeModel) {
441 				return ((SchemeModel) parentElement).getSchemes().toArray();
442 			}
443 			return new Object[0];
444 		}
445 
446 		@Override
getParent(Object element)447 		public Object getParent(Object element) {
448 			return ((ModelElement) element).getParent();
449 		}
450 
451 		@Override
hasChildren(Object element)452 		public boolean hasChildren(Object element) {
453 			return (element instanceof BindingModel) || (element instanceof ContextModel)
454 					|| (element instanceof SchemeModel);
455 		}
456 
457 		@Override
getElements(Object inputElement)458 		public Object[] getElements(Object inputElement) {
459 			return getChildren(inputElement);
460 		}
461 
462 	}
463 
464 	@Override
createContents(Composite parent)465 	protected Control createContents(Composite parent) {
466 		PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IWorkbenchHelpContextIds.KEYS_PREFERENCE_PAGE);
467 		final Composite page = new Composite(parent, SWT.NONE);
468 		GridLayout layout = new GridLayout(1, false);
469 		layout.marginWidth = 0;
470 		page.setLayout(layout);
471 
472 		IDialogSettings settings = getDialogSettings();
473 
474 		fPatternFilter = new CategoryPatternFilter(true, commandService.getCategory(null));
475 		if (settings.get(TAG_FILTER_UNCAT) != null) {
476 			fPatternFilter.filterCategories(settings.getBoolean(TAG_FILTER_UNCAT));
477 		}
478 
479 		createSchemeControls(page);
480 		createTree(page);
481 		createTreeControls(page);
482 		createDataControls(page);
483 		createShowKeysControls(page);
484 
485 		fill();
486 
487 		applyDialogFont(page);
488 
489 		// we want the description text control to span four lines, but because
490 		// we need the dialog's font for this information, we have to set it here
491 		// after the dialog font has been applied
492 		GC gc = new GC(fDescriptionText);
493 		gc.setFont(fDescriptionText.getFont());
494 		FontMetrics metrics = gc.getFontMetrics();
495 		gc.dispose();
496 		int height = metrics.getHeight() * 5 / 2;
497 
498 		GridData gridData = new GridData();
499 		gridData.grabExcessHorizontalSpace = true;
500 		gridData.horizontalAlignment = SWT.FILL;
501 		gridData.horizontalSpan = 2;
502 		gridData.heightHint = height;
503 		fDescriptionText.setLayoutData(gridData);
504 
505 		return page;
506 	}
507 
508 	/**
509 	 * Creates the button bar with "Filters..." and "Export CVS..." buttons.
510 	 *
511 	 * @param parent The composite in which the button bar should be placed; never
512 	 *               <code>null</code>.
513 	 * @return The button bar composite; never <code>null</code>.
514 	 */
createButtonBar(final Composite parent)515 	private final Control createButtonBar(final Composite parent) {
516 		GridLayout layout;
517 		GridData gridData;
518 		int widthHint;
519 
520 		// Create the composite to house the button bar.
521 		final Composite buttonBar = new Composite(parent, SWT.NONE);
522 		layout = new GridLayout(2, false);
523 		layout.marginWidth = 0;
524 		buttonBar.setLayout(layout);
525 		gridData = new GridData();
526 		gridData.horizontalAlignment = SWT.END;
527 		gridData.grabExcessHorizontalSpace = true;
528 		buttonBar.setLayoutData(gridData);
529 
530 		// Advanced button.
531 		final Button filtersButton = new Button(buttonBar, SWT.PUSH);
532 		gridData = new GridData();
533 		widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
534 		filtersButton.setText(NewKeysPreferenceMessages.FiltersButton_Text);
535 		gridData.widthHint = Math.max(widthHint, filtersButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
536 		filtersButton.setLayoutData(gridData);
537 		filtersButton.addSelectionListener(widgetSelectedAdapter(e -> {
538 			KeysPreferenceFiltersDialog dialog = new KeysPreferenceFiltersDialog(getShell());
539 			dialog.setFilterActionSet(fFilterActionSetContexts);
540 			dialog.setFilterInternal(fFilterInternalContexts);
541 			dialog.setFilterUncategorized(fFilteredTree.isFilteringCategories());
542 			if (dialog.open() == Window.OK) {
543 				fFilterActionSetContexts = dialog.getFilterActionSet();
544 				fFilterInternalContexts = dialog.getFilterInternal();
545 				fFilteredTree.filterCategories(dialog.getFilterUncategorized());
546 
547 				// Apply context filters
548 				keyController.filterContexts(fFilterActionSetContexts, fFilterInternalContexts);
549 
550 				ISelection currentContextSelection = fWhenCombo.getSelection();
551 				fWhenCombo.setInput(keyController.getContextModel());
552 				fWhenCombo.setSelection(currentContextSelection);
553 			}
554 		}));
555 
556 		// Export bindings to CSV
557 		final Button exportButton = new Button(buttonBar, SWT.PUSH);
558 		// gridData = new GridData();
559 		widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
560 		exportButton.setText(NewKeysPreferenceMessages.ExportButton_Text);
561 		gridData.widthHint = Math.max(widthHint, exportButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
562 		exportButton.setLayoutData(gridData);
563 		exportButton.addSelectionListener(
564 				widgetSelectedAdapter(e -> keyController.exportCSV(((Button) e.getSource()).getShell())));
565 
566 		return buttonBar;
567 	}
568 
createDataControls(final Composite parent)569 	private final void createDataControls(final Composite parent) {
570 		GridLayout layout;
571 		GridData gridData;
572 
573 		// Creates the data area.
574 		final Composite dataArea = new Composite(parent, SWT.NONE);
575 		layout = new GridLayout(2, true);
576 		layout.marginWidth = 0;
577 		dataArea.setLayout(layout);
578 		gridData = new GridData();
579 		gridData.grabExcessHorizontalSpace = true;
580 		gridData.horizontalAlignment = SWT.FILL;
581 		dataArea.setLayoutData(gridData);
582 
583 		// LEFT DATA AREA
584 		// Creates the left data area.
585 		final Composite leftDataArea = new Composite(dataArea, SWT.NONE);
586 		layout = new GridLayout(3, false);
587 		leftDataArea.setLayout(layout);
588 		gridData = new GridData();
589 		gridData.grabExcessHorizontalSpace = true;
590 		gridData.verticalAlignment = SWT.TOP;
591 		gridData.horizontalAlignment = SWT.FILL;
592 		leftDataArea.setLayoutData(gridData);
593 
594 		// The command name label.
595 		final Label commandNameLabel = new Label(leftDataArea, SWT.NONE);
596 		commandNameLabel.setText(NewKeysPreferenceMessages.CommandNameLabel_Text);
597 
598 		// The current command name.
599 		commandNameValueLabel = new Label(leftDataArea, SWT.NONE);
600 		gridData = new GridData();
601 		gridData.grabExcessHorizontalSpace = true;
602 		gridData.horizontalSpan = 2;
603 		gridData.horizontalAlignment = SWT.FILL;
604 		commandNameValueLabel.setLayoutData(gridData);
605 
606 		final Label commandDescriptionlabel = new Label(leftDataArea, SWT.LEAD);
607 		commandDescriptionlabel.setText(NewKeysPreferenceMessages.CommandDescriptionLabel_Text);
608 		gridData = new GridData();
609 		gridData.verticalAlignment = SWT.BEGINNING;
610 		commandDescriptionlabel.setLayoutData(gridData);
611 
612 		fDescriptionText = new Text(leftDataArea, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.READ_ONLY);
613 
614 		// The binding label.
615 		final Label bindingLabel = new Label(leftDataArea, SWT.NONE);
616 		bindingLabel.setText(NewKeysPreferenceMessages.BindingLabel_Text);
617 
618 		// The key sequence entry widget.
619 		fBindingText = new Text(leftDataArea, SWT.BORDER);
620 		gridData = new GridData();
621 		gridData.grabExcessHorizontalSpace = true;
622 		gridData.horizontalAlignment = SWT.FILL;
623 		gridData.widthHint = 200;
624 		fBindingText.setLayoutData(gridData);
625 
626 		fBindingText.addFocusListener(new FocusListener() {
627 			@Override
628 			public void focusGained(FocusEvent e) {
629 				fBindingService.setKeyFilterEnabled(false);
630 			}
631 
632 			@Override
633 			public void focusLost(FocusEvent e) {
634 				fBindingService.setKeyFilterEnabled(true);
635 			}
636 		});
637 		fBindingText.addDisposeListener(e -> {
638 			if (!fBindingService.isKeyFilterEnabled()) {
639 				fBindingService.setKeyFilterEnabled(true);
640 			}
641 		});
642 
643 		fKeySequenceText = new KeySequenceText(fBindingText);
644 		fKeySequenceText.setKeyStrokeLimit(4);
645 		fKeySequenceText.addPropertyChangeListener(event -> {
646 			if (!event.getOldValue().equals(event.getNewValue())) {
647 				final KeySequence keySequence = fKeySequenceText.getKeySequence();
648 				if (!keySequence.isComplete()) {
649 					return;
650 				}
651 
652 				BindingElement activeBinding = (BindingElement) keyController.getBindingModel().getSelectedElement();
653 				if (activeBinding != null) {
654 					activeBinding.setTrigger(keySequence);
655 				}
656 				fBindingText.setSelection(fBindingText.getTextLimit());
657 			}
658 		});
659 
660 		// Button for adding trapped key strokes
661 		final Button addKeyButton = new Button(leftDataArea, SWT.LEFT | SWT.ARROW);
662 		addKeyButton.setToolTipText(NewKeysPreferenceMessages.AddKeyButton_ToolTipText);
663 		gridData = new GridData();
664 		gridData.heightHint = fSchemeCombo.getCombo().getTextHeight();
665 		addKeyButton.setLayoutData(gridData);
666 
667 		// Arrow buttons aren't normally added to the tab list. Let's fix that.
668 		final Control[] tabStops = dataArea.getTabList();
669 		final ArrayList<Control> newTabStops = new ArrayList<>();
670 		for (Control tabStop : tabStops) {
671 			newTabStops.add(tabStop);
672 			if (fBindingText.equals(tabStop)) {
673 				newTabStops.add(addKeyButton);
674 			}
675 		}
676 		final Control[] newTabStopArray = newTabStops.toArray(new Control[newTabStops.size()]);
677 		dataArea.setTabList(newTabStopArray);
678 
679 		// Construct the menu to attach to the above button.
680 		final Menu addKeyMenu = new Menu(addKeyButton);
681 		final Iterator<KeyStroke> trappedKeyItr = KeySequenceText.TRAPPED_KEYS.iterator();
682 		while (trappedKeyItr.hasNext()) {
683 			final KeyStroke trappedKey = trappedKeyItr.next();
684 			final MenuItem menuItem = new MenuItem(addKeyMenu, SWT.PUSH);
685 			menuItem.setText(trappedKey.format());
686 			menuItem.addSelectionListener(widgetSelectedAdapter(e -> {
687 				fKeySequenceText.insert(trappedKey);
688 				fBindingText.setFocus();
689 				fBindingText.setSelection(fBindingText.getTextLimit());
690 			}));
691 		}
692 		addKeyButton.addSelectionListener(widgetSelectedAdapter(selectionEvent -> {
693 			Point buttonLocation = addKeyButton.getLocation();
694 			buttonLocation = dataArea.toDisplay(buttonLocation.x, buttonLocation.y);
695 			Point buttonSize = addKeyButton.getSize();
696 			addKeyMenu.setLocation(buttonLocation.x, buttonLocation.y + buttonSize.y);
697 			addKeyMenu.setVisible(true);
698 		}));
699 
700 		// The when label.
701 		final Label whenLabel = new Label(leftDataArea, SWT.NONE);
702 		whenLabel.setText(NewKeysPreferenceMessages.WhenLabel_Text);
703 
704 		// The when combo.
705 		fWhenCombo = new ComboViewer(leftDataArea);
706 		gridData = new GridData();
707 		gridData.grabExcessHorizontalSpace = true;
708 		gridData.horizontalAlignment = SWT.FILL;
709 		gridData.horizontalSpan = 2;
710 		gridData.widthHint = 200;
711 		ViewerComparator comparator = new ViewerComparator();
712 		fWhenCombo.setComparator(comparator);
713 		fWhenCombo.getCombo().setLayoutData(gridData);
714 		fWhenCombo.setContentProvider(new ModelContentProvider());
715 		fWhenCombo.setLabelProvider(new ListLabelProvider());
716 		fWhenCombo.addSelectionChangedListener(event -> {
717 			ContextElement context = (ContextElement) event.getStructuredSelection().getFirstElement();
718 			if (context != null) {
719 				keyController.getContextModel().setSelectedElement(context);
720 			}
721 		});
722 		IPropertyChangeListener whenListener = event -> {
723 			if (event.getSource() == keyController.getContextModel()
724 					&& CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
725 				Object newVal = event.getNewValue();
726 				StructuredSelection structuredSelection = newVal == null ? null : new StructuredSelection(newVal);
727 				fWhenCombo.setSelection(structuredSelection, true);
728 			}
729 		};
730 		keyController.addPropertyChangeListener(whenListener);
731 
732 		// RIGHT DATA AREA
733 		// Creates the right data area.
734 		final Composite rightDataArea = new Composite(dataArea, SWT.NONE);
735 		layout = new GridLayout(1, false);
736 		rightDataArea.setLayout(layout);
737 		gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
738 		rightDataArea.setLayoutData(gridData);
739 
740 		new Label(rightDataArea, SWT.NONE); // filler
741 
742 		// The description label.
743 		final Label descriptionLabel = new Label(rightDataArea, SWT.NONE);
744 		descriptionLabel.setText(NewKeysPreferenceMessages.ConflictsLabel_Text);
745 		gridData = new GridData();
746 		gridData.grabExcessHorizontalSpace = true;
747 		gridData.horizontalAlignment = SWT.FILL;
748 		descriptionLabel.setLayoutData(gridData);
749 
750 		conflictViewer = new TableViewer(rightDataArea, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
751 		Table table = conflictViewer.getTable();
752 		table.setHeaderVisible(true);
753 		TableColumn bindingNameColumn = new TableColumn(table, SWT.LEAD);
754 		bindingNameColumn.setText(NewKeysPreferenceMessages.CommandNameColumn_Text);
755 		bindingNameColumn.setWidth(150);
756 		TableColumn bindingContextNameColumn = new TableColumn(table, SWT.LEAD);
757 		bindingContextNameColumn.setText(NewKeysPreferenceMessages.WhenColumn_Text);
758 		bindingContextNameColumn.setWidth(150);
759 		gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
760 		// gridData.horizontalIndent = 10;
761 		table.setLayoutData(gridData);
762 		TableLayout tableLayout = new TableLayout();
763 		tableLayout.addColumnData(new ColumnWeightData(60));
764 		tableLayout.addColumnData(new ColumnWeightData(40));
765 		table.setLayout(tableLayout);
766 		conflictViewer.setContentProvider((IStructuredContentProvider) inputElement -> {
767 			if (inputElement instanceof Collection<?>) {
768 				return ((Collection<?>) inputElement).toArray(new Object[0]);
769 			}
770 			return new Object[0];
771 		});
772 		conflictViewer.setLabelProvider(new BindingElementLabelProvider() {
773 			@Override
774 			public String getColumnText(Object o, int index) {
775 				BindingElement element = (BindingElement) o;
776 				if (index == 0) {
777 					return element.getName();
778 				}
779 				return element.getContext().getName();
780 			}
781 		});
782 		conflictViewer.addSelectionChangedListener(event -> {
783 			ModelElement binding = (ModelElement) event.getStructuredSelection().getFirstElement();
784 			BindingModel bindingModel = keyController.getBindingModel();
785 			if (binding != null && binding != bindingModel.getSelectedElement()) {
786 				StructuredSelection selection = new StructuredSelection(binding);
787 
788 				bindingModel.setSelectedElement(binding);
789 				conflictViewer.setSelection(selection);
790 
791 				boolean selectionVisible = false;
792 				TreeItem[] items = fFilteredTree.getViewer().getTree().getItems();
793 				for (TreeItem item : items) {
794 					if (item.getData().equals(binding)) {
795 						selectionVisible = true;
796 						break;
797 					}
798 				}
799 
800 				if (!selectionVisible) {
801 					fFilteredTree.getFilterControl().setText(""); //$NON-NLS-1$
802 					fFilteredTree.getViewer().refresh();
803 					bindingModel.setSelectedElement(binding);
804 					conflictViewer.setSelection(selection);
805 				}
806 			}
807 		});
808 
809 		IPropertyChangeListener conflictsListener = event -> {
810 			if (event.getSource() == keyController.getConflictModel()
811 					&& CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
812 				if (keyController.getConflictModel().getConflicts() != null) {
813 					Object newVal = event.getNewValue();
814 					StructuredSelection structuredSelection = newVal == null ? null : new StructuredSelection(newVal);
815 					conflictViewer.setSelection(structuredSelection, true);
816 				}
817 			} else if (ConflictModel.PROP_CONFLICTS.equals(event.getProperty())) {
818 				conflictViewer.setInput(event.getNewValue());
819 			} else if (ConflictModel.PROP_CONFLICTS_ADD.equals(event.getProperty())) {
820 				conflictViewer.add(event.getNewValue());
821 			} else if (ConflictModel.PROP_CONFLICTS_REMOVE.equals(event.getProperty())) {
822 				conflictViewer.remove(event.getNewValue());
823 			}
824 		};
825 		keyController.addPropertyChangeListener(conflictsListener);
826 
827 		IPropertyChangeListener dataUpdateListener = event -> {
828 			BindingElement bindingElement = null;
829 			boolean weCare = false;
830 			if (event.getSource() == keyController.getBindingModel()
831 					&& CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
832 				bindingElement = (BindingElement) event.getNewValue();
833 				weCare = true;
834 			} else if (event.getSource() == keyController.getBindingModel().getSelectedElement()
835 					&& ModelElement.PROP_MODEL_OBJECT.equals(event.getProperty())) {
836 				bindingElement = (BindingElement) event.getSource();
837 				weCare = true;
838 			}
839 			if (bindingElement == null && weCare) {
840 				commandNameValueLabel.setText(""); //$NON-NLS-1$
841 				fDescriptionText.setText(""); //$NON-NLS-1$
842 				fBindingText.setText(""); //$NON-NLS-1$
843 			} else if (bindingElement != null) {
844 				commandNameValueLabel.setText(bindingElement.getName());
845 				String desc = bindingElement.getDescription();
846 				fDescriptionText.setText(desc == null ? "" : desc); //$NON-NLS-1$
847 				KeySequence trigger = (KeySequence) bindingElement.getTrigger();
848 				fKeySequenceText.setKeySequence(trigger);
849 			}
850 		};
851 		keyController.addPropertyChangeListener(dataUpdateListener);
852 
853 	}
854 
createTree(Composite parent)855 	private void createTree(Composite parent) {
856 		fPatternFilter = new CategoryPatternFilter(true, fDefaultCategory);
857 		fPatternFilter.filterCategories(true);
858 
859 		GridData gridData;
860 
861 		fFilteredTree = new CategoryFilterTree(parent, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER, fPatternFilter);
862 		final GridLayout layout = new GridLayout(1, false);
863 		layout.marginWidth = 0;
864 		fFilteredTree.setLayout(layout);
865 		gridData = new GridData();
866 		gridData.grabExcessHorizontalSpace = true;
867 		gridData.grabExcessVerticalSpace = true;
868 		gridData.horizontalAlignment = SWT.FILL;
869 		gridData.verticalAlignment = SWT.FILL;
870 		fFilteredTree.setLayoutData(gridData);
871 
872 		final TreeViewer viewer = fFilteredTree.getViewer();
873 		// Make sure the filtered tree has a height of ITEMS_TO_SHOW
874 		final Tree tree = viewer.getTree();
875 		tree.setHeaderVisible(true);
876 		final Object layoutData = tree.getLayoutData();
877 		if (layoutData instanceof GridData) {
878 			gridData = (GridData) layoutData;
879 			final int itemHeight = tree.getItemHeight();
880 			if (itemHeight > 1) {
881 				gridData.heightHint = ITEMS_TO_SHOW * itemHeight;
882 			}
883 		}
884 
885 		BindingModelComparator comparator = new BindingModelComparator();
886 		viewer.setComparator(comparator);
887 
888 		final TreeColumn commandNameColumn = new TreeColumn(tree, SWT.LEFT, COMMAND_NAME_COLUMN);
889 		commandNameColumn.setText(NewKeysPreferenceMessages.CommandNameColumn_Text);
890 		tree.setSortColumn(commandNameColumn);
891 		tree.setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
892 		commandNameColumn
893 				.addSelectionListener(new ResortColumn(comparator, commandNameColumn, viewer, COMMAND_NAME_COLUMN));
894 
895 		final TreeColumn triggerSequenceColumn = new TreeColumn(tree, SWT.LEFT, KEY_SEQUENCE_COLUMN);
896 		triggerSequenceColumn.setText(NewKeysPreferenceMessages.TriggerSequenceColumn_Text);
897 		triggerSequenceColumn
898 				.addSelectionListener(new ResortColumn(comparator, triggerSequenceColumn, viewer, KEY_SEQUENCE_COLUMN));
899 
900 		final TreeColumn whenColumn = new TreeColumn(tree, SWT.LEFT, CONTEXT_COLUMN);
901 		whenColumn.setText(NewKeysPreferenceMessages.WhenColumn_Text);
902 		whenColumn.addSelectionListener(new ResortColumn(comparator, whenColumn, viewer, CONTEXT_COLUMN));
903 
904 		final TreeColumn categoryColumn = new TreeColumn(tree, SWT.LEFT, CATEGORY_COLUMN);
905 		categoryColumn.setText(NewKeysPreferenceMessages.CategoryColumn_Text);
906 		categoryColumn.addSelectionListener(new ResortColumn(comparator, categoryColumn, viewer, CATEGORY_COLUMN));
907 
908 		final TreeColumn userMarker = new TreeColumn(tree, SWT.LEFT, USER_DELTA_COLUMN);
909 		userMarker.setText(NewKeysPreferenceMessages.UserColumn_Text);
910 		userMarker.addSelectionListener(new ResortColumn(comparator, userMarker, viewer, USER_DELTA_COLUMN));
911 
912 		viewer.setContentProvider(new ModelContentProvider());
913 		viewer.setLabelProvider(new BindingElementLabelProvider());
914 
915 		fFilteredTree.getPatternFilter().setIncludeLeadingWildcard(true);
916 		final TreeColumn[] columns = viewer.getTree().getColumns();
917 
918 		columns[COMMAND_NAME_COLUMN].setWidth(240);
919 		columns[KEY_SEQUENCE_COLUMN].setWidth(130);
920 		columns[CONTEXT_COLUMN].setWidth(130);
921 		columns[CATEGORY_COLUMN].setWidth(130);
922 		columns[USER_DELTA_COLUMN].setWidth(50);
923 
924 		viewer.addSelectionChangedListener(event -> {
925 			ModelElement binding = (ModelElement) event.getStructuredSelection().getFirstElement();
926 			keyController.getBindingModel().setSelectedElement(binding);
927 		});
928 
929 		IPropertyChangeListener treeUpdateListener = event -> {
930 			if (event.getSource() == keyController.getBindingModel()
931 					&& CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
932 				Object newVal = event.getNewValue();
933 				StructuredSelection structuredSelection = newVal == null ? null : new StructuredSelection(newVal);
934 				viewer.setSelection(structuredSelection, true);
935 			} else if (event.getSource() instanceof BindingElement
936 					&& ModelElement.PROP_MODEL_OBJECT.equals(event.getProperty())) {
937 				viewer.update(event.getSource(), null);
938 			} else if (BindingElement.PROP_CONFLICT.equals(event.getProperty())) {
939 				viewer.update(event.getSource(), null);
940 			} else if (BindingModel.PROP_BINDINGS.equals(event.getProperty())) {
941 				viewer.refresh();
942 			} else if (BindingModel.PROP_BINDING_ADD.equals(event.getProperty())) {
943 				viewer.add(keyController.getBindingModel(), event.getNewValue());
944 			} else if (BindingModel.PROP_BINDING_REMOVE.equals(event.getProperty())) {
945 				viewer.remove(event.getNewValue());
946 			} else if (BindingModel.PROP_BINDING_FILTER.equals(event.getProperty())) {
947 				viewer.refresh();
948 			}
949 		};
950 		keyController.addPropertyChangeListener(treeUpdateListener);
951 		// as far as I got
952 	}
953 
createTreeControls(final Composite parent)954 	private final Control createTreeControls(final Composite parent) {
955 		GridLayout layout;
956 		GridData gridData;
957 		int widthHint;
958 
959 		// Creates controls related to the tree.
960 		final Composite treeControls = new Composite(parent, SWT.NONE);
961 		layout = new GridLayout(4, false);
962 		layout.marginWidth = 0;
963 		treeControls.setLayout(layout);
964 		gridData = new GridData();
965 		gridData.grabExcessHorizontalSpace = true;
966 		gridData.horizontalAlignment = SWT.FILL;
967 		treeControls.setLayoutData(gridData);
968 
969 		final Button addBindingButton = new Button(treeControls, SWT.PUSH);
970 		gridData = new GridData();
971 		widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
972 		addBindingButton.setText(NewKeysPreferenceMessages.AddBindingButton_Text);
973 		gridData.widthHint = Math.max(widthHint, addBindingButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
974 		addBindingButton.setLayoutData(gridData);
975 		addBindingButton.addSelectionListener(widgetSelectedAdapter(event -> keyController.getBindingModel().copy()));
976 
977 		final Button removeBindingButton = new Button(treeControls, SWT.PUSH);
978 		gridData = new GridData();
979 		widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
980 		removeBindingButton.setText(NewKeysPreferenceMessages.RemoveBindingButton_Text);
981 		gridData.widthHint = Math.max(widthHint, removeBindingButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
982 		removeBindingButton.setLayoutData(gridData);
983 		removeBindingButton
984 				.addSelectionListener(widgetSelectedAdapter(event -> keyController.getBindingModel().remove()));
985 
986 		final Button restore = new Button(treeControls, SWT.PUSH);
987 		gridData = new GridData();
988 		widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
989 		restore.setText(NewKeysPreferenceMessages.RestoreBindingButton_Text);
990 		gridData.widthHint = Math.max(widthHint, restore.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
991 		restore.setLayoutData(gridData);
992 		restore.addSelectionListener(widgetSelectedAdapter(event -> {
993 			try {
994 				fFilteredTree.setRedraw(false);
995 				BindingModel bindingModel = keyController.getBindingModel();
996 				bindingModel.restoreBinding(keyController.getContextModel());
997 			} finally {
998 				fFilteredTree.setRedraw(true);
999 			}
1000 		}));
1001 
1002 		createButtonBar(treeControls);
1003 		return treeControls;
1004 	}
1005 
1006 	/**
1007 	 *
1008 	 */
fill()1009 	private void fill() {
1010 		fSchemeCombo.setInput(keyController.getSchemeModel());
1011 		fSchemeCombo.setSelection(new StructuredSelection(keyController.getSchemeModel().getSelectedElement()));
1012 
1013 		// Apply context filters
1014 		keyController.filterContexts(fFilterActionSetContexts, fFilterInternalContexts);
1015 		fWhenCombo.setInput(keyController.getContextModel());
1016 
1017 		fFilteredTree.filterCategories(fPatternFilter.isFilteringCategories());
1018 		fFilteredTree.getViewer().setInput(keyController.getBindingModel());
1019 	}
1020 
createSchemeControls(Composite parent)1021 	private void createSchemeControls(Composite parent) {
1022 		final Composite schemeControls = new Composite(parent, SWT.NONE);
1023 		GridLayout layout = new GridLayout(3, false);
1024 		layout.marginWidth = 0;
1025 		schemeControls.setLayout(layout);
1026 
1027 		final Label schemeLabel = new Label(schemeControls, SWT.NONE);
1028 		schemeLabel.setText(NewKeysPreferenceMessages.SchemeLabel_Text);
1029 
1030 		fSchemeCombo = new ComboViewer(schemeControls);
1031 		fSchemeCombo.setLabelProvider(new ListLabelProvider());
1032 		fSchemeCombo.setContentProvider(new ModelContentProvider());
1033 		GridData gridData = new GridData();
1034 		gridData.widthHint = 150;
1035 		gridData.horizontalAlignment = SWT.FILL;
1036 		fSchemeCombo.getCombo().setLayoutData(gridData);
1037 		fSchemeCombo.addSelectionChangedListener(
1038 				event -> BusyIndicator.showWhile(fFilteredTree.getViewer().getTree().getDisplay(), () -> {
1039 					SchemeElement scheme = (SchemeElement) event.getStructuredSelection().getFirstElement();
1040 					keyController.getSchemeModel().setSelectedElement(scheme);
1041 				}));
1042 		IPropertyChangeListener listener = event -> {
1043 			if (event.getSource() == keyController.getSchemeModel()
1044 					&& CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
1045 				Object newVal = event.getNewValue();
1046 				StructuredSelection structuredSelection = newVal == null ? null : new StructuredSelection(newVal);
1047 				fSchemeCombo.setSelection(structuredSelection, true);
1048 			}
1049 		};
1050 
1051 		keyController.addPropertyChangeListener(listener);
1052 	}
1053 
createShowKeysControls(Composite parent)1054 	private void createShowKeysControls(Composite parent) {
1055 		ShowKeysUI showKeysUI = new ShowKeysUI(fWorkbench, getPreferenceStore());
1056 
1057 		final Group group = new Group(parent, SWT.NONE);
1058 		group.setText(NewKeysPreferenceMessages.ShowCommandKeysGroup_Title);
1059 		group.setLayout(new GridLayout(1, false));
1060 		group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
1061 
1062 		fShowCommandKey = new Button(group, SWT.CHECK);
1063 		fShowCommandKey.setText(NewKeysPreferenceMessages.ShowCommandKeysForKeyboard_Text);
1064 		fShowCommandKey.setSelection(getPreferenceStore().getBoolean(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_KEYBOARD));
1065 		fShowCommandKey.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
1066 			// show a preview of the shortcut popup
1067 			if (fShowCommandKey.getSelection()) {
1068 				showKeysUI.openForPreview(ShowKeysToggleHandler.COMMAND_ID, null);
1069 			}
1070 		}));
1071 		fShowCommandKeyForMouseEvents = new Button(group, SWT.CHECK);
1072 		fShowCommandKeyForMouseEvents.setText(NewKeysPreferenceMessages.ShowCommandKeysForMouse_Text);
1073 		fShowCommandKeyForMouseEvents
1074 				.setSelection(getPreferenceStore().getBoolean(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_MOUSE_EVENTS));
1075 		fShowCommandKeyForMouseEvents.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
1076 			// show a preview of the shortcut popup
1077 			if (fShowCommandKeyForMouseEvents.getSelection()) {
1078 				showKeysUI.openForPreview(ShowKeysToggleHandler.COMMAND_ID, null);
1079 			}
1080 		}));
1081 	}
1082 
1083 	@Override
init(IWorkbench workbench)1084 	public void init(IWorkbench workbench) {
1085 		fWorkbench = workbench;
1086 		keyController = new KeyController();
1087 		keyController.init(workbench);
1088 
1089 		commandService = workbench.getService(ICommandService.class);
1090 		fDefaultCategory = commandService.getCategory(null);
1091 		fBindingService = workbench.getService(IBindingService.class);
1092 
1093 		commandImageService = workbench.getService(ICommandImageService.class);
1094 	}
1095 
1096 	@Override
applyData(Object data)1097 	public void applyData(Object data) {
1098 		if (data instanceof ModelElement) {
1099 			keyController.getBindingModel().setSelectedElement((ModelElement) data);
1100 		}
1101 		if (data instanceof Binding && fFilteredTree != null) {
1102 			BindingElement be = keyController.getBindingModel().getBindingToElement().get(data);
1103 			fFilteredTree.getViewer().setSelection(new StructuredSelection(be), true);
1104 		}
1105 		if (data instanceof ParameterizedCommand) {
1106 			Map<ParameterizedCommand, BindingElement> commandToElement = keyController.getBindingModel().getCommandToElement();
1107 
1108 			BindingElement be = commandToElement.get(data);
1109 			if (be != null) {
1110 				fFilteredTree.getViewer().setSelection(new StructuredSelection(be), true);
1111 			}
1112 		}
1113 	}
1114 
1115 	@Override
performOk()1116 	public boolean performOk() {
1117 		keyController.saveBindings(fBindingService);
1118 		IPreferenceStore preferenceStore = getPreferenceStore();
1119 		preferenceStore.setValue(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_KEYBOARD, fShowCommandKey.getSelection());
1120 		preferenceStore.setValue(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_MOUSE_EVENTS,
1121 				fShowCommandKeyForMouseEvents.getSelection());
1122 
1123 		saveState(getDialogSettings());
1124 		return super.performOk();
1125 	}
1126 
1127 	@Override
doGetPreferenceStore()1128 	protected IPreferenceStore doGetPreferenceStore() {
1129 		return WorkbenchPlugin.getDefault().getPreferenceStore();
1130 	}
1131 
1132 	/**
1133 	 * Save the state of the receiver.
1134 	 *
1135 	 * @param dialogSettings
1136 	 */
saveState(IDialogSettings dialogSettings)1137 	public void saveState(IDialogSettings dialogSettings) {
1138 		if (dialogSettings == null) {
1139 			return;
1140 		}
1141 		dialogSettings.put(TAG_FILTER_ACTION_SETS, fFilterActionSetContexts);
1142 		dialogSettings.put(TAG_FILTER_INTERNAL, fFilterInternalContexts);
1143 		dialogSettings.put(TAG_FILTER_UNCAT, fFilteredTree.isFilteringCategories());
1144 	}
1145 
getDialogSettings()1146 	protected IDialogSettings getDialogSettings() {
1147 		IDialogSettings workbenchSettings = WorkbenchPlugin.getDefault().getDialogSettings();
1148 
1149 		IDialogSettings settings = workbenchSettings.getSection(TAG_DIALOG_SECTION);
1150 
1151 		if (settings == null) {
1152 			settings = workbenchSettings.addNewSection(TAG_DIALOG_SECTION);
1153 		}
1154 		return settings;
1155 	}
1156 
1157 	@Override
performDefaults()1158 	protected final void performDefaults() {
1159 
1160 		// Ask the user to confirm
1161 		final String title = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxText;
1162 		final String message = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxMessage;
1163 		final boolean confirmed = MessageDialog.open(MessageDialog.CONFIRM, getShell(), title, message, SWT.SHEET);
1164 
1165 		if (confirmed) {
1166 			long startTime = 0L;
1167 			if (DEBUG) {
1168 				startTime = System.currentTimeMillis();
1169 			}
1170 
1171 			fFilteredTree.setRedraw(false);
1172 			BusyIndicator.showWhile(fFilteredTree.getViewer().getTree().getDisplay(),
1173 					() -> keyController.setDefaultBindings(fBindingService));
1174 			fFilteredTree.setRedraw(true);
1175 			if (DEBUG) {
1176 				final long elapsedTime = System.currentTimeMillis() - startTime;
1177 				Tracing.printTrace(TRACING_COMPONENT, "performDefaults:model in " + elapsedTime + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
1178 
1179 			}
1180 
1181 			fShowCommandKey
1182 					.setSelection(getPreferenceStore().getDefaultBoolean(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_KEYBOARD));
1183 			fShowCommandKeyForMouseEvents.setSelection(
1184 					getPreferenceStore().getDefaultBoolean(IPreferenceConstants.SHOW_KEYS_ENABLED_FOR_MOUSE_EVENTS));
1185 		}
1186 
1187 		super.performDefaults();
1188 	}
1189 }
1190