blob: d4c319298d68d9a1312b6202fea4fcf328fd272b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Christian W. Damus and others.
*
* All rights reserved. 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:
* Christian W. Damus - initial API and implementation
******************************************************************************/
package org.eclipse.emf.ecp.view.spi.swt.services;
import static java.util.Collections.singleton;
import static java.util.Objects.requireNonNull;
import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecp.view.spi.context.ViewModelContext;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeAddRemoveListener;
import org.eclipse.emf.ecp.view.spi.model.ModelChangeNotification;
import org.eclipse.emf.ecp.view.spi.model.VElement;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
/**
* A selection provider service that simply delegates the selection provider API
* to the selection provider registered for the "top-most" element in the view model,
* where "top-most" is the first element in a depth-first traversal.
*
* @since 1.20
*/
public class DefaultSelectionProviderService implements ECPSelectionProviderService {
private final Map<VElement, ISelectionProvider> registeredProviders = new HashMap<VElement, ISelectionProvider>();
private ViewModelContext context;
private DelegatingSelectionProvider selectionProvider;
private ModelChangeAddRemoveListener viewListener;
/**
* Initializes me.
*/
public DefaultSelectionProviderService() {
super();
}
@Override
public void instantiate(ViewModelContext context) {
this.context = context;
selectionProvider = new DelegatingSelectionProvider();
viewListener = createViewListener();
context.registerViewChangeListener(viewListener);
}
@Override
public void dispose() {
registeredProviders.clear();
selectionProvider = null;
if (context != null) {
context.unregisterViewChangeListener(viewListener);
viewListener = null;
context = null;
}
}
@Override
public int getPriority() {
return 0;
}
@Override
public ISelectionProvider getSelectionProvider() {
return selectionProvider;
}
@Override
public void registerSelectionProvider(VElement element, ISelectionProvider selectionProvider) {
if (registeredProviders.put(requireNonNull(element), requireNonNull(selectionProvider)) != selectionProvider) {
update();
}
}
private void update() {
ISelectionProvider delegate = null;
// Find the "top-most" selection provider
for (final Iterator<EObject> iter = getAllContents(singleton(context.getViewModel())); delegate == null
&& iter.hasNext();) {
delegate = registeredProviders.get(iter.next());
}
selectionProvider.setDelegate(delegate);
}
/**
* Create a listener that detects removal of elements from the view model
* and updates our selection-provider registration (and delegation) accordingly.
*
* @return the view model listener
*/
private ModelChangeAddRemoveListener createViewListener() {
return new ModelChangeAddRemoveListener() {
@Override
public void notifyRemove(Notifier notifier) {
if (registeredProviders.remove(notifier) != null) {
update();
}
}
@Override
public void notifyAdd(Notifier notifier) {
// Not interesting. We would need a registration call
}
@Override
public void notifyChange(ModelChangeNotification notification) {
// Not interesting
}
};
}
//
// Nested types
//
/**
* A selection provider that delegates to the "top-most" registered provider.
*/
private static class DelegatingSelectionProvider implements ISelectionProvider {
private ISelectionProvider delegate;
private final ISelectionChangedListener delegateListener = this::selectionChanged;
private final ListenerList<ISelectionChangedListener> listeners = new ListenerList<>();
DelegatingSelectionProvider() {
super();
}
@Override
public ISelection getSelection() {
return delegate == null ? StructuredSelection.EMPTY : delegate.getSelection();
}
@Override
public void setSelection(ISelection selection) {
if (delegate != null) {
delegate.setSelection(selection);
}
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
listeners.remove(listener);
}
void setDelegate(ISelectionProvider delegate) {
if (delegate == this.delegate) {
return;
}
if (this.delegate != null) {
this.delegate.removeSelectionChangedListener(delegateListener);
}
this.delegate = delegate;
if (this.delegate != null) {
this.delegate.addSelectionChangedListener(delegateListener);
fireSelection(delegate.getSelection());
}
}
void selectionChanged(SelectionChangedEvent event) {
fireSelection(event.getSelection());
}
private void fireSelection(ISelection selection) {
if (!listeners.isEmpty()) {
final SelectionChangedEvent event = new SelectionChangedEvent(this, selection);
listeners.forEach(l -> l.selectionChanged(event));
}
}
}
}