/*******************************************************************************
* Copyright (c) 2010, 2016 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
* Lars Vogel <Lars.Vogel@gmail.com> - Bug 429575
******************************************************************************/
package org.eclipse.e4.ui.workbench.swt.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.eclipse.core.commands.ParameterizedCommand;
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.Assert;
import org.eclipse.e4.core.commands.ECommandService;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.bindings.EBindingService;
import org.eclipse.e4.ui.bindings.internal.BindingTable;
import org.eclipse.e4.ui.bindings.internal.BindingTableManager;
import org.eclipse.e4.ui.internal.workbench.Activator;
import org.eclipse.e4.ui.internal.workbench.swt.Policy;
import org.eclipse.e4.ui.internal.workbench.swt.WorkbenchSWTActivator;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.commands.MBindingContext;
import org.eclipse.e4.ui.model.application.commands.MBindingTable;
import org.eclipse.e4.ui.model.application.commands.MBindings;
import org.eclipse.e4.ui.model.application.commands.MCommand;
import org.eclipse.e4.ui.model.application.commands.MKeyBinding;
import org.eclipse.e4.ui.model.application.commands.MParameter;
import org.eclipse.e4.ui.model.application.ui.MContext;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.services.EContextService;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
import org.eclipse.jface.bindings.TriggerSequence;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
/**
* Process contexts in the model, feeding them into the command service.
*/
public class BindingProcessingAddon {
private static final String[] DEFAULT_SCHEMES = { "org.eclipse.ui.defaultAcceleratorConfiguration" };
@Inject
private MApplication application;
@Inject
private IEventBroker broker;
@Inject
private ContextManager contextManager;
@Inject
private BindingTableManager bindingTables;
@Inject
@Optional
private BindingManager bindingManager;
@Inject
private ECommandService commandService;
@Inject
private EBindingService bindingService;
private EventHandler additionHandler;
private EventHandler contextHandler;
@PostConstruct
public void init() {
String[] schemes = DEFAULT_SCHEMES;
if (bindingManager != null) {
final Scheme activeScheme = bindingManager.getActiveScheme();
if (activeScheme != null) {
schemes = getSchemeIds(activeScheme.getId());
}
}
bindingTables.setActiveSchemes(schemes);
defineBindingTables();
activateContexts(application);
registerModelListeners();
}
private final String[] getSchemeIds(String schemeId) {
final List<String> strings = new ArrayList<>();
while (schemeId != null) {
strings.add(schemeId);
try {
schemeId = getScheme(schemeId).getParentId();
} catch (final NotDefinedException e) {
return new String[0];
}
}
return strings.toArray(new String[strings.size()]);
}
private final Scheme getScheme(String schemeId) {
return bindingManager.getScheme(schemeId);
}
private void activateContexts(Object me) {
if (me instanceof MBindings) {
MContext contextModel = (MContext) me;
MBindings container = (MBindings) me;
List<MBindingContext> bindingContexts = container
.getBindingContexts();
IEclipseContext context = contextModel.getContext();
if (context != null && !bindingContexts.isEmpty()) {
EContextService cs = context.get(EContextService.class);
for (MBindingContext element : bindingContexts) {
cs.activateContext(element.getElementId());
}
}
}
if (me instanceof MElementContainer) {
@SuppressWarnings("unchecked")
List<MUIElement> children = ((MElementContainer<MUIElement>) me).getChildren();
Iterator<MUIElement> i = children.iterator();
while (i.hasNext()) {
MUIElement e = i.next();
activateContexts(e);
}
}
}
private void defineBindingTables() {
if (Policy.DEBUG_CMDS) {
WorkbenchSWTActivator.trace(Policy.DEBUG_CMDS_FLAG, "Initialize binding tables from model", null); //$NON-NLS-1$
}
for (MBindingTable bindingTable : application.getBindingTables()) {
defineBindingTable(bindingTable);
}
}
/**
* @param bindingTable
*/
private void defineBindingTable(MBindingTable bindingTable) {
Assert.isNotNull(
bindingTable.getBindingContext(),
"Binding context referred to by the binding table \"" + bindingTable.getElementId() + "\""); //$NON-NLS-1$
Assert.isNotNull(bindingTable.getBindingContext().getElementId(),
"Element ID of binding table \"" + bindingTable.getElementId() + "\"."); //$NON-NLS-1$
final Context bindingContext = contextManager.getContext(bindingTable.getBindingContext().getElementId());
BindingTable table = bindingTables.getTable(bindingTable.getBindingContext().getElementId());
if (table == null) {
table = new BindingTable(bindingContext);
bindingTables.addTable(table);
}
for (MKeyBinding binding : bindingTable.getBindings()) {
defineBinding(table, bindingContext, binding);
}
}
/**
* @param bindingTable
* @param binding
*/
private void defineBinding(BindingTable bindingTable, Context bindingContext, MKeyBinding binding) {
Binding keyBinding = createBinding(bindingContext,
binding.getCommand(), binding.getParameters(),
binding.getKeySequence(), binding);
if (keyBinding != null && !binding.getTags().contains(EBindingService.DELETED_BINDING_TAG)) {
bindingTable.addBinding(keyBinding);
}
}
private Binding createBinding(Context bindingContext, MCommand cmdModel, List<MParameter> modelParms,
String keySequence, MKeyBinding binding) {
Binding keyBinding = null;
if (binding.getTransientData().get(EBindingService.MODEL_TO_BINDING_KEY) != null) {
try {
keyBinding = (Binding) binding.getTransientData().get(EBindingService.MODEL_TO_BINDING_KEY);
return keyBinding;
} catch (ClassCastException cce) {
System.err.println(
"Invalid type stored in transient data with the key "
+ EBindingService.MODEL_TO_BINDING_KEY);
return null;
}
}
if (cmdModel == null) {
Activator.log(LogService.LOG_ERROR, "binding with no command: " + binding); //$NON-NLS-1$
return null;
}
Map<String, Object> parameters = null;
if (modelParms != null && !modelParms.isEmpty()) {
parameters = new HashMap<>();
for (MParameter mParm : modelParms) {
parameters.put(mParm.getName(), mParm.getValue());
}
}
ParameterizedCommand cmd = commandService.createCommand(cmdModel.getElementId(), parameters);
TriggerSequence sequence = null;
sequence = bindingService.createSequence(keySequence);
if (cmd == null) {
System.err.println("Failed to find command for binding: " + binding); //$NON-NLS-1$
} else if (sequence == null) {
System.err.println("Failed to map binding: " + binding); //$NON-NLS-1$
} else {
try {
String schemeId = null;
String locale = null;
String platform = null;
Map<String, String> attrs = new HashMap<>();
List<String> tags = binding.getTags();
for (String tag : tags) {
// remember to skip the ':' in each tag!
if (tag.startsWith(EBindingService.SCHEME_ID_ATTR_TAG)) {
schemeId = tag.substring(9);
attrs.put(EBindingService.SCHEME_ID_ATTR_TAG, schemeId);
} else if (tag.startsWith(EBindingService.LOCALE_ATTR_TAG)) {
locale = tag.substring(7);
attrs.put(EBindingService.LOCALE_ATTR_TAG, locale);
} else if (tag.startsWith(EBindingService.PLATFORM_ATTR_TAG)) {
platform = tag.substring(9);
attrs.put(EBindingService.PLATFORM_ATTR_TAG, platform);
} else if (tag.startsWith(EBindingService.TYPE_ATTR_TAG)) {
// system bindings won't pass this attr
attrs.put(EBindingService.TYPE_ATTR_TAG, "user");
}
}
keyBinding = bindingService.createBinding(sequence, cmd, bindingContext.getId(), attrs);
binding.getTransientData().put(EBindingService.MODEL_TO_BINDING_KEY, keyBinding);
} catch (IllegalArgumentException e) {
if (Policy.DEBUG_MENUS) {
WorkbenchSWTActivator.trace(Policy.DEBUG_MENUS_FLAG, "failed to create: " + binding, e); //$NON-NLS-1$
}
return null;
}
}
return keyBinding;
}
private void updateBinding(MKeyBinding binding, boolean add, Object eObj) {
Object parentObj = ((EObject) binding).eContainer();
if (!(parentObj instanceof MBindingTable)) {
// the link will already be broken for removes, so we'll try this
if (eObj instanceof MBindingTable) {
parentObj = eObj;
}
}
if (parentObj == null) {
return;
}
MBindingTable bt = (MBindingTable) parentObj;
final Context bindingContext = contextManager.getContext(bt.getBindingContext().getElementId());
BindingTable table = bindingTables.getTable(bindingContext.getId());
if (table == null) {
Activator.log(LogService.LOG_ERROR, "Trying to create \'" + binding //$NON-NLS-1$
+ "\' without binding table " + bindingContext.getId()); //$NON-NLS-1$
return;
}
Binding keyBinding = createBinding(bindingContext, binding.getCommand(), binding.getParameters(),
binding.getKeySequence(), binding);
if (keyBinding != null) {
if (add) {
table.addBinding(keyBinding);
} else {
table.removeBinding(keyBinding);
}
}
}
@PreDestroy
public void dispose() {
unregisterModelListeners();
}
private void registerModelListeners() {
additionHandler = event -> {
Object elementObj = event
.getProperty(UIEvents.EventTags.ELEMENT);
if (elementObj instanceof MApplication) {
if (UIEvents.isADD(event)) {
for (Object newObj1 : UIEvents.asIterable(event,
UIEvents.EventTags.NEW_VALUE)) {
if (newObj1 instanceof MBindingTable) {
MBindingTable bt = (MBindingTable) newObj1;
final Context bindingContext = contextManager
.getContext(bt.getBindingContext()
.getElementId());
final BindingTable table = new BindingTable(
bindingContext);
bindingTables.addTable(table);
List<MKeyBinding> bindings = bt.getBindings();
for (MKeyBinding binding1 : bindings) {
Binding keyBinding = createBinding(
bindingContext,
binding1.getCommand(),
binding1.getParameters(),
binding1.getKeySequence(), binding1);
if (keyBinding != null) {
table.addBinding(keyBinding);
}
}
}
}
}
} else if (elementObj instanceof MBindingTable) {
// adding a binding
if (UIEvents.isADD(event)) {
for (Object newObj2 : UIEvents.asIterable(event,
UIEvents.EventTags.NEW_VALUE)) {
if (newObj2 instanceof MKeyBinding) {
MKeyBinding binding2 = (MKeyBinding) newObj2;
updateBinding(binding2, true, elementObj);
}
}
}
// removing a binding
else if (UIEvents.isREMOVE(event)) {
for (Object oldObj1 : UIEvents.asIterable(event,
UIEvents.EventTags.OLD_VALUE)) {
if (oldObj1 instanceof MKeyBinding) {
MKeyBinding binding3 = (MKeyBinding) oldObj1;
updateBinding(binding3, false, elementObj);
}
}
}
} else if (elementObj instanceof MKeyBinding) {
MKeyBinding binding4 = (MKeyBinding) elementObj;
String attrName = (String) event
.getProperty(UIEvents.EventTags.ATTNAME);
// System.out.println("MKeyBinding." + attrName + ": "
// + event.getProperty(UIEvents.EventTags.TYPE));
if (UIEvents.isSET(event)) {
Object oldObj2 = event
.getProperty(UIEvents.EventTags.OLD_VALUE);
if (UIEvents.KeyBinding.COMMAND.equals(attrName)) {
MKeyBinding oldBinding1 = (MKeyBinding) EcoreUtil
.copy((EObject) binding4);
oldBinding1.setCommand((MCommand) oldObj2);
updateBinding(oldBinding1, false,
((EObject) binding4).eContainer());
updateBinding(binding4, true, null);
} else if (UIEvents.KeySequence.KEYSEQUENCE
.equals(attrName)) {
MKeyBinding oldBinding2 = (MKeyBinding) EcoreUtil
.copy((EObject) binding4);
oldBinding2.setKeySequence((String) oldObj2);
updateBinding(oldBinding2, false,
((EObject) binding4).eContainer());
updateBinding(binding4, true, null);
}
} else if (UIEvents.KeyBinding.PARAMETERS.equals(attrName)) {
if (UIEvents.isADD(event)) {
Object newObj3 = event
.getProperty(UIEvents.EventTags.NEW_VALUE);
MKeyBinding oldBinding3 = (MKeyBinding) EcoreUtil
.copy((EObject) binding4);
if (UIEvents.EventTypes.ADD_MANY.equals(event
.getProperty(UIEvents.EventTags.TYPE))) {
oldBinding3.getParameters().removeAll(
(Collection<?>) newObj3);
} else {
oldBinding3.getParameters().remove(newObj3);
}
updateBinding(oldBinding3, false,
((EObject) binding4).eContainer());
updateBinding(binding4, true, null);
} else if (UIEvents.isREMOVE(event)) {
Object oldObj3 = event
.getProperty(UIEvents.EventTags.OLD_VALUE);
MKeyBinding oldBinding4 = (MKeyBinding) EcoreUtil
.copy((EObject) binding4);
if (UIEvents.EventTypes.REMOVE_MANY.equals(event
.getProperty(UIEvents.EventTags.TYPE))) {
@SuppressWarnings("unchecked")
Collection<MParameter> parms = (Collection<MParameter>) oldObj3;
oldBinding4.getParameters().addAll(parms);
} else {
oldBinding4.getParameters().add(
(MParameter) oldObj3);
}
updateBinding(oldBinding4, false,
((EObject) binding4).eContainer());
updateBinding(binding4, true, null);
}
}
// if we've updated the tags for an MKeyBinding
else if (UIEvents.ApplicationElement.TAGS.equals(attrName)) {
List<String> tags = binding4.getTags();
// if we added a deleted tag to the MKeyBinding, then
// remove it from the runtime binding tables
if (tags.contains(EBindingService.DELETED_BINDING_TAG)) {
updateBinding(binding4, false, elementObj);
}
// else we're adding the binding to the runtime tables
else {
updateBinding(binding4, true, elementObj);
}
}
}
};
broker.subscribe(UIEvents.BindingTableContainer.TOPIC_BINDINGTABLES, additionHandler);
broker.subscribe(UIEvents.BindingTable.TOPIC_BINDINGS, additionHandler);
broker.subscribe(UIEvents.KeyBinding.TOPIC_COMMAND, additionHandler);
broker.subscribe(UIEvents.KeyBinding.TOPIC_PARAMETERS, additionHandler);
broker.subscribe(UIEvents.KeySequence.TOPIC_KEYSEQUENCE, additionHandler);
broker.subscribe(UIEvents.ApplicationElement.TOPIC_TAGS, additionHandler);
contextHandler = event -> {
Object elementObj = event.getProperty(UIEvents.EventTags.ELEMENT);
Object newObj = event.getProperty(UIEvents.EventTags.NEW_VALUE);
if (UIEvents.EventTypes.SET.equals(event.getProperty(UIEvents.EventTags.TYPE))
&& newObj instanceof IEclipseContext) {
activateContexts(elementObj);
}
};
broker.subscribe(UIEvents.Context.TOPIC_CONTEXT, contextHandler);
}
private void unregisterModelListeners() {
broker.unsubscribe(additionHandler);
broker.unsubscribe(additionHandler);
broker.unsubscribe(additionHandler);
broker.unsubscribe(additionHandler);
broker.unsubscribe(additionHandler);
broker.unsubscribe(contextHandler);
}
}