blob: aadd13f71d1aaa08eec96ac3de9317f77cf53734 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.keys;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.HandleObject;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.util.Tracing;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionDelta;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
import org.eclipse.jface.bindings.keys.IKeyLookup;
import org.eclipse.jface.bindings.keys.KeyBinding;
import org.eclipse.jface.bindings.keys.KeyLookupFactory;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.contexts.IContextIds;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.WorkbenchException;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.internal.ShowViewMenu;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.Policy;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.services.PreferencePersistence;
import org.eclipse.ui.keys.IBindingService;
/**
* <p>
* A static class for accessing the registry and the preference store.
* </p>
*
* @since 3.1
*/
public class BindingPersistence extends PreferencePersistence {
/**
* Whether this class should print out debugging information when it reads in
* data, or writes to the preference store.
*/
private static final boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;
/**
* The index of the active scheme configuration elements in the indexed array.
*
* @see BindingPersistence#read()
*/
private static final int INDEX_ACTIVE_SCHEME = 0;
/**
* The index of the binding definition configuration elements in the indexed
* array.
*
* @see BindingPersistence#read()
*/
private static final int INDEX_BINDING_DEFINITIONS = 1;
/**
* The index of the scheme definition configuration elements in the indexed
* array.
*
* @see BindingPersistence#read()
*/
private static final int INDEX_SCHEME_DEFINITIONS = 2;
/**
* The name of the default scope in 2.1.x.
*/
private static final String LEGACY_DEFAULT_SCOPE = "org.eclipse.ui.globalScope"; //$NON-NLS-1$
/**
* A look-up map for 2.1.x style <code>string</code> keys on a
* <code>keyBinding</code> element.
*/
private static final Map<String, Integer> r2_1KeysByName = new HashMap<>();
static {
final IKeyLookup lookup = KeyLookupFactory.getDefault();
r2_1KeysByName.put(IKeyLookup.BACKSPACE_NAME, lookup.formalKeyLookupInteger(IKeyLookup.BACKSPACE_NAME));
r2_1KeysByName.put(IKeyLookup.TAB_NAME, lookup.formalKeyLookupInteger(IKeyLookup.TAB_NAME));
r2_1KeysByName.put(IKeyLookup.RETURN_NAME, lookup.formalKeyLookupInteger(IKeyLookup.RETURN_NAME));
r2_1KeysByName.put(IKeyLookup.ENTER_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ENTER_NAME));
r2_1KeysByName.put(IKeyLookup.ESCAPE_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ESCAPE_NAME));
r2_1KeysByName.put(IKeyLookup.ESC_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ESC_NAME));
r2_1KeysByName.put(IKeyLookup.DELETE_NAME, lookup.formalKeyLookupInteger(IKeyLookup.DELETE_NAME));
r2_1KeysByName.put(IKeyLookup.SPACE_NAME, lookup.formalKeyLookupInteger(IKeyLookup.SPACE_NAME));
r2_1KeysByName.put(IKeyLookup.ARROW_UP_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ARROW_UP_NAME));
r2_1KeysByName.put(IKeyLookup.ARROW_DOWN_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ARROW_DOWN_NAME));
r2_1KeysByName.put(IKeyLookup.ARROW_LEFT_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ARROW_LEFT_NAME));
r2_1KeysByName.put(IKeyLookup.ARROW_RIGHT_NAME, lookup.formalKeyLookupInteger(IKeyLookup.ARROW_RIGHT_NAME));
r2_1KeysByName.put(IKeyLookup.PAGE_UP_NAME, lookup.formalKeyLookupInteger(IKeyLookup.PAGE_UP_NAME));
r2_1KeysByName.put(IKeyLookup.PAGE_DOWN_NAME, lookup.formalKeyLookupInteger(IKeyLookup.PAGE_DOWN_NAME));
r2_1KeysByName.put(IKeyLookup.HOME_NAME, lookup.formalKeyLookupInteger(IKeyLookup.HOME_NAME));
r2_1KeysByName.put(IKeyLookup.END_NAME, lookup.formalKeyLookupInteger(IKeyLookup.END_NAME));
r2_1KeysByName.put(IKeyLookup.INSERT_NAME, lookup.formalKeyLookupInteger(IKeyLookup.INSERT_NAME));
r2_1KeysByName.put(IKeyLookup.F1_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F1_NAME));
r2_1KeysByName.put(IKeyLookup.F2_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F2_NAME));
r2_1KeysByName.put(IKeyLookup.F3_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F3_NAME));
r2_1KeysByName.put(IKeyLookup.F4_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F4_NAME));
r2_1KeysByName.put(IKeyLookup.F5_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F5_NAME));
r2_1KeysByName.put(IKeyLookup.F6_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F6_NAME));
r2_1KeysByName.put(IKeyLookup.F7_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F7_NAME));
r2_1KeysByName.put(IKeyLookup.F8_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F8_NAME));
r2_1KeysByName.put(IKeyLookup.F9_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F9_NAME));
r2_1KeysByName.put(IKeyLookup.F10_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F10_NAME));
r2_1KeysByName.put(IKeyLookup.F11_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F11_NAME));
r2_1KeysByName.put(IKeyLookup.F12_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F12_NAME));
r2_1KeysByName.put(IKeyLookup.F13_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F13_NAME));
r2_1KeysByName.put(IKeyLookup.F14_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F14_NAME));
r2_1KeysByName.put(IKeyLookup.F15_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F15_NAME));
r2_1KeysByName.put(IKeyLookup.F16_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F16_NAME));
r2_1KeysByName.put(IKeyLookup.F17_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F17_NAME));
r2_1KeysByName.put(IKeyLookup.F18_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F18_NAME));
r2_1KeysByName.put(IKeyLookup.F19_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F19_NAME));
r2_1KeysByName.put(IKeyLookup.F20_NAME, lookup.formalKeyLookupInteger(IKeyLookup.F20_NAME));
}
/**
* Converts a 2.1.x style key sequence (as parsed from the <code>string</code>
* attribute of the <code>keyBinding</code>) to a 3.1 key sequence.
*
* @param r21KeySequence The sequence of 2.1.x key strokes that should be
* converted into a 3.1 key sequence; never
* <code>null</code>.
* @return A 3.1 key sequence; never <code>null</code>.
*/
private static final KeySequence convert2_1Sequence(int[] r21KeySequence) {
final int r21KeySequenceLength = r21KeySequence.length;
final KeyStroke[] keyStrokes = new KeyStroke[r21KeySequenceLength];
for (int i = 0; i < r21KeySequenceLength; i++) {
keyStrokes[i] = convert2_1Stroke(r21KeySequence[i]);
}
return KeySequence.getInstance(keyStrokes);
}
/**
* Converts a 2.1.x style key stroke (as parsed from the <code>string</code>
* attribute of the <code>keyBinding</code> to a 3.1 key stroke.
*
* @param r21Stroke The 2.1.x stroke to convert; must never be
* <code>null</code>.
* @return A 3.1 key stroke; never <code>null</code>.
*/
private static final KeyStroke convert2_1Stroke(final int r21Stroke) {
return SWTKeySupport.convertAcceleratorToKeyStroke(r21Stroke);
}
/**
* Returns the default scheme identifier for the currently running application.
*
* @return The default scheme identifier (<code>String</code>); never
* <code>null</code>, but may be empty or point to an undefined scheme.
*/
public static final String getDefaultSchemeId() {
final IPreferenceStore store = PlatformUI.getPreferenceStore();
return store.getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
}
/**
* Parses a 2.1.x <code>string</code> attribute of the <code>keyBinding</code>
* element.
*
* @param string The string to parse; must not be <code>null</code>.
* @return An array of integer values -- each integer representing a single key
* stroke. This array may be empty, but it is never <code>null</code>.
*/
private static final int[] parse2_1Sequence(final String string) {
final StringTokenizer stringTokenizer = new StringTokenizer(string);
final int length = stringTokenizer.countTokens();
final int[] strokes = new int[length];
for (int i = 0; i < length; i++) {
strokes[i] = parse2_1Stroke(stringTokenizer.nextToken());
}
return strokes;
}
/**
* Parses a single 2.1.x key stroke string, as provided by
* <code>parse2_1Sequence</code>.
*
* @param string The string to parse; must not be <code>null</code>.
* @return An single integer value representing this key stroke.
*/
private static final int parse2_1Stroke(final String string) {
final StringTokenizer stringTokenizer = new StringTokenizer(string, KeyStroke.KEY_DELIMITER, true);
// Copy out the tokens so we have random access.
final int size = stringTokenizer.countTokens();
final String[] tokens = new String[size];
for (int i = 0; stringTokenizer.hasMoreTokens(); i++) {
tokens[i] = stringTokenizer.nextToken();
}
int value = 0;
if (size % 2 == 1) {
String token = tokens[size - 1];
final Integer integer = r2_1KeysByName.get(token.toUpperCase(Locale.ENGLISH));
if (integer != null) {
value = integer.intValue();
} else if (token.length() == 1) {
value = token.toUpperCase().charAt(0);
}
if (value != 0) {
for (int i = 0; i < size - 1; i++) {
token = tokens[i];
if (i % 2 == 0) {
if (token.equalsIgnoreCase(IKeyLookup.CTRL_NAME)) {
if ((value & SWT.CTRL) != 0) {
return 0;
}
value |= SWT.CTRL;
} else if (token.equalsIgnoreCase(IKeyLookup.ALT_NAME)) {
if ((value & SWT.ALT) != 0) {
return 0;
}
value |= SWT.ALT;
} else if (token.equalsIgnoreCase(IKeyLookup.SHIFT_NAME)) {
if ((value & SWT.SHIFT) != 0) {
return 0;
}
value |= SWT.SHIFT;
} else if (token.equalsIgnoreCase(IKeyLookup.COMMAND_NAME)) {
if ((value & SWT.COMMAND) != 0) {
return 0;
}
value |= SWT.COMMAND;
} else {
return 0;
}
} else if (!KeyStroke.KEY_DELIMITER.equals(token)) {
return 0;
}
}
}
}
return value;
}
/**
* <p>
* Reads the registry and the preference store, and determines the identifier
* for the scheme that should be active. There is a complicated order of
* priorities for this. The registry will only be read if there is no user
* preference, and the default active scheme id is different than the default
* default active scheme id.
* </p>
* <ol>
* <li>A non-default preference.</li>
* <li>The legacy preference XML memento.</li>
* <li>A default preference value that is different than the default default
* active scheme id.</li>
* <li>The registry.</li>
* <li>The default default active scheme id.</li>
* </ol>
*
* @param configurationElements The configuration elements from the commands
* extension point; must not be
* <code>null</code>.
* @param configurationElementCount The number of configuration elements that
* are really in the array.
* @param preferences The memento wrapping the commands preference
* key; may be <code>null</code>.
* @param bindingManager The binding manager that should be updated
* with the active scheme. This binding manager
* must already have its schemes defined. This
* value must not be <code>null</code>.
*/
private static final void readActiveScheme(final IConfigurationElement[] configurationElements,
final int configurationElementCount, final IMemento preferences, final BindingManager bindingManager) {
// A non-default preference.
final IPreferenceStore store = PlatformUI.getPreferenceStore();
final String defaultActiveSchemeId = store.getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
final String preferenceActiveSchemeId = store.getString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
if ((preferenceActiveSchemeId != null) && (!preferenceActiveSchemeId.equals(defaultActiveSchemeId))) {
try {
bindingManager.setActiveScheme(bindingManager.getScheme(preferenceActiveSchemeId));
return;
} catch (final NotDefinedException e) {
// Let's keep looking....
}
}
// A legacy preference XML memento.
if (preferences != null) {
final IMemento[] preferenceMementos = preferences.getChildren(TAG_ACTIVE_KEY_CONFIGURATION);
int preferenceMementoCount = preferenceMementos.length;
for (int i = preferenceMementoCount - 1; i >= 0; i--) {
final IMemento memento = preferenceMementos[i];
String id = memento.getString(ATT_KEY_CONFIGURATION_ID);
if (id != null) {
try {
bindingManager.setActiveScheme(bindingManager.getScheme(id));
return;
} catch (final NotDefinedException e) {
// Let's keep looking....
}
}
}
}
// A default preference value that is different than the default.
if ((defaultActiveSchemeId != null && defaultActiveSchemeId.length() > 0)
&& (!defaultActiveSchemeId.equals(IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID))) {
try {
bindingManager.setActiveScheme(bindingManager.getScheme(defaultActiveSchemeId));
return;
} catch (final NotDefinedException e) {
// Let's keep looking....
}
}
// The registry.
for (int i = configurationElementCount - 1; i >= 0; i--) {
final IConfigurationElement configurationElement = configurationElements[i];
String id = configurationElement.getAttribute(ATT_KEY_CONFIGURATION_ID);
if (id != null) {
try {
bindingManager.setActiveScheme(bindingManager.getScheme(id));
return;
} catch (final NotDefinedException e) {
// Let's keep looking....
}
}
id = configurationElement.getAttribute(ATT_VALUE);
if (id != null) {
try {
bindingManager.setActiveScheme(bindingManager.getScheme(id));
return;
} catch (final NotDefinedException e) {
// Let's keep looking....
}
}
}
// The default default active scheme id.
try {
bindingManager.setActiveScheme(bindingManager.getScheme(IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID));
} catch (final NotDefinedException e) {
// this is bad - the default default scheme should always exist
throw new Error("The default default active scheme id is not defined."); //$NON-NLS-1$
}
}
/**
* Reads all of the binding definitions from the preferences.
*
* @param preferences The memento for the commands preferences key.
* @param bindingManager The binding manager to which the bindings should be
* added; must not be <code>null</code>.
* @param commandService The command service for the workbench; must not be
* <code>null</code>.
*/
private static final void readBindingsFromPreferences(final IMemento preferences,
final BindingManager bindingManager, final CommandManager commandService) {
List<IStatus> warningsToLog = new ArrayList<>(1);
if (preferences != null) {
final IMemento[] preferenceMementos = preferences.getChildren(TAG_KEY_BINDING);
int preferenceMementoCount = preferenceMementos.length;
for (int i = preferenceMementoCount - 1; i >= 0; i--) {
final IMemento memento = preferenceMementos[i];
// Read out the command id.
String commandId = readOptional(memento, ATT_COMMAND_ID);
if (commandId == null) {
commandId = readOptional(memento, ATT_COMMAND);
}
final Command command;
if (commandId != null) {
command = commandService.getCommand(commandId);
} else {
command = null;
}
// Read out the scheme id.
String schemeId = readOptional(memento, ATT_KEY_CONFIGURATION_ID);
if (schemeId == null) {
schemeId = readRequired(memento, ATT_CONFIGURATION, warningsToLog,
"Key bindings need a scheme or key configuration"); //$NON-NLS-1$
if (schemeId == null) {
continue;
}
}
// Read out the context id.
String contextId = readOptional(memento, ATT_CONTEXT_ID);
if (contextId == null) {
contextId = readOptional(memento, ATT_SCOPE);
}
if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
contextId = null;
}
if (contextId == null) {
contextId = IContextIds.CONTEXT_ID_WINDOW;
}
// Read out the key sequence.
String keySequenceText = readOptional(memento, ATT_KEY_SEQUENCE);
KeySequence keySequence = null;
if (keySequenceText == null) {
keySequenceText = readRequired(memento, ATT_STRING, warningsToLog,
"Key bindings need a key sequence or string"); //$NON-NLS-1$
if (keySequenceText == null) {
continue;
}
// The key sequence is in the old-style format.
keySequence = convert2_1Sequence(parse2_1Sequence(keySequenceText));
} else {
// The key sequence is in the new-style format.
try {
keySequence = KeySequence.getInstance(keySequenceText);
} catch (final ParseException e) {
addWarning(warningsToLog, "Could not parse", null, //$NON-NLS-1$
commandId, "keySequence", keySequenceText); //$NON-NLS-1$
continue;
}
if (keySequence.isEmpty() || !keySequence.isComplete()) {
addWarning(warningsToLog, "Key bindings cannot use an empty or incomplete key sequence", //$NON-NLS-1$
null, commandId, "keySequence", keySequence //$NON-NLS-1$
.toString());
continue;
}
}
// Read out the locale and platform.
final String locale = readOptional(memento, ATT_LOCALE);
final String platform = readOptional(memento, ATT_PLATFORM);
// Read out the parameters
final ParameterizedCommand parameterizedCommand = command != null
? readParameters(memento, warningsToLog, command)
: null;
final Binding binding = new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale,
platform, null, Binding.USER);
bindingManager.addBinding(binding);
}
}
// If there were any warnings, then log them now.
logWarnings(warningsToLog, "Warnings while parsing the key bindings from the preference store"); //$NON-NLS-1$
}
/**
* Reads all of the binding definitions from the commands extension point.
*
* @param configurationElements The configuration elements in the commands
* extension point; must not be
* <code>null</code>, but may be empty.
* @param configurationElementCount The number of configuration elements that
* are really in the array.
* @param bindingManager The binding manager to which the bindings
* should be added; must not be
* <code>null</code>.
* @param commandService The command service for the workbench; must
* not be <code>null</code>.
*/
private static final void readBindingsFromRegistry(final IConfigurationElement[] configurationElements,
final int configurationElementCount, final BindingManager bindingManager,
final CommandManager commandService) {
final Collection<KeyBinding> bindings = new ArrayList<>(configurationElementCount);
final List<IStatus> warningsToLog = new ArrayList<>(1);
HashSet<KeyBinding> cocoaTempList = new HashSet<>();
// IViewRegistry viewRegistry =
// PlatformUI.getWorkbench().getViewRegistry();
// the local cache for the sequence modifiers
IConfigurationElement[] sequenceModifiers = new IConfigurationElement[0];
if (configurationElementCount > 0)
sequenceModifiers = getSequenceModifierElements(configurationElements[0]);
for (int i = 0; i < configurationElementCount; i++) {
final IConfigurationElement configurationElement = configurationElements[i];
// different extension. update the cache ...
if (i > 0 && !configurationElement.getDeclaringExtension()
.equals(configurationElements[i - 1].getDeclaringExtension()))
sequenceModifiers = getSequenceModifierElements(configurationElement);
/*
* Read out the command id. Doing this before determining if the key binding is
* actually valid is a bit wasteful. However, it is helpful to have the command
* identifier when logging syntax errors.
*/
String commandId = readCommandId(configurationElement);
String viewParameter = null;
final Command command;
if (commandId != null) {
// TODO should we still try processing keybindings to viewIds?
// if (viewRegistry.find(commandId) == null) {
command = commandService.getCommand(commandId);
if (!command.isDefined()) {
// Reference to an undefined command. This is invalid.
addWarning(warningsToLog, "Cannot bind to an undefined command", //$NON-NLS-1$
configurationElement, commandId);
continue;
}
} else {
command = null;
}
// Read out the scheme id.
String schemeId = readSchemeId(configurationElement, warningsToLog, commandId);
if (isEmpty(schemeId))
continue;
// Read out the context id.
String contextId = readContextId(configurationElement);
String keySequenceText = readKeySequenceText(configurationElement);
if (isEmpty(keySequenceText)) {
// The key sequence should never be null. This is pointless
addWarning(warningsToLog, "Defining a key binding with no key sequence has no effect", //$NON-NLS-1$
configurationElement, commandId);
continue;
}
// Read out the key sequence.
KeySequence keySequence = readKeySequence(configurationElement, warningsToLog, commandId, keySequenceText);
if (keySequence == null)
continue;
// Read out the locale and platform.
String locale = readNonEmptyAttribute(configurationElement, ATT_LOCALE);
String platform = readNonEmptyAttribute(configurationElement, ATT_PLATFORM);
// Read out the parameters, if any.
ParameterizedCommand parameterizedCommand = readParameterizedCommand(warningsToLog, configurationElement,
viewParameter, command);
List<KeyBinding> modifiedBindings = applyModifiers(keySequence, keySequenceText, platform,
sequenceModifiers, parameterizedCommand, schemeId, contextId, locale, warningsToLog);
KeyBinding binding = modifiedBindings.get(0);
if (modifiedBindings.size() > 1) {
for (int j = 1; j < modifiedBindings.size(); j++) {
bindings.add(modifiedBindings.get(j));
}
}
if (Util.WS_COCOA.equals(platform)) {
cocoaTempList.add(binding);
} else if (Util.WS_CARBON.equals(platform)) {
bindings.add(binding);
// temp work around ... simply honour the carbon
// bindings for cocoa.
cocoaTempList.add(new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale,
Util.WS_COCOA, null, Binding.SYSTEM));
} else {
bindings.add(binding);
}
}
if (cocoaTempList.size() > 0) {
bindings.addAll(cocoaTempList);
}
final Binding[] bindingArray = bindings.toArray(new Binding[bindings.size()]);
bindingManager.setBindings(bindingArray);
logWarnings(warningsToLog,
"Warnings while parsing the key bindings from the 'org.eclipse.ui.commands' and 'org.eclipse.ui.bindings' extension point"); //$NON-NLS-1$
}
private static List<KeyBinding> applyModifiers(KeySequence keySequence, String keySequenceText, String platform,
IConfigurationElement[] sequenceModifiers, ParameterizedCommand parameterizedCommand, String schemeId,
String contextId, String locale, List<IStatus> warningsToLog) {
List<KeyBinding> bindings = new ArrayList<>();
for (IConfigurationElement sequenceModifier : sequenceModifiers) {
String findSequence = sequenceModifier.getAttribute(ATT_FIND);
if (keySequenceText.startsWith(findSequence)) {
String replaceSequence = sequenceModifier.getAttribute(ATT_REPLACE);
String modifiedSequence = replaceSequence + keySequenceText.substring(findSequence.length());
String platformsString = sequenceModifier.getAttribute(ATT_PLATFORMS);
String[] platforms = parseCommaSeparatedString(platformsString);
try {
if (platform == null) {
addGenericBindings(keySequence, parameterizedCommand, schemeId, contextId, locale, bindings,
modifiedSequence, platforms);
} else {
getBindingForPlatform(keySequence, platform, parameterizedCommand, schemeId, contextId, locale,
bindings, modifiedSequence, platforms);
}
} catch (ParseException e) {
bindings.clear();
addWarning(warningsToLog, "Cannot create modified sequence for key binding", //$NON-NLS-1$
sequenceModifier, parameterizedCommand.getId(), ATT_REPLACE, replaceSequence);
}
break;
}
}
if (bindings.isEmpty()) {
// no modifier was applied/error occurred ...
KeyBinding binding = new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale,
platform, null, Binding.SYSTEM);
bindings.add(binding);
}
return bindings;
}
private static void getBindingForPlatform(KeySequence keySequence, String platform,
ParameterizedCommand parameterizedCommand, String schemeId, String contextId, String locale,
List<KeyBinding> bindings, String modifiedSequence, String[] platforms) throws ParseException {
int j = 0;
for (; j < platforms.length; j++) {
if (platforms[j].equals(platform)) {
KeyBinding newBinding = new KeyBinding(KeySequence.getInstance(modifiedSequence), parameterizedCommand,
schemeId, contextId, locale, platforms[j], null, Binding.SYSTEM);
bindings.add(newBinding);
break;
}
}
if (j == platforms.length) {
// platform doesn't match. use the unmodified sequence
KeyBinding newBinding = new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale,
platform, null, Binding.SYSTEM);
bindings.add(newBinding);
}
}
private static void addGenericBindings(KeySequence keySequence, ParameterizedCommand parameterizedCommand,
String schemeId, String contextId, String locale, List<KeyBinding> bindings, String modifiedSequence,
String[] platforms) throws ParseException {
KeyBinding originalBinding = new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale,
null, null, Binding.SYSTEM);
bindings.add(originalBinding);
String platform = SWT.getPlatform();
boolean modifierExists = false;
for (String currentPlatform : platforms) {
if (currentPlatform.equals(platform)) {
modifierExists = true;
break;
}
}
if (modifierExists) {
KeyBinding newBinding = new KeyBinding(KeySequence.getInstance(modifiedSequence), parameterizedCommand,
schemeId, contextId, locale, SWT.getPlatform(), null, Binding.SYSTEM);
KeyBinding deleteBinding = new KeyBinding(keySequence, null, schemeId, contextId, locale, SWT.getPlatform(),
null, Binding.SYSTEM);
bindings.add(newBinding);
bindings.add(deleteBinding);
}
}
private static IConfigurationElement[] getSequenceModifierElements(IConfigurationElement configurationElement) {
IExtension extension = configurationElement.getDeclaringExtension();
List<IConfigurationElement> modifierElements = new ArrayList<>();
for (final IConfigurationElement configElement : extension.getConfigurationElements()) {
if (TAG_SEQUENCE_MODIFIER.equals(configElement.getName()))
modifierElements.add(configElement);
}
return modifierElements.toArray(new IConfigurationElement[modifierElements.size()]);
}
public static String[] parseCommaSeparatedString(String commaSeparatedString) {
StringTokenizer tokenizer = new StringTokenizer(commaSeparatedString, ", "); //$NON-NLS-1$
int count = tokenizer.countTokens();
String[] tokens = new String[count];
for (int i = 0; i < tokens.length; i++) {
tokens[i] = tokenizer.nextToken();
}
return tokens;
}
private static String readKeySequenceText(IConfigurationElement configurationElement) {
String keySequenceText = configurationElement.getAttribute(ATT_SEQUENCE);
if (isEmpty(keySequenceText)) {
keySequenceText = configurationElement.getAttribute(ATT_KEY_SEQUENCE);
}
if (isEmpty(keySequenceText))
keySequenceText = configurationElement.getAttribute(ATT_STRING);
return keySequenceText;
}
private static KeySequence readKeySequence(IConfigurationElement configurationElement, List<IStatus> warningsToLog,
String commandId, String keySequenceText) {
KeySequence keySequence = null;
if (keySequenceText.equals(configurationElement.getAttribute(ATT_STRING))) {
// The key sequence is in the old-style format.
try {
keySequence = convert2_1Sequence(parse2_1Sequence(keySequenceText));
} catch (final IllegalArgumentException e) {
addWarning(warningsToLog, "Could not parse key sequence", //$NON-NLS-1$
configurationElement, commandId, "keySequence", //$NON-NLS-1$
keySequenceText);
return null;
}
} else {
// The key sequence is in the new-style format.
try {
keySequence = KeySequence.getInstance(keySequenceText);
} catch (final ParseException e) {
addWarning(warningsToLog, "Could not parse key sequence", //$NON-NLS-1$
configurationElement, commandId, "keySequence", //$NON-NLS-1$
keySequenceText);
return null;
}
if (keySequence.isEmpty() || !keySequence.isComplete()) {
addWarning(warningsToLog, "Key bindings should not have an empty or incomplete key sequence", //$NON-NLS-1$
configurationElement, commandId, "keySequence", //$NON-NLS-1$
keySequence.toString());
return null;
}
}
return keySequence;
}
private static ParameterizedCommand readParameterizedCommand(final List<IStatus> warningsToLog,
final IConfigurationElement configurationElement, String viewParameter, final Command command) {
final ParameterizedCommand parameterizedCommand;
if (command == null) {
parameterizedCommand = null;
} else if (viewParameter != null) {
HashMap<String, String> parms = new HashMap<>();
parms.put(ShowViewMenu.VIEW_ID_PARM, viewParameter);
parameterizedCommand = ParameterizedCommand.generateCommand(command, parms);
} else {
parameterizedCommand = readParameters(configurationElement, warningsToLog, command);
}
return parameterizedCommand;
}
/**
* Reads the specified attribute from the configuration element. If the value is
* an empty string, will return null.
*
* @param configurationElement
* @return the attribute value
*/
private static String readNonEmptyAttribute(IConfigurationElement configurationElement, String attribute) {
String attributeValue = configurationElement.getAttribute(attribute);
if ((attributeValue != null) && (attributeValue.isEmpty())) {
attributeValue = null;
}
return attributeValue;
}
private static String readContextId(final IConfigurationElement configurationElement) {
String contextId = configurationElement.getAttribute(ATT_CONTEXT_ID);
if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
contextId = null;
} else if ((contextId == null) || (contextId.isEmpty())) {
contextId = configurationElement.getAttribute(ATT_SCOPE);
if (LEGACY_DEFAULT_SCOPE.equals(contextId)) {
contextId = null;
}
}
if ((contextId == null) || (contextId.isEmpty())) {
contextId = IContextIds.CONTEXT_ID_WINDOW;
}
return contextId;
}
private static String readSchemeId(IConfigurationElement configurationElement, List<IStatus> warningsToLog,
String commandId) {
String schemeId = configurationElement.getAttribute(ATT_SCHEME_ID);
if ((schemeId == null) || (schemeId.isEmpty())) {
schemeId = configurationElement.getAttribute(ATT_KEY_CONFIGURATION_ID);
if ((schemeId == null) || (schemeId.isEmpty())) {
schemeId = configurationElement.getAttribute(ATT_CONFIGURATION);
if ((schemeId == null) || (schemeId.isEmpty())) {
// The scheme id should never be null. This is invalid.
addWarning(warningsToLog, "Key bindings need a scheme", //$NON-NLS-1$
configurationElement, commandId);
}
}
}
return schemeId;
}
private static String readCommandId(final IConfigurationElement configurationElement) {
String commandId = configurationElement.getAttribute(ATT_COMMAND_ID);
if ((commandId == null) || (commandId.isEmpty())) {
commandId = configurationElement.getAttribute(ATT_COMMAND);
}
if ((commandId != null) && (commandId.isEmpty())) {
commandId = null;
}
return commandId;
}
private static boolean isEmpty(String string) {
return string == null || string.isEmpty();
}
/**
* Reads all of the scheme definitions from the registry.
*
* @param configurationElements The configuration elements in the commands
* extension point; must not be
* <code>null</code>, but may be empty.
* @param configurationElementCount The number of configuration elements that
* are really in the array.
* @param bindingManager The binding manager to which the schemes
* should be added; must not be
* <code>null</code>.
*/
private static final void readSchemesFromRegistry(final IConfigurationElement[] configurationElements,
final int configurationElementCount, final BindingManager bindingManager) {
// Undefine all the previous handle objects.
final HandleObject[] handleObjects = bindingManager.getDefinedSchemes();
if (handleObjects != null) {
for (HandleObject handleObject : handleObjects) {
handleObject.undefine();
}
}
final List<IStatus> warningsToLog = new ArrayList<>(1);
for (int i = 0; i < configurationElementCount; i++) {
final IConfigurationElement configurationElement = configurationElements[i];
// Read out the attributes.
final String id = readRequired(configurationElement, ATT_ID, warningsToLog, "Schemes need an id"); //$NON-NLS-1$
if (id == null) {
continue;
}
final String name = readRequired(configurationElement, ATT_NAME, warningsToLog, "A scheme needs a name", //$NON-NLS-1$
id);
if (name == null) {
continue;
}
final String description = readOptional(configurationElement, ATT_DESCRIPTION);
String parentId = configurationElement.getAttribute(ATT_PARENT_ID);
if ((parentId != null) && (parentId.isEmpty())) {
parentId = configurationElement.getAttribute(ATT_PARENT);
if ((parentId != null) && (parentId.isEmpty())) {
parentId = null;
}
}
// Define the scheme.
final Scheme scheme = bindingManager.getScheme(id);
scheme.define(name, description, parentId);
}
logWarnings(warningsToLog,
"Warnings while parsing the key bindings from the 'org.eclipse.ui.bindings', 'org.eclipse.ui.acceleratorConfigurations' and 'org.eclipse.ui.commands' extension point"); //$NON-NLS-1$
}
/**
* Writes the given active scheme and bindings to the preference store. Only
* bindings that are of the <code>Binding.USER</code> type will be written; the
* others will be ignored.
*
* @param activeScheme The scheme which should be persisted; may be
* <code>null</code>.
* @param bindings The bindings which should be persisted; may be
* <code>null</code>
* @throws IOException If something happens while trying to write to the
* workbench preference store.
*/
static final void write(final Scheme activeScheme, final Binding[] bindings) throws IOException {
// Print out debugging information, if requested.
if (DEBUG) {
Tracing.printTrace("BINDINGS", "Persisting active scheme '" //$NON-NLS-1$ //$NON-NLS-2$
+ activeScheme.getId() + '\'');
Tracing.printTrace("BINDINGS", "Persisting bindings"); //$NON-NLS-1$ //$NON-NLS-2$
}
// Write the simple preference key to the UI preference store.
writeActiveScheme(activeScheme);
// Build the XML block for writing the bindings and active scheme.
final XMLMemento xmlMemento = XMLMemento.createWriteRoot(EXTENSION_COMMANDS);
if (activeScheme != null) {
writeActiveSchemeToPreferences(xmlMemento, activeScheme);
}
if (bindings != null) {
final int bindingsLength = bindings.length;
for (int i = 0; i < bindingsLength; i++) {
final Binding binding = bindings[i];
if (binding.getType() == Binding.USER) {
writeBindingToPreferences(xmlMemento, binding);
}
}
}
// Write the XML block to the workbench preference store.
final IPreferenceStore preferenceStore = WorkbenchPlugin.getDefault().getPreferenceStore();
try (Writer writer = new StringWriter()) {
xmlMemento.save(writer);
preferenceStore.setValue(EXTENSION_COMMANDS, writer.toString());
}
}
/**
* Writes the active scheme to its own preference key. This key is used by RCP
* applications as part of their plug-in customization.
*
* @param scheme The scheme to write to the preference store. If the scheme is
* <code>null</code>, then it is removed.
*/
private static final void writeActiveScheme(final Scheme scheme) {
final IPreferenceStore store = PlatformUI.getPreferenceStore();
final String schemeId = (scheme == null) ? null : scheme.getId();
final String defaultSchemeId = store.getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
if ((defaultSchemeId == null) ? (scheme != null) : (!defaultSchemeId.equals(schemeId))) {
store.setValue(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID, scheme.getId());
} else {
store.setToDefault(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
}
}
/**
* Writes the active scheme to the memento. If the scheme is <code>null</code>,
* then all schemes in the memento are removed.
*
* @param memento The memento to which the scheme should be written; must not be
* <code>null</code>.
* @param scheme The scheme that should be written; must not be
* <code>null</code>.
*/
private static final void writeActiveSchemeToPreferences(final IMemento memento, final Scheme scheme) {
// Add this active scheme, if it is not the default.
final IPreferenceStore store = PlatformUI.getPreferenceStore();
final String schemeId = scheme.getId();
final String defaultSchemeId = store.getDefaultString(IWorkbenchPreferenceConstants.KEY_CONFIGURATION_ID);
if ((defaultSchemeId == null) ? (schemeId != null) : (!defaultSchemeId.equals(schemeId))) {
final IMemento child = memento.createChild(TAG_ACTIVE_KEY_CONFIGURATION);
child.putString(ATT_KEY_CONFIGURATION_ID, schemeId);
}
}
/**
* Writes the binding to the memento. This creates a new child element on the
* memento, and places the properties of the binding as its attributes.
*
* @param parent The parent memento for the binding element; must not be
* <code>null</code>.
* @param binding The binding to write; must not be <code>null</code>.
*/
private static final void writeBindingToPreferences(final IMemento parent, final Binding binding) {
final IMemento element = parent.createChild(TAG_KEY_BINDING);
element.putString(ATT_CONTEXT_ID, binding.getContextId());
final ParameterizedCommand parameterizedCommand = binding.getParameterizedCommand();
final String commandId = (parameterizedCommand == null) ? null : parameterizedCommand.getId();
element.putString(ATT_COMMAND_ID, commandId);
element.putString(ATT_KEY_CONFIGURATION_ID, binding.getSchemeId());
element.putString(ATT_KEY_SEQUENCE, binding.getTriggerSequence().toString());
element.putString(ATT_LOCALE, binding.getLocale());
element.putString(ATT_PLATFORM, binding.getPlatform());
if (parameterizedCommand != null) {
final Map<String, String> parameterizations = parameterizedCommand.getParameterMap();
final Iterator<Map.Entry<String, String>> parameterizationItr = parameterizations.entrySet().iterator();
while (parameterizationItr.hasNext()) {
final Map.Entry<String, String> entry = parameterizationItr.next();
final String id = entry.getKey();
final String value = entry.getValue();
final IMemento parameterElement = element.createChild(TAG_PARAMETER);
parameterElement.putString(ATT_ID, id);
parameterElement.putString(ATT_VALUE, value);
}
}
}
/**
* The binding manager which should be populated with the values from the
* registry and preference store; must not be <code>null</code>.
*/
private final BindingManager bindingManager;
/**
* The command service for the workbench; must not be <code>null</code>.
*/
private final CommandManager commandManager;
/**
* Constructs a new instance of <code>BindingPersistence</code>.
*
* @param bindingManager The binding manager which should be populated with the
* values from the registry and preference store; must not
* be <code>null</code>.
* @param commandService The command service for the workbench; must not be
* <code>null</code>.
*/
public BindingPersistence(final BindingManager bindingManager, final CommandManager commandManager) {
this.bindingManager = bindingManager;
this.commandManager = commandManager;
// HACK. Calling super.read() installs a required preferences change listener.
// See bug 266604.
super.read();
}
@Override
protected final boolean isChangeImportant(final IRegistryChangeEvent event) {
return false;
}
public boolean bindingsNeedUpdating(final IRegistryChangeEvent event) {
final IExtensionDelta[] acceleratorConfigurationDeltas = event.getExtensionDeltas(PlatformUI.PLUGIN_ID,
IWorkbenchRegistryConstants.PL_ACCELERATOR_CONFIGURATIONS);
if (acceleratorConfigurationDeltas.length == 0) {
final IExtensionDelta[] bindingDeltas = event.getExtensionDeltas(PlatformUI.PLUGIN_ID,
IWorkbenchRegistryConstants.PL_BINDINGS);
if (bindingDeltas.length == 0) {
final IExtensionDelta[] commandDeltas = event.getExtensionDeltas(PlatformUI.PLUGIN_ID,
IWorkbenchRegistryConstants.PL_COMMANDS);
if (commandDeltas.length == 0) {
final IExtensionDelta[] acceleratorScopeDeltas = event.getExtensionDeltas(PlatformUI.PLUGIN_ID,
IWorkbenchRegistryConstants.PL_ACCELERATOR_SCOPES);
if (acceleratorScopeDeltas.length == 0) {
final IExtensionDelta[] contextDeltas = event.getExtensionDeltas(PlatformUI.PLUGIN_ID,
IWorkbenchRegistryConstants.PL_CONTEXTS);
if (contextDeltas.length == 0) {
final IExtensionDelta[] actionDefinitionDeltas = event.getExtensionDeltas(
PlatformUI.PLUGIN_ID, IWorkbenchRegistryConstants.PL_ACTION_DEFINITIONS);
if (actionDefinitionDeltas.length == 0) {
return false;
}
}
}
}
}
}
return true;
}
@Override
protected final boolean isChangeImportant(final PropertyChangeEvent event) {
return EXTENSION_COMMANDS.equals(event.getProperty());
}
/**
* Reads all of the binding information from the registry and from the
* preference store.
*/
@Override
public final void read() {
super.read();
reRead();
}
public void reRead() {
// Create the extension registry mementos.
final IExtensionRegistry registry = Platform.getExtensionRegistry();
int activeSchemeElementCount = 0;
int bindingDefinitionCount = 0;
int schemeDefinitionCount = 0;
final IConfigurationElement[][] indexedConfigurationElements = new IConfigurationElement[3][];
// Sort the bindings extension point based on element name.
final IConfigurationElement[] bindingsExtensionPoint = registry.getConfigurationElementsFor(EXTENSION_BINDINGS);
for (final IConfigurationElement configurationElement : bindingsExtensionPoint) {
final String name = configurationElement.getName();
// Check if it is a binding definition.
if (TAG_KEY.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_BINDING_DEFINITIONS,
bindingDefinitionCount++);
} else
// Check to see if it is a scheme definition.
if (TAG_SCHEME.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
schemeDefinitionCount++);
}
}
// Sort the commands extension point based on element name.
final IConfigurationElement[] commandsExtensionPoint = registry.getConfigurationElementsFor(EXTENSION_COMMANDS);
for (final IConfigurationElement configurationElement : commandsExtensionPoint) {
final String name = configurationElement.getName();
// Check if it is a binding definition.
if (TAG_KEY_BINDING.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_BINDING_DEFINITIONS,
bindingDefinitionCount++);
// Check if it is a scheme defintion.
} else if (TAG_KEY_CONFIGURATION.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
schemeDefinitionCount++);
// Check if it is an active scheme identifier.
} else if (TAG_ACTIVE_KEY_CONFIGURATION.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_ACTIVE_SCHEME,
activeSchemeElementCount++);
}
}
/*
* Sort the accelerator configuration extension point into the scheme
* definitions.
*/
final IConfigurationElement[] acceleratorConfigurationsExtensionPoint = registry
.getConfigurationElementsFor(EXTENSION_ACCELERATOR_CONFIGURATIONS);
for (final IConfigurationElement configurationElement : acceleratorConfigurationsExtensionPoint) {
final String name = configurationElement.getName();
// Check if the name matches the accelerator configuration element
if (TAG_ACCELERATOR_CONFIGURATION.equals(name)) {
addElementToIndexedArray(configurationElement, indexedConfigurationElements, INDEX_SCHEME_DEFINITIONS,
schemeDefinitionCount++);
}
}
// Create the preference memento.
final IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore();
final String preferenceString = store.getString(EXTENSION_COMMANDS);
IMemento preferenceMemento = null;
if ((preferenceString != null) && (preferenceString.length() > 0)) {
final Reader reader = new StringReader(preferenceString);
try {
preferenceMemento = XMLMemento.createReadRoot(reader);
} catch (final WorkbenchException e) {
// Could not initialize the preference memento.
}
}
// Read the scheme definitions.
readSchemesFromRegistry(indexedConfigurationElements[INDEX_SCHEME_DEFINITIONS], schemeDefinitionCount,
bindingManager);
readActiveScheme(indexedConfigurationElements[INDEX_ACTIVE_SCHEME], activeSchemeElementCount, preferenceMemento,
bindingManager);
readBindingsFromRegistry(indexedConfigurationElements[INDEX_BINDING_DEFINITIONS], bindingDefinitionCount,
bindingManager, commandManager);
readBindingsFromPreferences(preferenceMemento, bindingManager, commandManager);
}
}