blob: 8eab97ef27f0a9a0f871a9df4dd5d8e86ca99952 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2016 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
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 440810, 472654
*******************************************************************************/
package org.eclipse.ui.internal.keys.model;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Set;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.e4.ui.bindings.EBindingService;
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.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.keys.KeysPreferencePage;
import org.eclipse.ui.internal.keys.NewKeysPreferenceMessages;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.services.IServiceLocator;
import org.eclipse.ui.statushandlers.StatusManager;
/**
* @since 3.4
*
*/
public class KeyController {
private static final String DELIMITER = ","; //$NON-NLS-1$
private static final String ESCAPED_QUOTE = "\""; //$NON-NLS-1$
private static final String REPLACEMENT = "\"\""; //$NON-NLS-1$
/**
* The resource bundle from which translations can be retrieved.
*/
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(KeysPreferencePage.class.getName());
private ListenerList<IPropertyChangeListener> eventManager = null;
private BindingManager fBindingManager;
private ContextModel contextModel;
private SchemeModel fSchemeModel;
private BindingModel bindingModel;
private boolean notifying = true;
private ConflictModel conflictModel;
private IServiceLocator serviceLocator;
private ListenerList<IPropertyChangeListener> getEventManager() {
if (eventManager == null) {
eventManager = new ListenerList<>(ListenerList.IDENTITY);
}
return eventManager;
}
public void setNotifying(boolean b) {
notifying = b;
}
public boolean isNotifying() {
return notifying;
}
public void firePropertyChange(Object source, String propId, Object oldVal, Object newVal) {
if (!isNotifying()) {
return;
}
if (Objects.equals(oldVal, newVal)) {
return;
}
PropertyChangeEvent event = new PropertyChangeEvent(source, propId, oldVal, newVal);
for (IPropertyChangeListener listener : getEventManager()) {
listener.propertyChange(event);
}
}
public void addPropertyChangeListener(IPropertyChangeListener listener) {
getEventManager().add(listener);
}
public void removePropertyChangeListener(IPropertyChangeListener listener) {
getEventManager().remove(listener);
}
public void init(IServiceLocator locator) {
getEventManager().clear();
this.serviceLocator = locator;
fBindingManager = loadModelBackend(serviceLocator);
contextModel = new ContextModel(this);
contextModel.init(serviceLocator);
fSchemeModel = new SchemeModel(this);
fSchemeModel.init(fBindingManager);
bindingModel = new BindingModel(this);
bindingModel.init(serviceLocator, fBindingManager, contextModel);
conflictModel = new ConflictModel(this);
conflictModel.init(fBindingManager, bindingModel);
addSetContextListener();
addSetBindingListener();
addSetConflictListener();
addSetKeySequenceListener();
addSetSchemeListener();
addSetModelObjectListener();
}
private static BindingManager loadModelBackend(IServiceLocator locator) {
IBindingService bindingService = locator.getService(IBindingService.class);
BindingManager bindingManager = new BindingManager(new ContextManager(), new CommandManager());
final Scheme[] definedSchemes = bindingService.getDefinedSchemes();
try {
Scheme modelActiveScheme = null;
for (final Scheme scheme : definedSchemes) {
final Scheme copy = bindingManager.getScheme(scheme.getId());
copy.define(scheme.getName(), scheme.getDescription(), scheme.getParentId());
if (scheme.getId().equals(bindingService.getActiveScheme().getId())) {
modelActiveScheme = copy;
}
}
bindingManager.setActiveScheme(modelActiveScheme);
} catch (final NotDefinedException e) {
StatusManager.getManager().handle(new Status(IStatus.WARNING, WorkbenchPlugin.PI_WORKBENCH,
"Keys page found an undefined scheme", e)); //$NON-NLS-1$
}
bindingManager.setLocale(bindingService.getLocale());
bindingManager.setPlatform(bindingService.getPlatform());
Set<Binding> bindings = new HashSet<>();
EBindingService eBindingService = locator.getService(EBindingService.class);
bindings.addAll(eBindingService.getActiveBindings());
bindings.addAll(Arrays.asList(bindingService.getBindings()));
bindingManager.setBindings(bindings.toArray(new Binding[0]));
return bindingManager;
}
public ContextModel getContextModel() {
return contextModel;
}
public SchemeModel getSchemeModel() {
return fSchemeModel;
}
public BindingModel getBindingModel() {
return bindingModel;
}
public ConflictModel getConflictModel() {
return conflictModel;
}
private void addSetContextListener() {
addPropertyChangeListener(event -> {
if (event.getSource() == contextModel && CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
updateBindingContext((ContextElement) event.getNewValue());
}
});
}
private void addSetBindingListener() {
addPropertyChangeListener(event -> {
if (event.getSource() == bindingModel && CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
BindingElement binding = (BindingElement) event.getNewValue();
if (binding == null) {
conflictModel.setSelectedElement(null);
return;
}
conflictModel.setSelectedElement(binding);
ContextElement context = binding.getContext();
if (context != null) {
contextModel.setSelectedElement(context);
}
}
});
}
private void addSetConflictListener() {
addPropertyChangeListener(event -> {
if (event.getSource() == conflictModel && CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
if (event.getNewValue() != null) {
bindingModel.setSelectedElement((ModelElement) event.getNewValue());
}
}
});
}
private void addSetKeySequenceListener() {
addPropertyChangeListener(event -> {
if (BindingElement.PROP_TRIGGER.equals(event.getProperty())) {
updateTrigger((BindingElement) event.getSource(), (KeySequence) event.getOldValue(),
(KeySequence) event.getNewValue());
}
});
}
private void addSetModelObjectListener() {
addPropertyChangeListener(event -> {
if (event.getSource() instanceof BindingElement
&& ModelElement.PROP_MODEL_OBJECT.equals(event.getProperty())) {
if (event.getNewValue() != null) {
BindingElement element = (BindingElement) event.getSource();
Object oldValue = event.getOldValue();
Object newValue = event.getNewValue();
if (oldValue instanceof Binding && newValue instanceof Binding) {
conflictModel.updateConflictsFor(element, ((Binding) oldValue).getTriggerSequence(),
((Binding) newValue).getTriggerSequence(), false);
} else {
conflictModel.updateConflictsFor(element, false);
}
ContextElement context = element.getContext();
if (context != null) {
contextModel.setSelectedElement(context);
}
}
}
});
}
private void addSetSchemeListener() {
addPropertyChangeListener(event -> {
if (event.getSource() == fSchemeModel && CommonModel.PROP_SELECTED_ELEMENT.equals(event.getProperty())) {
changeScheme((SchemeElement) event.getOldValue(), (SchemeElement) event.getNewValue());
}
});
}
/**
* @param oldScheme
* @param newScheme
*/
protected void changeScheme(SchemeElement oldScheme, SchemeElement newScheme) {
if (newScheme == null || newScheme.getModelObject() == fBindingManager.getActiveScheme()) {
return;
}
try {
fBindingManager.setActiveScheme((Scheme) newScheme.getModelObject());
bindingModel.refresh(contextModel);
bindingModel.setSelectedElement(null);
} catch (NotDefinedException e) {
WorkbenchPlugin.log(e);
}
}
private void updateBindingContext(ContextElement context) {
if (context == null) {
return;
}
BindingElement activeBinding = (BindingElement) bindingModel.getSelectedElement();
if (activeBinding == null) {
return;
}
String activeSchemeId = fSchemeModel.getSelectedElement().getId();
Object obj = activeBinding.getModelObject();
if (obj instanceof KeyBinding) {
KeyBinding keyBinding = (KeyBinding) obj;
if (!keyBinding.getContextId().equals(context.getId())) {
final KeyBinding binding = new KeyBinding(keyBinding.getKeySequence(),
keyBinding.getParameterizedCommand(), activeSchemeId, context.getId(), null, null, null,
Binding.USER);
if (keyBinding.getType() == Binding.USER) {
fBindingManager.removeBinding(keyBinding);
} else {
fBindingManager.addBinding(new KeyBinding(keyBinding.getKeySequence(), null,
keyBinding.getSchemeId(), keyBinding.getContextId(), null, null, null, Binding.USER));
}
bindingModel.getBindingToElement().remove(activeBinding.getModelObject());
fBindingManager.addBinding(binding);
activeBinding.fill(binding, contextModel);
bindingModel.getBindingToElement().put(binding, activeBinding);
}
}
}
/**
* @param activeBinding
* @param oldSequence
* @param keySequence
*/
public void updateTrigger(BindingElement activeBinding, KeySequence oldSequence, KeySequence keySequence) {
if (activeBinding == null) {
return;
}
Object obj = activeBinding.getModelObject();
if (obj instanceof KeyBinding) {
KeyBinding keyBinding = (KeyBinding) obj;
if (!keyBinding.getKeySequence().equals(keySequence)) {
if (keySequence != null && !keySequence.isEmpty()) {
String activeSchemeId = fSchemeModel.getSelectedElement().getId();
ModelElement selectedElement = contextModel.getSelectedElement();
String activeContextId = selectedElement == null ? IContextService.CONTEXT_ID_WINDOW
: selectedElement.getId();
final KeyBinding binding = new KeyBinding(keySequence, keyBinding.getParameterizedCommand(),
activeSchemeId, activeContextId, null, null, null, Binding.USER);
Map<Binding, BindingElement> bindingToElement = bindingModel.getBindingToElement();
bindingToElement.remove(keyBinding);
if (keyBinding.getType() == Binding.USER) {
fBindingManager.removeBinding(keyBinding);
} else {
fBindingManager.addBinding(new KeyBinding(keyBinding.getKeySequence(), null,
keyBinding.getSchemeId(), keyBinding.getContextId(), null, null, null, Binding.USER));
}
fBindingManager.addBinding(binding);
activeBinding.fill(binding, contextModel);
bindingModel.getBindingToElement().put(binding, activeBinding);
// Remove binding for any system conflicts
bindingModel.setSelectedElement(activeBinding);
} else {
bindingModel.getBindingToElement().remove(keyBinding);
if (keyBinding.getType() == Binding.USER) {
fBindingManager.removeBinding(keyBinding);
} else {
fBindingManager.addBinding(new KeyBinding(keyBinding.getKeySequence(), null,
keyBinding.getSchemeId(), keyBinding.getContextId(), null, null, null, Binding.USER));
}
activeBinding.fill(keyBinding.getParameterizedCommand());
}
}
} else if (obj instanceof ParameterizedCommand) {
ParameterizedCommand cmd = (ParameterizedCommand) obj;
if (keySequence != null && !keySequence.isEmpty()) {
String activeSchemeId = fSchemeModel.getSelectedElement().getId();
ModelElement selectedElement = contextModel.getSelectedElement();
String activeContextId = selectedElement == null ? IContextService.CONTEXT_ID_WINDOW
: selectedElement.getId();
final KeyBinding binding = new KeyBinding(keySequence, cmd, activeSchemeId, activeContextId, null, null,
null, Binding.USER);
fBindingManager.addBinding(binding);
activeBinding.fill(binding, contextModel);
bindingModel.getBindingToElement().put(binding, activeBinding);
}
}
}
/**
* Replaces all the current bindings with the bindings in the local copy of the
* binding manager.
*
* @param bindingService The binding service that saves the changes made to the
* local copy of the binding manager
*/
public void saveBindings(IBindingService bindingService) {
try {
bindingService.savePreferences(fBindingManager.getActiveScheme(), fBindingManager.getBindings());
} catch (IOException e) {
logPreferenceStoreException(e);
}
}
/**
* 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;
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);
StatusUtil.handleStatus(message, exception, StatusManager.SHOW);
}
/**
* Filters contexts for the When Combo.
*
* @param actionSets <code>true</code> to filter action set contexts
* @param internal <code>false</code> to filter internal contexts
*
*/
public void filterContexts(boolean actionSets, boolean internal) {
contextModel.filterContexts(actionSets, internal);
}
/**
* Sets the bindings to default.
*
* @param bindingService
*/
public void setDefaultBindings(IBindingService bindingService) {
// Fix the scheme in the local changes.
final String defaultSchemeId = bindingService.getDefaultSchemeId();
final Scheme defaultScheme = fBindingManager.getScheme(defaultSchemeId);
try {
fBindingManager.setActiveScheme(defaultScheme);
} catch (final NotDefinedException e) {
// At least we tried....
}
// Restore any User defined bindings
for (Binding binding : fBindingManager.getBindings()) {
if (binding.getType() == Binding.USER) {
fBindingManager.removeBinding(binding);
}
}
bindingModel.refresh(contextModel);
saveBindings(bindingService);
}
public void exportCSV(Shell shell) {
final FileDialog fileDialog = new FileDialog(shell, SWT.SAVE | SWT.SHEET);
fileDialog.setFilterExtensions(new String[] { "*.csv" }); //$NON-NLS-1$
fileDialog.setFilterNames(new String[] { Util.translateString(RESOURCE_BUNDLE, "csvFilterName") }); //$NON-NLS-1$
fileDialog.setOverwrite(true);
final String filePath = fileDialog.open();
if (filePath == null) {
return;
}
final SafeRunnable runnable = new SafeRunnable() {
@Override
public final void run() throws IOException {
try (Writer fileWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(filePath), StandardCharsets.UTF_8))) {
final Object[] bindingElements = bindingModel.getBindings().toArray();
for (Object bindingElement : bindingElements) {
final BindingElement be = (BindingElement) bindingElement;
if (be.getTrigger() == null || be.getTrigger().isEmpty()) {
continue;
}
StringBuilder buffer = new StringBuilder();
buffer.append(ESCAPED_QUOTE + Util.replaceAll(be.getCategory(), ESCAPED_QUOTE, REPLACEMENT)
+ ESCAPED_QUOTE + DELIMITER);
buffer.append(ESCAPED_QUOTE + be.getName() + ESCAPED_QUOTE + DELIMITER);
buffer.append(ESCAPED_QUOTE + be.getTrigger().format() + ESCAPED_QUOTE + DELIMITER);
buffer.append(ESCAPED_QUOTE + be.getContext().getName() + ESCAPED_QUOTE + DELIMITER);
buffer.append(ESCAPED_QUOTE + be.getId() + ESCAPED_QUOTE);
buffer.append(System.getProperty("line.separator")); //$NON-NLS-1$
fileWriter.write(buffer.toString());
}
}
}
};
SafeRunner.run(runnable);
}
}