/*******************************************************************************
* Copyright (c) 2006-2013 The RCP Company 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:
* The RCP Company - initial API and implementation
*******************************************************************************/
package com.rcpcompany.uibindings.internal.utils;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.util.SafeRunnable;
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;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.internal.PopupMenuExtender;
import com.rcpcompany.uibindings.IBinding;
import com.rcpcompany.uibindings.IBindingContext;
import com.rcpcompany.uibindings.IContainerBinding;
import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.IValueBinding;
import com.rcpcompany.uibindings.IViewerBinding;
import com.rcpcompany.uibindings.model.utils.BasicUtils;
import com.rcpcompany.uibindings.utils.IBindingContextSelectionProvider;
import com.rcpcompany.uibindings.utils.IFilteringTableAdapter;
import com.rcpcompany.utils.logging.LogUtils;
/**
* Implementation of {@link IBindingContextSelectionProvider}.
*
* @author Tonny Madsen, The RCP Company
*/
public class BindingContextSelectionProvider extends AbstractContextMonitor implements
IBindingContextSelectionProvider, ISelectionProvider, IDisposable {
/**
* Factory method for {@link IBindingContextSelectionProvider} that checks a provider does not
* already exist.
*
* @param context the context
* @param site the site
* @param setupSelectionProvider whether to set the selection provider on the site
* @return the new selection provider
*/
public static IBindingContextSelectionProvider adapt(IBindingContext context, IWorkbenchPartSite site,
boolean setupSelectionProvider) {
final BindingContextSelectionProvider provider = context.getService(BindingContextSelectionProvider.class);
if (provider != null) return provider;
return new BindingContextSelectionProvider(context, site, setupSelectionProvider);
}
/**
* Constructs and initializes a new selection provider...
*
* @param context the binding
* @param site the workbench site
* @param setupSelectionProvider whether to set the selection provider on the site
*/
public BindingContextSelectionProvider(IBindingContext context, IWorkbenchPartSite site,
boolean setupSelectionProvider) {
super(context);
mySite = site;
mySetupSelectionProvider = setupSelectionProvider;
if (context.getTop() == null) {
final IllegalStateException ex = new IllegalStateException("No top component set.");
LogUtils.error(context, null, ex);
throw ex;
}
init();
}
/**
* Initializes this filter.
*/
@Override
public void init() {
if (mySetupSelectionProvider) {
if (mySite.getSelectionProvider() != null) {
LogUtils.error(this,
"Site '" + mySite + "' already have a selection provider: " + mySite.getSelectionProvider());
}
mySite.setSelectionProvider(this);
}
Display.getCurrent().addFilter(SWT.FocusIn, myFocusListener);
createContextMenu();
super.init();
}
/**
* Map of all registered controls along with their providers.
*/
private final Map<Control, ISelectionProvider> myProviders = new HashMap<Control, ISelectionProvider>();
/**
* Selection listener that is installed on the current selection provider to forward changes in
* the current selection provider...
*/
private final ISelectionChangedListener mySelectionChangedListener = new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
checkSelection();
}
};
/**
* Empty selection used when there are no current selection.
*/
protected static final ISelection myEmptySelection = new ISelection() {
@Override
public boolean isEmpty() {
return true;
}
@Override
public String toString() {
return "*EMPTY SELECTION*";
};
};
@Override
public void addControl(Control control, ISelectionProvider provider) {
if (control.getShell() != myMenuManager.getMenu().getShell()) return;
myProviders.put(control, provider);
control.setMenu(myMenuManager.getMenu());
checkFocus();
}
@Override
public void removeControl(Control control) {
if (!control.isDisposed() && control.getShell() != myMenuManager.getMenu().getShell()) return;
myProviders.remove(control);
if (!control.isDisposed()) {
control.setMenu(null);
}
checkFocus();
}
@Override
public void addControl(Control control, IObservableValue selection) {
addControl(control, new ObservableValueSelectionProvider(selection));
}
@Override
public void addControl(Control control, IObservableList selection) {
addControl(control, new ObservableListSelectionProvider(selection));
}
@Override
public void addViewer(Viewer viewer) {
addControl(viewer.getControl(), viewer);
}
/**
* Check if the current focus control has a registered selection provider.
* <p>
* If so, a {@link ISelectionChangedListener} is added to the control.
* <p>
* Called whenever the focus has changed or the registered selection providers change
*/
protected void checkFocus() {
/*
* Find the provider for the closest enclosing widget
*/
Control c = myFocusControl;
ISelectionProvider provider = null;
while (c != null) {
provider = myProviders.get(c);
if (provider != null) {
break;
}
if (c.isDisposed()) {
break;
}
c = c.getParent();
}
if (provider == myCurrentProvider) return;
if (myCurrentProvider != null) {
myCurrentProvider.removeSelectionChangedListener(mySelectionChangedListener);
}
myCurrentProvider = provider;
if (myCurrentProvider != null) {
myCurrentProvider.addSelectionChangedListener(mySelectionChangedListener);
}
checkSelection();
}
/**
* Checks whether the current selection has changed and fires an event if it has...
*/
protected void checkSelection() {
ISelection selection = null;
if (myCurrentProvider != null) {
selection = myCurrentProvider.getSelection();
}
if (selection == null) {
selection = myEmptySelection;
}
if (BasicUtils.equals(selection, myCurrentSelection)) return;
myCurrentSelection = selection;
fireSelectionChanged(new SelectionChangedEvent(this, myCurrentSelection));
}
/**
* The workbench site where the menu is registered.
*/
private final IWorkbenchPartSite mySite;
/**
* Whether to setup this selection provider on the site.
*/
private final boolean mySetupSelectionProvider;
/**
* The current selection provider.
*/
protected ISelectionProvider myCurrentProvider = null;
/**
* The current selection.
*/
private ISelection myCurrentSelection = myEmptySelection;
/**
* org.eclipse.ui.internal class used to extend popup menus
*/
private PopupMenuExtender myPopupMenuExtender = null;
/**
* The menu.
*/
/* package */final MenuManager myMenuManager = new MenuManager();
/**
* List of selection change listeners (element type: <code>ISelectionChangedListener</code>).
*
* @see #fireSelectionChanged
*/
private final ListenerList selectionChangedListeners = new ListenerList();
protected Control myFocusControl;
private final Listener myFocusListener = new Listener() {
@Override
public void handleEvent(Event event) {
if (event.type != SWT.FocusIn) return;
/*
* Only react to focus changes within the top component
*/
final Control newFocusControl = (Control) event.widget;
Control c = newFocusControl;
while (c != null && c != getContext().getTop()) {
c = c.getParent();
}
if (c == null) return;
setFocusControl(newFocusControl);
}
};
private final Listener myFocusDisposeListener = new Listener() {
@Override
public void handleEvent(Event event) {
// LogUtils.error(myFocusControl, "Control is disposed!");
setFocusControl(null);
}
};
/**
* Creates the context menu.
*/
@SuppressWarnings("restriction")
private void createContextMenu() {
// myMenuManager.setRemoveAllWhenShown(true);
// mySite.registerContextMenu(myMenuManager, this);
// myMenuManager.add(new Separator("open"));
// myMenuManager.add(new Separator("undo"));
// myMenuManager.add(new Separator("new"));
// myMenuManager.add(new Separator("delete"));
// myMenuManager.add(new Separator("select"));
// myMenuManager.add(new Separator("navigation"));
// myMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
myMenuManager.createContextMenu(getContext().getTop());
getContext().getTop().setMenu(myMenuManager.getMenu());
// myMenuManager.getMenu().addMenuListener(new MenuListener() {
// @Override
// public void menuShown(MenuEvent e) {
// LogUtils.debug(BindingContextSelectionProvider.this, "");
// }
//
// @Override
// public void menuHidden(MenuEvent e) {
// LogUtils.debug(BindingContextSelectionProvider.this, "");
// }
// });
myPopupMenuExtender = new PopupMenuExtender(mySite.getId(), myMenuManager, this, mySite.getPart(), false);
}
protected void setFocusControl(Control newFocusControl) {
if (myFocusControl != newFocusControl) {
if (myFocusControl != null) {
myFocusControl.removeListener(SWT.Dispose, myFocusDisposeListener);
}
myFocusControl = newFocusControl;
if (myFocusControl != null) {
myFocusControl.addListener(SWT.Dispose, myFocusDisposeListener);
}
}
checkFocus();
}
/**
* Removes the filtering functionality again.
*/
@Override
public void dispose() {
setFocusControl(null);
// mySite.unregisterContextMenu(myMenuManager, this);
if (myPopupMenuExtender != null) {
myPopupMenuExtender.dispose();
myPopupMenuExtender = null;
}
if (mySetupSelectionProvider) {
mySite.setSelectionProvider(null);
}
Display.getCurrent().removeFilter(SWT.FocusIn, myFocusListener);
final Menu menu = myMenuManager.getMenu();
super.dispose();
/*
* Now that the service itself is disposed, there should be no references to the menu any
* more, so we can safely dispose it...
*/
if (menu != null) {
menu.dispose();
}
}
@Override
protected void bindingAdded(IBinding binding) {
if (binding instanceof IValueBinding) {
final IValueBinding vb = (IValueBinding) binding;
/*
* Ignore bindings where the UI attribute has a non-empty attribute name...
*/
final String attribute = vb.getUIAttribute().getAttribute();
if (attribute != null && attribute.length() > 0) return;
/*
* We need a control :-)
*/
final Control control = vb.getControl();
if (control == null) return;
final IObservable o = vb.getModelObservable();
if (o instanceof IObservableValue) {
addControl(control, (IObservableValue) o);
}
if (o instanceof IObservableList) {
addControl(control, (IObservableList) o);
}
} else if (binding instanceof IViewerBinding) {
final IViewerBinding vb = (IViewerBinding) binding;
addViewer(vb.getViewer()); // TODO SWTB
final IFilteringTableAdapter filtering = vb.getService(IFilteringTableAdapter.class);
if (filtering == null) return;
addControl(filtering.getText(), vb.getViewer()); // TODO SWTB
return;
} else if (binding instanceof IContainerBinding) {
final IContainerBinding vb = (IContainerBinding) binding;
final Control control = vb.getControl();
if (control == null) return;
final IObservableValue ov = vb.getSingleSelection();
if (ov == null) return;
addControl(control, ov);
return;
}
}
@Override
protected void bindingRemoved(IBinding binding) {
final Control control = binding.getControl();
if (control == null) return;
removeControl(control);
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
@Override
public ISelection getSelection() {
return myCurrentSelection;
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
@Override
public void setSelection(ISelection selection) {
if (myCurrentProvider != null) {
myCurrentProvider.setSelection(selection);
}
}
/**
* Notifies any selection changed listeners that the viewer's selection has changed. Only
* listeners registered at the time this method is called are notified.
*
* @param event a selection changed event
*
* @see ISelectionChangedListener#selectionChanged
*/
protected void fireSelectionChanged(final SelectionChangedEvent event) {
final Object[] listeners = selectionChangedListeners.getListeners();
for (final Object listener : listeners) {
final ISelectionChangedListener l = (ISelectionChangedListener) listener;
SafeRunnable.run(new SafeRunnable() {
@Override
public void run() {
l.selectionChanged(event);
}
});
}
}
/**
* Simple selection provider based on the value of an observable value.
*/
private final class ObservableValueSelectionProvider implements ISelectionProvider, IValueChangeListener {
private final ListenerList selectionChangedListeners = new ListenerList();
/**
* The current selection.
*/
private ISelection mySelection = myEmptySelection;
/**
* The observable value that forms the base of the selection provider.
*/
private final IObservableValue myValue;
protected ObservableValueSelectionProvider(IObservableValue value) {
myValue = value;
value.addValueChangeListener(this);
handleValueChange(null);
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
@Override
public ISelection getSelection() {
return mySelection;
}
@Override
public void setSelection(ISelection sel) {
// Not supported as this is a not a viewer
}
private void fireSelectionChanged() {
if (selectionChangedListeners != null) {
final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
final Object[] listeners = selectionChangedListeners.getListeners();
for (final Object l : listeners) {
final ISelectionChangedListener listener = (ISelectionChangedListener) l;
listener.selectionChanged(event);
}
}
}
@Override
public void handleValueChange(ValueChangeEvent event) {
Object value = myValue.getValue();
if (!(value instanceof EObject) && myValue instanceof IObserving) {
value = ((IObserving) myValue).getObserved();
}
if (value instanceof EObject) {
mySelection = new StructuredSelection(value);
} else {
mySelection = myEmptySelection;
}
fireSelectionChanged();
}
}
/**
* Simple selection provider based on the value of an observable list.
*/
private final class ObservableListSelectionProvider implements ISelectionProvider, IListChangeListener {
private final ListenerList selectionChangedListeners = new ListenerList();
/**
* The current selection.
*/
private ISelection mySelection = myEmptySelection;
/**
* The observable value that forms the base of the selection provider.
*/
private final IObservableList myList;
protected ObservableListSelectionProvider(IObservableList list) {
myList = list;
list.addListChangeListener(this);
}
@Override
public void addSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.add(listener);
}
@Override
public void removeSelectionChangedListener(ISelectionChangedListener listener) {
selectionChangedListeners.remove(listener);
}
@Override
public ISelection getSelection() {
return mySelection;
}
@Override
public void setSelection(ISelection sel) {
// Not supported as this is a not a viewer
}
private void fireSelectionChanged() {
if (selectionChangedListeners != null) {
final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());
final Object[] listeners = selectionChangedListeners.getListeners();
for (final Object listener2 : listeners) {
final ISelectionChangedListener listener = (ISelectionChangedListener) listener2;
listener.selectionChanged(event);
}
}
}
@Override
public void handleListChange(ListChangeEvent event) {
if (myList.isEmpty()) {
mySelection = myEmptySelection;
} else {
mySelection = new StructuredSelection(myList);
}
fireSelectionChanged();
}
}
}