blob: 5e0b9513d006f8e4e58d13dc73b5e7d614251d69 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.keys;
import java.io.IOException;
import java.net.URL;
import com.ibm.icu.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.Category;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NamedHandleObject;
import org.eclipse.core.commands.common.NamedHandleObjectComparator;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.contexts.Context;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
import org.eclipse.jface.bindings.keys.KeyBinding;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeySequenceText;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.contexts.IContextIds;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.resource.DeviceResourceException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.NamedHandleObjectLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeNode;
import org.eclipse.jface.viewers.TreeNodeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.commands.ICommandImageService;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.IBindingService;
/**
* <p>
* A preference page that is capable of displaying and editing the bindings
* between commands and user input events. These are typically things like
* keyboard shortcuts.
* </p>
* <p>
* This preference page has four general types of methods. Create methods are
* called when the page is first made visisble. They are responsible for
* creating all of the widgets, and laying them out within the preference page.
* Fill methods populate the contents of the widgets that contain collections of
* data from which items can be selected. The select methods respond to
* selection events from the user, such as a button press or a table selection.
* The update methods update the contents of various widgets based on the
* current state of the user interface. For example, the command name label will
* always try to match the current select in the binding table.
* </p>
*
* @since 3.2
*/
public final class NewKeysPreferencePage extends PreferencePage implements
IWorkbenchPreferencePage {
/**
* A FilteredTree that provides a combo which is used to organize and display
* elements in the tree according to the selected criteria.
*
*/
protected class GroupedFilteredTree extends FilteredTree {
/**
* The combo box containing all of the possible grouping options. This value
* may be <code>null</code> if there are no grouping controls, or if the
* controls have not yet been created.
*/
protected Combo groupingCombo;
/**
* Constructor for GroupedFilteredTree.
*
* @param parent
* @param treeStyle
* @param filter
*/
protected GroupedFilteredTree(Composite parent, int treeStyle, PatternFilter filter){
super(parent, treeStyle, filter);
}
protected void createControl(final Composite parent, final int treeStyle) {
GridData gridData;
GridLayout layout;
layout = new GridLayout();
// Why doesn't this seem to be working??
layout.marginHeight = 0;
layout.marginWidth = 0;
setLayout(layout);
setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
setFont(parent.getFont());
// Create the filter controls
filterComposite = new Composite(this, SWT.NONE);
GridLayout filterLayout = new GridLayout(3, false);
filterLayout.marginHeight = 0;
filterLayout.marginWidth = 0;
filterComposite.setLayout(filterLayout);
filterComposite.setFont(parent.getFont());
createFilterControls(filterComposite);
filterComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
// Create the grouping control
final Control groupingControl = createGroupingControl(filterComposite);
groupingControl.setLayoutData(new GridData());
groupingControl.setFont(parent.getFont());
// Create a table tree viewer.
final Control treeControl = createTreeControl(this, treeStyle);
gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = 3;
treeControl.setLayoutData(gridData);
}
/**
* <p>
* Creates the grouping controls that will appear in the top-right in the
* default layout. The default grouping controls are a label and a combo
* box.
* </p>
* <p>
* Subclasses may extend or override this method. Before this method
* completes, <code>groupingCombo</code> should be initialized.
* Subclasses must create a combo box which contains the
* possible groupings.
* </p>
*
* @param parent
* The composite in which the grouping control should be placed;
* must not be <code>null</code>.
* @return The composite containing the grouping controls, or the grouping
* control itself (if there is only one control).
*/
protected Control createGroupingControl(final Composite parent) {
// Create the composite that will contain the grouping controls.
Composite groupingControl = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
groupingControl.setLayout(layout);
groupingControl.setFont(parent.getFont());
// The label providing some direction as to what the combo represents.
Label groupingLabel = new Label(groupingControl, SWT.NONE);
groupingLabel.setText(NewKeysPreferenceMessages.GroupingCombo_Label);
groupingLabel.setLayoutData(new GridData());
// The combo box
groupingCombo = new Combo(groupingControl, SWT.READ_ONLY);
GridData gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
groupingCombo.setLayoutData(gridData);
return groupingControl;
}
/**
* Returns the combo box which controls the grouping of the items in the
* tree. This combo box is created in
* {@link GroupedFilteredTree#createGroupingControl(Composite)}.
*
* @return The grouping combo. This value may be <code>null</code> if
* there is no grouping control, or if the grouping control has not
* yet been created.
*/
public final Combo getGroupingCombo() {
return groupingCombo;
}
}
/**
* A label provider that simply extracts the command name and the formatted
* trigger sequence from a given binding, and matches them to the correct
* column.
*/
private final class BindingLabelProvider extends LabelProvider implements
ITableLabelProvider {
/**
* The index of the column with the button for adding an item.
*/
private static final int COLUMN_ADD = 2;
/**
* The index of the column containing the command name.
*/
private static final int COLUMN_COMMAND = 0;
/**
* The index of the column with the button for removing an item.
*/
private static final int COLUMN_REMOVE = 3;
/**
* The index of the column containing the trigger sequence.
*/
private static final int COLUMN_TRIGGER_SEQUENCE = 1;
/**
* The number of columns being displayed.
*/
private static final int NUMBER_OF_COLUMNS = 4;
/**
* A resource manager for this preference page.
*/
private final LocalResourceManager localResourceManager = new LocalResourceManager(
JFaceResources.getResources());
public final void dispose() {
super.dispose();
localResourceManager.dispose();
}
public final Image getColumnImage(final Object element,
final int columnIndex) {
final Object value = ((TreeNode) element).getValue();
if (value instanceof Binding) {
switch (columnIndex) {
case COLUMN_COMMAND:
final ParameterizedCommand parameterizedCommand = ((Binding) value)
.getParameterizedCommand();
if (parameterizedCommand != null) {
final String commandId = parameterizedCommand.getId();
final ImageDescriptor imageDescriptor = commandImageService
.getImageDescriptor(commandId);
if (imageDescriptor == null) {
return null;
}
try {
return localResourceManager
.createImage(imageDescriptor);
} catch (final DeviceResourceException e) {
final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
+ commandId + '\'';
final IStatus status = new Status(IStatus.ERROR,
WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
}
}
return null;
case COLUMN_ADD:
return ImageFactory.getImage("plus"); //$NON-NLS-1$
case COLUMN_REMOVE:
return ImageFactory.getImage("minus"); //$NON-NLS-1$
}
} else if (value instanceof ParameterizedCommand) {
switch (columnIndex) {
case COLUMN_COMMAND:
final ParameterizedCommand parameterizedCommand = (ParameterizedCommand) value;
final String commandId = parameterizedCommand.getId();
final ImageDescriptor imageDescriptor = commandImageService
.getImageDescriptor(commandId);
if (imageDescriptor == null) {
return null;
}
try {
return localResourceManager
.createImage(imageDescriptor);
} catch (final DeviceResourceException e) {
final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
+ commandId + '\'';
final IStatus status = new Status(IStatus.ERROR,
WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
}
return null;
}
} else if ((value instanceof Category) || (value instanceof String)) {
switch (columnIndex) {
case COLUMN_COMMAND:
final URL url = BundleUtility.find(PlatformUI.PLUGIN_ID,
ICON_GROUP_OF_BINDINGS);
final ImageDescriptor imageDescriptor = ImageDescriptor
.createFromURL(url);
try {
return localResourceManager
.createImage(imageDescriptor);
} catch (final DeviceResourceException e) {
final String message = "Problem retrieving image for groups of bindings: '" //$NON-NLS-1$
+ ICON_GROUP_OF_BINDINGS + '\'';
final IStatus status = new Status(IStatus.ERROR,
WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
WorkbenchPlugin.log(message, status);
}
}
}
return null;
}
public final String getColumnText(final Object element,
final int columnIndex) {
final Object value = ((TreeNode) element).getValue();
if (value instanceof Binding) {
final Binding binding = (Binding) value;
switch (columnIndex) {
case COLUMN_COMMAND:
try {
return binding.getParameterizedCommand().getName();
} catch (final NotDefinedException e) {
return null;
}
case COLUMN_TRIGGER_SEQUENCE:
return binding.getTriggerSequence().format();
default:
return null;
}
} else if (value instanceof Category) {
if (columnIndex == COLUMN_COMMAND) {
try {
return ((Category) value).getName();
} catch (final NotDefinedException e) {
return null;
}
}
return null;
} else if (value instanceof String) {
// This is a context.
if (columnIndex == COLUMN_COMMAND) {
try {
return contextService.getContext((String) value)
.getName();
} catch (final NotDefinedException e) {
return null;
}
}
return null;
} else if (value instanceof ParameterizedCommand) {
if (columnIndex == COLUMN_COMMAND) {
try {
return ((ParameterizedCommand) value).getName();
} catch (final NotDefinedException e) {
return null;
}
}
return null;
}
return null;
}
}
/**
* Sorts the bindings in the filtered tree based on the current grouping.
*/
private final class BindingComparator extends ViewerComparator {
public final int category(final Object element) {
switch (grouping) {
case GROUPING_CATEGORY:
// TODO This has to be done with something other than the hash.
try {
final ParameterizedCommand command = (element instanceof ParameterizedCommand) ? (ParameterizedCommand) element
: ((Binding) element).getParameterizedCommand();
return command.getCommand().getCategory().hashCode();
} catch (final NotDefinedException e) {
return 0;
}
case GROUPING_CONTEXT:
// TODO This has to be done with something other than the hash.
if (element instanceof Binding) {
return ((Binding) element).getContextId().hashCode();
}
case GROUPING_NONE:
default:
return 0;
}
}
public final int compare(final Viewer viewer, final Object a,
final Object b) {
final String selectedText = filteredTree.getGroupingCombo()
.getText();
try {
if (NewKeysPreferenceMessages.GroupingCombo_Category_Text
.equals(selectedText)) {
// The tree node values will be Category and Binding
// instances.
final Object x = ((TreeNode) a).getValue();
final Object y = ((TreeNode) b).getValue();
/*
* Check to see if any of the objects can be converted to
* parameterized commands.
*/
ParameterizedCommand commandA = null;
if (x instanceof ParameterizedCommand) {
commandA = (ParameterizedCommand) x;
} else if (x instanceof Binding) {
commandA = ((Binding) x).getParameterizedCommand();
}
ParameterizedCommand commandB = null;
if (y instanceof ParameterizedCommand) {
commandB = (ParameterizedCommand) y;
} else if (y instanceof Binding) {
commandB = ((Binding) y).getParameterizedCommand();
}
if ((x instanceof Category) && (y instanceof Category)) {
return Util.compare(((Category) x).getName(),
((Category) y).getName());
} else if ((commandA != null) && (commandB != null)) {
return Util.compare(commandA, commandB);
} else if ((x instanceof Category) && (commandB != null)) {
final int compare = Util.compare(((Category) x)
.getName(), commandB.getCommand().getCategory()
.getName());
return (compare == 0) ? -1 : compare;
} else if ((y instanceof Category) && (commandA != null)) {
final int compare = Util.compare(((Category) y)
.getName(), commandA.getCommand().getCategory()
.getName());
return (compare == 0) ? 1 : compare;
}
} else if (NewKeysPreferenceMessages.GroupingCombo_When_Text
.equals(selectedText)) {
// The tree node values will be String and Binding
// instances.
final Object x = ((TreeNode) a).getValue();
final Object y = ((TreeNode) b).getValue();
if ((x instanceof String) && (y instanceof String)) {
return Util.compare(contextService.getContext(
(String) x).getName(), contextService
.getContext((String) y).getName());
} else if ((x instanceof Binding) && (y instanceof Binding)) {
return Util.compare(((Binding) x)
.getParameterizedCommand(), ((Binding) y)
.getParameterizedCommand());
} else if ((x instanceof ParameterizedCommand)
&& (y instanceof ParameterizedCommand)) {
return Util.compare((ParameterizedCommand) x,
(ParameterizedCommand) y);
} else if ((x instanceof String) && (y instanceof Binding)) {
final int compare = Util
.compare(contextService.getContext((String) x)
.getName(), contextService.getContext(
((Binding) y).getContextId()).getName());
return (compare == 0) ? -1 : compare;
} else if ((y instanceof String) && (x instanceof Binding)) {
final int compare = Util.compare(contextService
.getContext(((Binding) x).getContextId())
.getName(), contextService.getContext(
(String) y).getName());
return (compare == 0) ? 1 : compare;
} else if ((x instanceof String)
&& (y instanceof ParameterizedCommand)) {
final int compare = Util.compare(contextService
.getContext((String) x).getName(),
contextService.getContext(
IContextIds.CONTEXT_ID_WINDOW)
.getName());
return (compare == 0) ? -1 : compare;
} else if ((y instanceof String)
&& (x instanceof ParameterizedCommand)) {
final int compare = Util.compare(contextService
.getContext(IContextIds.CONTEXT_ID_WINDOW)
.getName(), contextService.getContext(
(String) y).getName());
return (compare == 0) ? 1 : compare;
} else if ((x instanceof Binding)
&& (y instanceof ParameterizedCommand)) {
final ParameterizedCommand commandX = ((Binding) x)
.getParameterizedCommand();
final ParameterizedCommand commandY = (ParameterizedCommand) y;
final int compare = Util.compare(commandX, commandY);
return (compare == 0) ? -1 : compare;
} else if ((y instanceof Binding)
&& (x instanceof ParameterizedCommand)) {
final ParameterizedCommand commandY = ((Binding) y)
.getParameterizedCommand();
final ParameterizedCommand commandX = (ParameterizedCommand) x;
final int compare = Util.compare(commandX, commandY);
return (compare == 0) ? 1 : compare;
}
} else { // (GROUPING_NONE_NAME.equals(selectedText))
/*
* The tree node values will be Binding or
* ParameterizedCommand instances.
*/
final Object x = ((TreeNode) a).getValue();
final Object y = ((TreeNode) b).getValue();
final ParameterizedCommand commandX = (x instanceof Binding) ? ((Binding) x)
.getParameterizedCommand()
: (ParameterizedCommand) x;
final ParameterizedCommand commandY = (y instanceof Binding) ? ((Binding) y)
.getParameterizedCommand()
: (ParameterizedCommand) y;
return Util.compare(commandX, commandY);
}
} catch (final NotDefinedException e) {
// This could be made a lot more fine-grained.
}
return 0;
}
}
/**
* A tree node that knows what kind of information to return in the
* <code>toString</code> method. This is used for pattern matching.
*/
private static final class BindingTreeNode extends TreeNode {
private BindingTreeNode(final Object object) {
super(object);
}
public final String toString() {
final Object value = getValue();
final ParameterizedCommand command;
if (value instanceof Binding) {
command = ((Binding) value).getParameterizedCommand();
} else if (value instanceof ParameterizedCommand) {
command = (ParameterizedCommand) value;
} else {
return null;
}
try {
return command.getName()
+ command.getCommand().getDescription();
} catch (final NotDefinedException e) {
return null;
}
}
}
/**
* The constant value for <code>grouping</code> when the bindings should
* be grouped by category.
*/
private static final int GROUPING_CATEGORY = 0;
/**
* The constant value for <code>grouping</code> when the bindings should
* be grouped by context.
*/
private static final int GROUPING_CONTEXT = 1;
/**
* The constant value for <code>grouping</code> when the bindings should
* not be grouped (i.e., they should be displayed in a flat list).
*/
private static final int GROUPING_NONE = 2;
/**
* The path at which the icon for "groups of bindings" is located.
*/
private static final String ICON_GROUP_OF_BINDINGS = "$nl$/icons/full/obj16/keygroups_obj.gif"; //$NON-NLS-1$
/**
* The number of items to show in the bindings table tree.
*/
private static final int ITEMS_TO_SHOW = 7;
/**
* A comparator that can be used for display of
* <code>NamedHandleObject</code> instances to the end user.
*/
private static final NamedHandleObjectComparator NAMED_HANDLE_OBJECT_COMPARATOR = new NamedHandleObjectComparator();
/**
* Sorts the given array of <code>NamedHandleObject</code> instances based
* on their name. This is generally useful if they will be displayed to an
* end users.
*
* @param objects
* The objects to be sorted; must not be <code>null</code>.
* @return The same array, but sorted in place; never <code>null</code>.
*/
private static final NamedHandleObject[] sortByName(
final NamedHandleObject[] objects) {
Arrays.sort(objects, NAMED_HANDLE_OBJECT_COMPARATOR);
return objects;
}
/**
* The workbench's binding service. This binding service is used to access
* the current set of bindings, and to persist changes.
*/
private IBindingService bindingService;
/**
* The text widget containing the key sequence. This value is
* <code>null</code> until the controls are created.
*/
private Text bindingText;
/**
* The workbench's command image service. This command image service is used
* to provide an icon beside each command.
*/
private ICommandImageService commandImageService;
/**
* The label containing the name of the currently selected binding's
* command. This value is <code>null</code> until the controls are
* created.
*/
private Label commandNameValueLabel;
/**
* The workbench's command service. This command service is used to access
* the list of commands.
*/
private ICommandService commandService;
/**
* The workbench's context service. This context service is used to access
* the list of contexts.
*/
private IContextService contextService;
/**
* The label containing the description of the currently selected binding's
* command. This value is <code>null</code> until the controls are
* created.
*/
private Label descriptionValueLabel;
/**
* The filtered tree containing the list of commands and bindings to edit.
*/
private GroupedFilteredTree filteredTree;
/**
* The grouping for the bindings tree. Either there should be no group
* (i.e., flat list), or the bindings should be grouped by either category
* or context.
*/
private int grouping = GROUPING_NONE;
/**
* The key sequence entry widget containing the trigger sequence for the
* currently selected binding. This value is <code>null</code> until the
* controls are created.
*/
private KeySequenceText keySequenceText;
/**
* A binding manager local to this preference page. When the page is
* initialized, the current bindings are read out from the binding service
* and placed in this manager. This manager is then updated as the user
* makes changes. When the user has finished, the contents of this manager
* are compared with the contents of the binding service. The changes are
* then persisted.
*/
private BindingManager localChangeManager;
/**
* The context id of the binding which the user is trying to add. This value
* is derived from the binding that is selected at the time the user tried
* to add a binding. If this value is <code>null</code>, then the user is
* not currently trying to add a binding to a command that already has a
* binding.
*/
private String markedContextId = null;
/**
* The parameterized command to which the user is currently trying to add a
* binding. If this value is <code>null</code>, then the user is not
* currently trying to add a binding to a command that already has a
* binding.
*/
private ParameterizedCommand markedParameterizedCommand = null;
/**
* The combo box containing the list of possible schemes to choose from.
* This value is <code>null</code> until the contents are created.
*/
private ComboViewer schemeCombo = null;
/**
* The check box controlling whether all commands should be shown in the
* filtered tree. This value is <code>null</code> until the contents are
* created.
*/
private Button showAllCheckBox = null;
/**
* The combo box containing the list of possible contexts to choose from.
* This value is <code>null</code> until the contents are create.
*/
private ComboViewer whenCombo = null;
/**
* Adds a new binding based on an existing binding. The command and the
* context are copied from the existing binding. The scheme id is set to be
* the user's personal derivative scheme. The preference page is updated,
* and focus is placed in the key sequence field.
*
* @param binding
* The binding to be added; must not be <code>null</code>.
*/
private final void bindingAdd(final Binding binding) {
// Remember the parameterized command and context.
markedParameterizedCommand = binding.getParameterizedCommand();
markedContextId = binding.getContextId();
// Update the preference page.
update();
// Select the new binding.
filteredTree.getViewer().setSelection(
new StructuredSelection(new BindingTreeNode(
markedParameterizedCommand)), true);
bindingText.setFocus();
bindingText.selectAll();
}
/**
* Removes an existing binding. The preference page is then updated.
*
* @param binding
* The binding to be removed; must not be <code>null</code>.
*/
private final void bindingRemove(final KeyBinding binding) {
final String contextId = binding.getContextId();
final String schemeId = binding.getSchemeId();
final KeySequence triggerSequence = binding.getKeySequence();
localChangeManager.removeBindings(triggerSequence, schemeId, contextId,
null, null, null, Binding.USER);
// TODO This should be the user's personal scheme.
localChangeManager.addBinding(new KeyBinding(triggerSequence, null,
schemeId, contextId, null, null, null, Binding.USER));
update();
}
/**
* Creates the button bar across the bottom of the preference page. This
* button bar contains the "Advanced..." button.
*
* @param parent
* The composite in which the button bar should be placed; never
* <code>null</code>.
* @return The button bar composite; never <code>null</code>.
*/
private final Control createButtonBar(final Composite parent) {
GridLayout layout;
GridData gridData;
int widthHint;
// Create the composite to house the button bar.
final Composite buttonBar = new Composite(parent, SWT.NONE);
layout = new GridLayout(1, false);
layout.marginWidth = 0;
buttonBar.setLayout(layout);
gridData = new GridData();
gridData.horizontalAlignment = SWT.END;
buttonBar.setLayoutData(gridData);
// Advanced button.
final Button advancedButton = new Button(buttonBar, SWT.PUSH);
gridData = new GridData();
widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
advancedButton.setText(NewKeysPreferenceMessages.AdvancedButton_Text);
gridData.widthHint = Math.max(widthHint, advancedButton.computeSize(
SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
advancedButton.setLayoutData(gridData);
return buttonBar;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
*/
protected final Control createContents(final Composite parent) {
GridLayout layout = null;
// Creates a composite to hold all of the page contents.
final Composite page = new Composite(parent, SWT.NONE);
layout = new GridLayout(1, false);
layout.marginWidth = 0;
page.setLayout(layout);
createSchemeControls(page);
createTree(page);
createTreeControls(page);
createDataControls(page);
createButtonBar(page);
fill();
update();
return page;
}
private final Control createDataControls(final Composite parent) {
GridLayout layout;
GridData gridData;
// Creates the data area.
final Composite dataArea = new Composite(parent, SWT.NONE);
layout = new GridLayout(2, true);
layout.marginWidth = 0;
dataArea.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
dataArea.setLayoutData(gridData);
// LEFT DATA AREA
// Creates the left data area.
final Composite leftDataArea = new Composite(dataArea, SWT.NONE);
layout = new GridLayout(3, false);
leftDataArea.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.verticalAlignment = SWT.TOP;
gridData.horizontalAlignment = SWT.FILL;
leftDataArea.setLayoutData(gridData);
// The command name label.
final Label commandNameLabel = new Label(leftDataArea, SWT.NONE);
commandNameLabel.setText(NewKeysPreferenceMessages.CommandNameLabel_Text);
// The current command name.
commandNameValueLabel = new Label(leftDataArea, SWT.NONE);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalSpan = 2;
gridData.horizontalAlignment = SWT.FILL;
commandNameValueLabel.setLayoutData(gridData);
// The binding label.
final Label bindingLabel = new Label(leftDataArea, SWT.NONE);
bindingLabel.setText(NewKeysPreferenceMessages.BindingLabel_Text);
// The key sequence entry widget.
bindingText = new Text(leftDataArea, SWT.BORDER);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
gridData.widthHint = 200;
bindingText.setLayoutData(gridData);
keySequenceText = new KeySequenceText(bindingText);
keySequenceText.setKeyStrokeLimit(4);
keySequenceText
.addPropertyChangeListener(new IPropertyChangeListener() {
public final void propertyChange(
final PropertyChangeEvent event) {
if (!event.getOldValue().equals(event.getNewValue())) {
keySequenceChanged();
}
}
});
// Button for adding trapped key strokes
final Button addKeyButton = new Button(leftDataArea, SWT.LEFT
| SWT.ARROW);
addKeyButton.setToolTipText(NewKeysPreferenceMessages.AddKeyButton_ToolTipText);
gridData = new GridData();
gridData.heightHint = schemeCombo.getCombo().getTextHeight();
addKeyButton.setLayoutData(gridData);
// Arrow buttons aren't normally added to the tab list. Let's fix that.
final Control[] tabStops = dataArea.getTabList();
final ArrayList newTabStops = new ArrayList();
for (int i = 0; i < tabStops.length; i++) {
Control tabStop = tabStops[i];
newTabStops.add(tabStop);
if (bindingText.equals(tabStop)) {
newTabStops.add(addKeyButton);
}
}
final Control[] newTabStopArray = (Control[]) newTabStops
.toArray(new Control[newTabStops.size()]);
dataArea.setTabList(newTabStopArray);
// Construct the menu to attach to the above button.
final Menu addKeyMenu = new Menu(addKeyButton);
final Iterator trappedKeyItr = KeySequenceText.TRAPPED_KEYS.iterator();
while (trappedKeyItr.hasNext()) {
final KeyStroke trappedKey = (KeyStroke) trappedKeyItr.next();
final MenuItem menuItem = new MenuItem(addKeyMenu, SWT.PUSH);
menuItem.setText(trappedKey.format());
menuItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
keySequenceText.insert(trappedKey);
bindingText.setFocus();
bindingText.setSelection(bindingText.getTextLimit());
}
});
}
addKeyButton.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent selectionEvent) {
Point buttonLocation = addKeyButton.getLocation();
buttonLocation = dataArea.toDisplay(buttonLocation.x,
buttonLocation.y);
Point buttonSize = addKeyButton.getSize();
addKeyMenu.setLocation(buttonLocation.x, buttonLocation.y
+ buttonSize.y);
addKeyMenu.setVisible(true);
}
});
// The when label.
final Label whenLabel = new Label(leftDataArea, SWT.NONE);
whenLabel.setText(NewKeysPreferenceMessages.WhenLabel_Text);
// The when combo.
whenCombo = new ComboViewer(leftDataArea);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
gridData.horizontalSpan = 2;
whenCombo.getCombo().setLayoutData(gridData);
whenCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
whenCombo.setContentProvider(new ArrayContentProvider());
// RIGHT DATA AREA
// Creates the right data area.
final Composite rightDataArea = new Composite(dataArea, SWT.NONE);
layout = new GridLayout(1, false);
rightDataArea.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.verticalAlignment = SWT.TOP;
gridData.horizontalAlignment = SWT.FILL;
rightDataArea.setLayoutData(gridData);
// The description label.
final Label descriptionLabel = new Label(rightDataArea, SWT.NONE);
descriptionLabel.setText(NewKeysPreferenceMessages.DescriptionLabel_Text);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
descriptionLabel.setLayoutData(gridData);
// The description value.
descriptionValueLabel = new Label(rightDataArea, SWT.WRAP);
gridData = new GridData();
gridData.horizontalAlignment = SWT.FILL;
gridData.verticalAlignment = SWT.FILL;
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalIndent = 30;
gridData.verticalIndent = 5;
gridData.widthHint = 200;
descriptionValueLabel.setLayoutData(gridData);
return dataArea;
}
private final Control createSchemeControls(final Composite parent) {
GridLayout layout;
GridData gridData;
int widthHint;
// Create a composite to hold the controls.
final Composite schemeControls = new Composite(parent, SWT.NONE);
layout = new GridLayout(3, false);
layout.marginWidth = 0;
schemeControls.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
schemeControls.setLayoutData(gridData);
// Create the label.
final Label schemeLabel = new Label(schemeControls, SWT.NONE);
schemeLabel.setText(NewKeysPreferenceMessages.SchemeLabel_Text);
// Create the combo.
schemeCombo = new ComboViewer(schemeControls);
schemeCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
schemeCombo.setContentProvider(new ArrayContentProvider());
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
schemeCombo.getCombo().setLayoutData(gridData);
schemeCombo
.addSelectionChangedListener(new ISelectionChangedListener() {
public final void selectionChanged(
final SelectionChangedEvent event) {
selectSchemeCombo(event);
}
});
// Create the delete button.
final Button deleteSchemeButton = new Button(schemeControls, SWT.PUSH);
gridData = new GridData();
widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
deleteSchemeButton.setText(NewKeysPreferenceMessages.DeleteSchemeButton_Text);
gridData.widthHint = Math.max(widthHint, deleteSchemeButton
.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
deleteSchemeButton.setLayoutData(gridData);
return schemeControls;
}
private final Control createTree(final Composite parent) {
GridData gridData;
filteredTree = new GroupedFilteredTree(parent, SWT.SINGLE
/* | SWT.FULL_SELECTION */| SWT.BORDER, new PatternFilter());
final GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
filteredTree.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
gridData.verticalAlignment = SWT.FILL;
filteredTree.setLayoutData(gridData);
// Make sure the filtered tree has a height of ITEMS_TO_SHOW
final Tree tree = filteredTree.getViewer().getTree();
tree.setHeaderVisible(true);
final Object layoutData = tree.getLayoutData();
if (layoutData instanceof GridData) {
gridData = (GridData) layoutData;
final int itemHeight = tree.getItemHeight();
if (itemHeight > 1) {
gridData.heightHint = ITEMS_TO_SHOW * itemHeight;
}
}
// Create the columns for the tree.
final TreeColumn commandNameColumn = new TreeColumn(tree, SWT.LEFT,
BindingLabelProvider.COLUMN_COMMAND);
commandNameColumn.setText(NewKeysPreferenceMessages.CommandNameColumn_Text);
final TreeColumn triggerSequenceColumn = new TreeColumn(tree, SWT.LEFT,
BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE);
triggerSequenceColumn.setText(NewKeysPreferenceMessages.TriggerSequenceColumn_Text);
new TreeColumn(tree, SWT.LEFT, BindingLabelProvider.COLUMN_ADD);
new TreeColumn(tree, SWT.LEFT, BindingLabelProvider.COLUMN_REMOVE);
// Set up the providers for the viewer.
final TreeViewer viewer = filteredTree.getViewer();
viewer.setLabelProvider(new BindingLabelProvider());
viewer.setContentProvider(new TreeNodeContentProvider());
viewer.setComparator(new BindingComparator());
/*
* Listen for selection changes so that the data controls can be
* updated.
*/
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
public final void selectionChanged(final SelectionChangedEvent event) {
selectTreeRow(event);
}
});
tree.addMouseListener(new MouseAdapter() {
public final void mouseDown(final MouseEvent event) {
selectTreeColumn(event);
}
});
// Adjust how the filter works.
filteredTree.getPatternFilter().setIncludeLeadingWildcard(true);
// Set the grouping options.
final Combo groupingCombo = filteredTree.getGroupingCombo();
final String[] groupings = { NewKeysPreferenceMessages.GroupingCombo_Category_Text,
NewKeysPreferenceMessages.GroupingCombo_When_Text,
NewKeysPreferenceMessages.GroupingCombo_None_Text };
final Collator collator = Collator.getInstance();
Arrays.sort(groupings, collator);
groupingCombo.setItems(groupings);
groupingCombo.setText(NewKeysPreferenceMessages.GroupingCombo_None_Text);
groupingCombo.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
update();
}
});
return filteredTree;
}
private final Control createTreeControls(final Composite parent) {
GridLayout layout;
GridData gridData;
int widthHint;
// Creates controls related to the tree.
final Composite treeControls = new Composite(parent, SWT.NONE);
layout = new GridLayout(3, false);
layout.marginWidth = 0;
treeControls.setLayout(layout);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
treeControls.setLayoutData(gridData);
// Create the show all check box.
showAllCheckBox = new Button(treeControls, SWT.CHECK);
gridData = new GridData();
gridData.grabExcessHorizontalSpace = true;
gridData.horizontalAlignment = SWT.FILL;
gridData.verticalAlignment = SWT.TOP;
showAllCheckBox.setLayoutData(gridData);
showAllCheckBox.setText(NewKeysPreferenceMessages.ShowAllCheckBox_Text);
showAllCheckBox.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
updateTree();
}
});
// Create the delete binding button.
final Button addBindingButton = new Button(treeControls, SWT.PUSH);
gridData = new GridData();
widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
addBindingButton.setText(NewKeysPreferenceMessages.AddBindingButton_Text);
gridData.widthHint = Math.max(widthHint, addBindingButton.computeSize(
SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
addBindingButton.setLayoutData(gridData);
addBindingButton.addSelectionListener(new SelectionAdapter() {
public final void widgetSelected(final SelectionEvent event) {
selectAddBindingButton(event);
}
});
// Create the delete binding button.
final Button removeBindingButton = new Button(treeControls, SWT.PUSH);
gridData = new GridData();
widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
removeBindingButton.setText(NewKeysPreferenceMessages.RemoveBindingButton_Text);
gridData.widthHint = Math.max(widthHint, removeBindingButton
.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
removeBindingButton.setLayoutData(gridData);
removeBindingButton.addSelectionListener(new SelectionAdapter() {
public final void widgetSelected(final SelectionEvent event) {
selectRemoveBindingButton(event);
}
});
return treeControls;
}
/**
* Copies all of the information from the workbench into a local change
* manager, and then the local change manager is used to populate the
* contents of the various widgets on the page.
*
* The widgets affected by this method are: scheme combo, bindings
* table/tree model, and the when combo.
*/
private final void fill() {
// Make an internal binding manager to track changes.
localChangeManager = new BindingManager(new ContextManager(),
new CommandManager());
final Scheme[] definedSchemes = bindingService.getDefinedSchemes();
try {
for (int i = 0; i < definedSchemes.length; i++) {
final Scheme scheme = definedSchemes[i];
final Scheme copy = localChangeManager
.getScheme(scheme.getId());
copy.define(scheme.getName(), scheme.getDescription(), scheme
.getParentId());
}
localChangeManager
.setActiveScheme(bindingService.getActiveScheme());
} catch (final NotDefinedException e) {
throw new Error(
"There is a programmer error in the keys preference page"); //$NON-NLS-1$
}
localChangeManager.setLocale(bindingService.getLocale());
localChangeManager.setPlatform(bindingService.getPlatform());
localChangeManager.setBindings(bindingService.getBindings());
// Update the scheme combo.
schemeCombo
.setInput(sortByName(localChangeManager.getDefinedSchemes()));
setScheme(localChangeManager.getActiveScheme());
// Update the when combo.
whenCombo.setInput(sortByName(contextService.getDefinedContexts()));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
*/
public final void init(final IWorkbench workbench) {
bindingService = (IBindingService) workbench.getService(IBindingService.class);
commandImageService = (ICommandImageService) workbench.getService(ICommandImageService.class);
commandService = (ICommandService) workbench.getService(ICommandService.class);
contextService = (IContextService) workbench.getService(IContextService.class);
}
/**
* Updates the interface as the key sequence has changed. This finds the
* selected item. If the selected item is a binding, then it updates the
* binding -- either by updating a user binding, or doing the deletion
* marker dance with a system binding. If the selected item is a
* parameterized command, then a binding is created based on the data
* controls.
*/
private final void keySequenceChanged() {
final KeySequence keySequence = keySequenceText.getKeySequence();
if ((keySequence == null) || (!keySequence.isComplete())
|| (keySequence.isEmpty())) {
return;
}
ISelection selection = filteredTree.getViewer().getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection) selection;
final TreeNode node = (TreeNode) structuredSelection
.getFirstElement();
if (node != null) {
final Object object = node.getValue();
selection = whenCombo.getSelection();
final String contextId;
if (selection instanceof IStructuredSelection) {
structuredSelection = (IStructuredSelection) selection;
final Object firstElement = structuredSelection
.getFirstElement();
if (firstElement == null) {
contextId = IContextIds.CONTEXT_ID_WINDOW;
} else {
contextId = ((Context) firstElement).getId();
}
} else {
contextId = IContextIds.CONTEXT_ID_WINDOW;
}
if (object instanceof KeyBinding) {
// TODO
} else if (object instanceof ParameterizedCommand) {
// TODO This should use the user's personal scheme.
final KeyBinding binding = new KeyBinding(keySequence,
(ParameterizedCommand) object,
"org.eclipse.ui.defaultAcceleratorConfiguration", //$NON-NLS-1$
contextId, null, null, null, Binding.USER);
localChangeManager.addBinding(binding);
update();
filteredTree.getViewer().setSelection(
new StructuredSelection(
new BindingTreeNode(binding)), true);
}
}
}
}
/**
* Logs the given exception, and opens an error dialog saying that something
* went wrong. The exception is assumed to have something to do with the
* preference store.
*
* @param exception
* The exception to be logged; must not be <code>null</code>.
*/
private final void logPreferenceStoreException(final Throwable exception) {
final String message = NewKeysPreferenceMessages.PreferenceStoreError_Message;
final String title = NewKeysPreferenceMessages.PreferenceStoreError_Title;
String exceptionMessage = exception.getMessage();
if (exceptionMessage == null) {
exceptionMessage = message;
}
final IStatus status = new Status(IStatus.ERROR,
WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage, exception);
WorkbenchPlugin.log(message, status);
ErrorDialog.openError(schemeCombo.getCombo().getShell(), title,
message, status);
}
protected final void performDefaults() {
// Ask the user to confirm
final String title = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxText;
final String message = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxMessage;
final boolean confirmed = MessageDialog.openConfirm(getShell(), title,
message);
if (confirmed) {
// Fix the scheme in the local changes.
final String defaultSchemeId = bindingService.getDefaultSchemeId();
final Scheme defaultScheme = localChangeManager
.getScheme(defaultSchemeId);
try {
localChangeManager.setActiveScheme(defaultScheme);
} catch (final NotDefinedException e) {
// At least we tried....
}
// Fix the bindings in the local changes.
final Binding[] currentBindings = localChangeManager.getBindings();
final int currentBindingsLength = currentBindings.length;
final Set trimmedBindings = new HashSet();
for (int i = 0; i < currentBindingsLength; i++) {
final Binding binding = currentBindings[i];
if (binding.getType() != Binding.USER) {
trimmedBindings.add(binding);
}
}
final Binding[] trimmedBindingArray = (Binding[]) trimmedBindings
.toArray(new Binding[trimmedBindings.size()]);
localChangeManager.setBindings(trimmedBindingArray);
// Apply the changes.
try {
bindingService.savePreferences(defaultScheme,
trimmedBindingArray);
} catch (final IOException e) {
logPreferenceStoreException(e);
}
}
setScheme(localChangeManager.getActiveScheme());
super.performDefaults();
}
public final boolean performOk() {
// Save the preferences.
try {
bindingService.savePreferences(
localChangeManager.getActiveScheme(), localChangeManager
.getBindings());
} catch (final IOException e) {
logPreferenceStoreException(e);
}
return super.performOk();
}
/**
* Handles the selection event on the add binding button. This adds a new
* binding based on the current selection.
*
* @param event
* Ignored.
*/
private final void selectAddBindingButton(final SelectionEvent event) {
// Check to make sure we've got a selection.
final TreeViewer viewer = filteredTree.getViewer();
final ISelection selection = viewer.getSelection();
if (!(selection instanceof IStructuredSelection)) {
return;
}
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
final Object firstElement = structuredSelection.getFirstElement();
if (firstElement instanceof TreeNode) {
final Object value = ((TreeNode) firstElement).getValue();
if (value instanceof KeyBinding) {
bindingAdd((KeyBinding) value);
} else if (value instanceof ParameterizedCommand) {
bindingText.setFocus();
}
}
}
/**
* Handles the selection event on the remove binding button. This removes
* the selected binding.
*
* @param event
* Ignored.
*/
private final void selectRemoveBindingButton(final SelectionEvent event) {
// Check to make sure we've got a selection.
final TreeViewer viewer = filteredTree.getViewer();
final ISelection selection = viewer.getSelection();
if (!(selection instanceof IStructuredSelection)) {
return;
}
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
final Object firstElement = structuredSelection.getFirstElement();
if (firstElement instanceof TreeNode) {
final Object value = ((TreeNode) firstElement).getValue();
if (value instanceof KeyBinding) {
bindingRemove((KeyBinding) value);
} else if (value == markedParameterizedCommand) {
markedParameterizedCommand = null;
markedContextId = null;
update();
}
}
}
/**
* Handles a selection event on the scheme combo. If the scheme has changed,
* then the local change manager is updated, and the page's contents are
* updated as well.
*
* @param event
* The selection event; must not be <code>null</code>.
*/
private final void selectSchemeCombo(final SelectionChangedEvent event) {
final ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection) {
final Object firstElement = ((IStructuredSelection) selection)
.getFirstElement();
if (firstElement instanceof Scheme) {
final Scheme newScheme = (Scheme) firstElement;
if (newScheme != localChangeManager.getActiveScheme()) {
try {
localChangeManager.setActiveScheme(newScheme);
update();
} catch (final NotDefinedException e) {
// TODO The scheme wasn't valid.
}
}
}
}
}
/**
* Handles a mouse down event on the tree. If the mouse click corresponds
* with one of the button images on the right, then add or remove, as
* appropriate.
*
* @param event
* The mouse down event; must not be <code>null</code>.
*/
private final void selectTreeColumn(final MouseEvent event) {
final TreeViewer viewer = filteredTree.getViewer();
final Tree tree = viewer.getTree();
final Point point = new Point(event.x, event.y);
final TreeItem item = tree.getItem(point);
if (item == null) {
return;
}
for (int i = 0; i < BindingLabelProvider.NUMBER_OF_COLUMNS; i++) {
final Rectangle rectangle = item.getBounds(i);
if (rectangle.contains(point)) {
// Check to make sure we're clicking a button.
if ((i != BindingLabelProvider.COLUMN_ADD)
&& (i != BindingLabelProvider.COLUMN_REMOVE)) {
return;
}
// Check to make sure we've got a selection.
final ISelection selection = viewer.getSelection();
if (!(selection instanceof IStructuredSelection)) {
return;
}
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
final TreeNode treeNode = (TreeNode) structuredSelection
.getFirstElement();
final Object value = treeNode.getValue();
if (value instanceof KeyBinding) {
final KeyBinding binding = (KeyBinding) value;
if (i == BindingLabelProvider.COLUMN_ADD) {
bindingAdd(binding);
} else if (i == BindingLabelProvider.COLUMN_REMOVE) {
bindingRemove(binding);
}
}
}
}
}
/**
* If the row has changed, then update the data controls.
*/
private final void selectTreeRow(final SelectionChangedEvent event) {
updateDataControls();
}
/**
* Sets the currently selected scheme. Setting the scheme always triggers an
* update of the underlying widgets.
*
* @param scheme
* The scheme to select; may be <code>null</code>.
*/
private final void setScheme(final Scheme scheme) {
schemeCombo.setSelection(new StructuredSelection(scheme));
}
/**
* Updates all of the controls on this preference page in response to a user
* interaction.
*/
private final void update() {
updateTree();
updateDataControls();
}
/**
* Updates the data controls to match the current selection, if any.
*/
private final void updateDataControls() {
final ISelection selection = filteredTree.getViewer().getSelection();
if (selection instanceof IStructuredSelection) {
final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
final TreeNode node = (TreeNode) structuredSelection
.getFirstElement();
if (node != null) {
final Object object = node.getValue();
if (object instanceof KeyBinding) {
final KeyBinding binding = (KeyBinding) object;
try {
commandNameValueLabel.setText(binding
.getParameterizedCommand().getName());
String description = binding.getParameterizedCommand()
.getCommand().getDescription();
if (description == null) {
description = Util.ZERO_LENGTH_STRING;
}
descriptionValueLabel.setText(description);
descriptionValueLabel.pack(true);
} catch (final NotDefinedException e) {
// It's probably okay to just let this one slide.
}
keySequenceText.setKeySequence(binding.getKeySequence());
whenCombo.setSelection(new StructuredSelection(
contextService.getContext(binding.getContextId())));
} else if (object instanceof ParameterizedCommand) {
final ParameterizedCommand command = (ParameterizedCommand) object;
try {
commandNameValueLabel.setText(command.getName());
String description = command.getCommand()
.getDescription();
if (description == null) {
description = Util.ZERO_LENGTH_STRING;
}
descriptionValueLabel.setText(description);
descriptionValueLabel.pack(true);
} catch (final NotDefinedException e) {
// It's probably okay to just let this one slide.
}
keySequenceText.clear();
if (command == markedParameterizedCommand) {
whenCombo.setSelection(new StructuredSelection(
contextService.getContext(markedContextId)));
} else {
whenCombo
.setSelection(new StructuredSelection(
contextService
.getContext(IContextIds.CONTEXT_ID_WINDOW)));
}
}
}
}
}
private final void updateTree() {
final TreeViewer viewer = filteredTree.getViewer();
final Collection bindings = localChangeManager
.getActiveBindingsDisregardingContextFlat();
/*
* Add all of the parameterized commands (without bindings) if the show
* all check box is selected.
*/
if (showAllCheckBox.getSelection()) {
final Collection commandIds = commandService.getDefinedCommandIds();
final Collection commands = new ArrayList();
final Iterator commandIdItr = commandIds.iterator();
while (commandIdItr.hasNext()) {
final String currentCommandId = (String) commandIdItr.next();
final Command currentCommand = commandService
.getCommand(currentCommandId);
try {
commands.addAll(ParameterizedCommand
.generateCombinations(currentCommand));
} catch (final NotDefinedException e) {
// It is safe to just ignore undefined commands.
}
}
// Remove duplicates.
final Iterator commandItr = commands.iterator();
while (commandItr.hasNext()) {
final ParameterizedCommand command = (ParameterizedCommand) commandItr
.next();
if (localChangeManager
.getActiveBindingsDisregardingContextFor(command).length > 0) {
commandItr.remove();
}
}
bindings.addAll(commands);
}
// Add the marked parameterized command, if any.
if (markedParameterizedCommand != null) {
bindings.add(markedParameterizedCommand);
}
// Check the grouping.
final String grouping = filteredTree.getGroupingCombo().getText();
if (NewKeysPreferenceMessages.GroupingCombo_Category_Text.equals(grouping)) {
// Group all of the bindings by category.
final HashMap bindingsByCategory = new HashMap();
final Iterator bindingItr = bindings.iterator();
while (bindingItr.hasNext()) {
final Object object = bindingItr.next();
final ParameterizedCommand command;
if (object instanceof Binding) {
command = ((Binding) object).getParameterizedCommand();
} else {
command = (ParameterizedCommand) object;
}
try {
final Category category = command.getCommand()
.getCategory();
final Object existing = bindingsByCategory.get(category);
if (existing instanceof Collection) {
final Collection existingBindings = (Collection) existing;
existingBindings.add(object);
} else {
final Collection newCollection = new ArrayList();
newCollection.add(object);
bindingsByCategory.put(category, newCollection);
}
} catch (final NotDefinedException e) {
// Just skip this one.
continue;
}
}
// Convert the hash map into nodes.
final Iterator entryItr = bindingsByCategory.entrySet().iterator();
final TreeNode[] elements = new TreeNode[bindingsByCategory.size()];
int i = 0;
while (entryItr.hasNext()) {
final Map.Entry entry = (Map.Entry) entryItr.next();
final TreeNode parentNode = new BindingTreeNode(entry.getKey());
final Collection childValues = (Collection) entry.getValue();
final Iterator childValueItr = childValues.iterator();
final TreeNode[] children = new TreeNode[childValues.size()];
int j = 0;
while (childValueItr.hasNext()) {
final TreeNode childNode = new BindingTreeNode(
childValueItr.next());
childNode.setParent(parentNode);
children[j++] = childNode;
}
parentNode.setChildren(children);
elements[i++] = parentNode;
}
// Set the input.
viewer.setInput(elements);
} else if (NewKeysPreferenceMessages.GroupingCombo_When_Text.equals(grouping)) {
// Group all of the bindings by context.
final HashMap bindingsByContextId = new HashMap();
final Iterator bindingItr = bindings.iterator();
while (bindingItr.hasNext()) {
final Object binding = bindingItr.next();
final String contextId;
if (binding instanceof ParameterizedCommand) {
contextId = IContextIds.CONTEXT_ID_WINDOW;
} else {
contextId = ((Binding) binding).getContextId();
}
final Object existing = bindingsByContextId.get(contextId);
if (existing instanceof Collection) {
final Collection existingBindings = (Collection) existing;
existingBindings.add(binding);
} else {
final Collection newCollection = new ArrayList();
newCollection.add(binding);
bindingsByContextId.put(contextId, newCollection);
}
}
// Convert the hash map into nodes.
final Iterator entryItr = bindingsByContextId.entrySet().iterator();
final TreeNode[] elements = new TreeNode[bindingsByContextId.size()];
int i = 0;
while (entryItr.hasNext()) {
final Map.Entry entry = (Map.Entry) entryItr.next();
final TreeNode parentNode = new BindingTreeNode(entry.getKey());
final Collection childValues = (Collection) entry.getValue();
final Iterator childValueItr = childValues.iterator();
final TreeNode[] children = new TreeNode[childValues.size()];
int j = 0;
while (childValueItr.hasNext()) {
final TreeNode childNode = new BindingTreeNode(
childValueItr.next());
childNode.setParent(parentNode);
children[j++] = childNode;
}
parentNode.setChildren(children);
elements[i++] = parentNode;
}
// Set the input.
viewer.setInput(elements);
} else {
// Just a flat list. Convert the flat list into nodes.
final Iterator bindingItr = bindings.iterator();
final TreeNode[] elements = new BindingTreeNode[bindings.size()];
int i = 0;
while (bindingItr.hasNext()) {
elements[i++] = new BindingTreeNode(bindingItr.next());
}
// Set the input.
viewer.setInput(elements);
}
// Repack all of the columns.
final Tree tree = viewer.getTree();
final TreeColumn[] columns = tree.getColumns();
if (NewKeysPreferenceMessages.GroupingCombo_Category_Text.equals(grouping)
|| NewKeysPreferenceMessages.GroupingCombo_When_Text.equals(grouping)) {
columns[0].setWidth(292);
} else {
columns[0].setWidth(292);
}
columns[1].setWidth(234);
columns[2].setWidth(22);
columns[3].setWidth(22);
}
}