package org.archstudio.archipelago2.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.archstudio.archipelago2.Archipelago2Utils;
import org.archstudio.archipelago2.IArchipelago2ContentProvider;
import org.archstudio.archipelago2.IArchipelago2LabelDecorator;
import org.archstudio.archipelago2.IArchipelago2LabelProvider;
import org.archstudio.archipelago2.IArchipelago2MenuContributor;
import org.archstudio.archipelago2.IArchipelago2Outline;
import org.archstudio.archipelago2.IArchipelago2Provider;
import org.archstudio.archipelago2.OutlineElementTransfer;
import org.archstudio.archipelago2.core.outline.CopyPasteObjRefMenuContributor;
import org.archstudio.archipelago2.core.outline.ObjRefLabelDecorator;
import org.archstudio.archipelago2.core.outline.ObjRefLabelProvider;
import org.archstudio.archipelago2.core.outline.PathContentProvider;
import org.archstudio.archipelago2.core.outline.PathLabelProvider;
import org.archstudio.archipelago2.core.outline.XAdlContentProvider;
import org.archstudio.archipelago2.core.outline.XAdlLabelProvider;
import org.archstudio.archipelago2.core.outline.swt.EditObjRefNameInOutine;
import org.archstudio.archipelago2.core.outline.swt.EditOnClickOfAlreadySelectedElement;
import org.archstudio.bna.ui.utils.AbstractBNAUI;
import org.archstudio.myx.fw.IMyxBrick;
import org.archstudio.myx.fw.MyxRegistry;
import org.archstudio.sysutils.SystemUtils;
import org.archstudio.xadl.XadlUtils;
import org.archstudio.xarchadt.IXArchADT;
import org.archstudio.xarchadt.IXArchADTTypeMetadata;
import org.archstudio.xarchadt.ObjRef;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Maintains an outline for Archipelago editors.
* <p/>
* Each element in the outline consists of a list of all nodes from the root {@link #docRef} node to
* a given element, capturing the path from the root. This allows content providers and listeners to
* receive and manipulate the full context of a particular path. The only exception is that content
* provides return only the child node objects rather than new lists; from these, this class create
* new element lists.
*
* @author sahendrickson@gmail.com (Scott A. Hendrickson)
*/
public class Archipelago2OutlinePage extends ContentOutlinePage
implements IArchipelago2Outline, ILabelProviderListener {
/** XArchADT instance backing this label provider. */
protected final IXArchADT xarch;
/** XArchADT root document reference. */
protected final ObjRef docRef;
/** The content providers supplied by the org.archstudio.archipelago2.outlines extension. */
protected List<IArchipelago2ContentProvider> contentProviders = new ArrayList<>();
/** The content providers supplied by the org.archstudio.archipelago2.outlines extension. */
protected List<IArchipelago2LabelProvider> labelProviders = new ArrayList<>();
/** The label decorators supplied by the org.archstudio.archipelago2.outlines extension. */
protected List<IArchipelago2LabelDecorator> labelDecorators = new ArrayList<>();
/** The menu contributors supplied by the org.archstudio.archipelago2.outlines extension. */
protected List<IArchipelago2MenuContributor> menuContributors = new ArrayList<>();
/**
* The listeners to notify if the user performs a "hard" selection, i.e., double clicks on an
* element or presses enter while it is selected.
*/
protected List<ISelectionChangedListener> hardSelectionListeners = new CopyOnWriteArrayList<>();
/**
* A cross reference of node objects to the tree elements (i.e., List<Object>) that contain them.
*/
protected Map<Object, Set<List<Object>>> nodeObjectToElementsMap = new HashMap<>();
/**
* A cross reference of tree elements (i.e., List<Object>) and the children node objects they
* contain.
*/
protected Map<Object, Set<Object>> elementToChildNodeObjectsMap = new HashMap<>();
/** The element to select and edit when it becomes available. */
protected List<Object> selectAndEditWhenAvailable = null;
/** The cell text editor. */
protected EditObjRefNameInOutine editor;
public Archipelago2OutlinePage(IXArchADT xarch, ObjRef docRef) {
this.xarch = xarch;
this.docRef = docRef;
}
/**
* Initializes a provider by calling its
* {@link IArchipelago2Provider#init(IXArchADT, ObjRef, org.eclipse.swt.widgets.Display, IArchipelago2Outline)
* init} method.
*
* @param provider the provider to initialize.
* @return the provider after initialization.
*/
private <T extends IArchipelago2Provider> T init(T provider) {
provider.init(xarch, docRef, getTreeViewer().getTree().getDisplay(), this);
if (provider instanceof IBaseLabelProvider) {
((IBaseLabelProvider) provider).addListener(this);
}
if (provider instanceof IArchipelago2ContentProvider) {
((IArchipelago2ContentProvider) provider).addListener(this);
}
return provider;
}
@Override
public void dispose() {
for (IArchipelago2Provider provider : Iterables.concat(contentProviders, labelProviders,
labelDecorators)) {
if (provider instanceof IBaseLabelProvider) {
((IBaseLabelProvider) provider).removeListener(this);
}
if (provider instanceof IArchipelago2ContentProvider) {
((IArchipelago2ContentProvider) provider).removeListener(this);
}
provider.dispose();
}
contentProviders.clear();
labelProviders.clear();
labelDecorators.clear();
super.dispose();
}
@Override
public void createControl(Composite parent) {
super.createControl(parent);
final TreeViewer treeViewer = getTreeViewer();
final Tree tree = treeViewer.getTree();
MyxRegistry myxRegistry = MyxRegistry.getSharedInstance();
IMyxBrick brick = myxRegistry.waitForBrick(Archipelago2MyxComponent.class);
// Read content and label provider extensions.
PathContentProvider pathContentProvider = init(new PathContentProvider());
IExtensionRegistry reg = Platform.getExtensionRegistry();
if (reg != null) {
for (IConfigurationElement configurationElement : reg
.getConfigurationElementsFor("org.archstudio.archipelago2.outlines")) {
String bundleName = configurationElement.getDeclaringExtension().getContributor().getName();
if ("Path".equals(configurationElement.getName())) {
pathContentProvider.addPath(configurationElement.getAttribute("name"));
} else if ("ContentProvider".equals(configurationElement.getName())) {
String providerClassName = configurationElement.getAttribute("class");
try {
Class<?> providerClass = Platform.getBundle(bundleName).loadClass(providerClassName);
contentProviders.add(init((IArchipelago2ContentProvider) providerClass.newInstance()));
} catch (Exception ignored) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, bundleName, "Class "
+ providerClassName + " either doesn't exist or has no default constructor."));
}
} else if ("LabelProvider".equals(configurationElement.getName())) {
String providerClassName = configurationElement.getAttribute("class");
try {
Class<?> providerClass = Platform.getBundle(bundleName).loadClass(providerClassName);
labelProviders.add(init((IArchipelago2LabelProvider) providerClass.newInstance()));
} catch (Exception ignored) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, bundleName, "Class "
+ providerClassName + " either doesn't exist or has no default constructor."));
}
} else if ("LabelDecorator".equals(configurationElement.getName())) {
String providerClassName = configurationElement.getAttribute("class");
try {
Class<?> providerClass = Platform.getBundle(bundleName).loadClass(providerClassName);
labelDecorators.add(init((IArchipelago2LabelDecorator) providerClass.newInstance()));
} catch (Exception ignored) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, bundleName, "Class "
+ providerClassName + " either doesn't exist or has no default constructor."));
}
} else if ("MenuContributor".equals(configurationElement.getName())) {
String providerClassName = configurationElement.getAttribute("class");
try {
Class<?> providerClass = Platform.getBundle(bundleName).loadClass(providerClassName);
menuContributors.add(init((IArchipelago2MenuContributor) providerClass.newInstance()));
} catch (Exception ignored) {
Activator.getDefault().getLog().log(new Status(IStatus.ERROR, bundleName, "Class "
+ providerClassName + " either doesn't exist or has no default constructor."));
}
}
}
}
contentProviders.add(pathContentProvider);
labelProviders.add(init(new PathLabelProvider(pathContentProvider)));
contentProviders.add(init(new XAdlContentProvider()));
labelProviders.add(init(new XAdlLabelProvider()));
labelProviders.add(init(new ObjRefLabelProvider()));
labelDecorators.add(init(new ObjRefLabelDecorator()));
menuContributors.add(init(new CopyPasteObjRefMenuContributor()));
for (Object provider : Iterables.concat(contentProviders, labelProviders, labelDecorators)) {
MyxRegistry.getSharedInstance().registerObject(brick, provider);
}
// Enable editing ObjRef name attribute in the outline view.
editor = new EditObjRefNameInOutine(this, treeViewer, xarch);
// Trigger edit on selection of already selected item.
new EditOnClickOfAlreadySelectedElement(this, treeViewer, editor);
// Add a content provider that accumulates content from the providers in contentProvider.
treeViewer.setContentProvider(new ITreeContentProvider() {
@Override
public void dispose() {}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
for (IArchipelago2ContentProvider provider : contentProviders) {
provider.inputChanged(viewer, oldInput, newInput);
}
}
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
@Override
public Object[] getChildren(Object parentElement) {
@SuppressWarnings("unchecked")
List<Object> parentPath = (List<Object>) parentElement;
Set<Object> children = new HashSet<>();
for (IArchipelago2ContentProvider provider : contentProviders) {
children.addAll(Arrays.asList(provider.getElements(parentPath)));
}
children.remove(null);
return updateCrossReferences(parentElement, children);
}
/**
* Updates nodeObjectToElementsMap and elementToChildNodeObjectsMap to reflect the new set of
* children.
*
* @param element The element containing the children.
* @param newChildren The new set of children for that element.
* @return the newChildren in the form of an array.
*/
@SuppressWarnings("unchecked")
private Object[] updateCrossReferences(Object element, Set<Object> newChildren) {
Set<Object> children = elementToChildNodeObjectsMap.get(element);
if (children == null) {
elementToChildNodeObjectsMap.put(element, children = new HashSet<>());
}
Set<Object> differingChildren = Sets.symmetricDifference(children, newChildren);
for (Object child : Lists.newArrayList(differingChildren)) {
Set<List<Object>> elements = nodeObjectToElementsMap.get(child);
if (elements == null) {
nodeObjectToElementsMap.put(child, elements = new HashSet<>());
}
List<Object> childElement = Archipelago2Utils.append((List<Object>) element, child);
if (children.remove(child)) {
elements.remove(childElement);
if (elements.size() == 0) {
nodeObjectToElementsMap.remove(child);
}
} else {
children.add(child);
elements.add(childElement);
}
}
if (newChildren.size() == 0) {
elementToChildNodeObjectsMap.remove(element);
}
List<Object> elementPath = (List<Object>) element;
List<List<Object>> childPaths = Lists.newArrayListWithCapacity(newChildren.size());
for (Object child : newChildren) {
childPaths.add(Archipelago2Utils.append(elementPath, child));
}
return childPaths.toArray(new Object[childPaths.size()]);
}
@Override
public boolean hasChildren(Object element) {
return getChildren(element).length > 0;
}
@Override
public Object getParent(Object element) {
@SuppressWarnings("unchecked")
List<Object> childPath = (List<Object>) element;
if (childPath.isEmpty()) {
return null;
}
return childPath.subList(0, childPath.size() - 1);
}
});
// Add a label provider that scans the labelProviders and labelDecorators for a label.
final IStyledLabelProvider labelProvider = new IStyledLabelProvider() {
@Override
public void dispose() {}
@Override
public void addListener(ILabelProviderListener listener) {}
@Override
public void removeListener(ILabelProviderListener listener) {}
@Override
public boolean isLabelProperty(Object element, String property) {
@SuppressWarnings("unchecked")
List<Object> nodePath = (List<Object>) element;
for (IArchipelago2LabelProvider provider : labelProviders) {
if (provider.isLabelProperty(nodePath, property)) {
return true;
}
}
for (IArchipelago2LabelDecorator provider : labelDecorators) {
if (provider.isLabelProperty(nodePath, property)) {
return true;
}
}
return false;
}
@Override
public Image getImage(Object element) {
@SuppressWarnings("unchecked")
List<Object> nodePath = (List<Object>) element;
Image image = null;
for (IArchipelago2LabelProvider provider : labelProviders) {
image = provider.getImage(nodePath);
if (image != null) {
break;
}
}
if (image != null) {
for (IArchipelago2LabelDecorator provider : labelDecorators) {
Image decoratedImage = provider.decorateImage(image, nodePath);
if (decoratedImage != null) {
image = decoratedImage;
}
}
}
return image;
}
@Override
public StyledString getStyledText(Object element) {
@SuppressWarnings("unchecked")
List<Object> nodePath = (List<Object>) element;
StyledString text = null;
for (IArchipelago2LabelProvider provider : labelProviders) {
text = provider.getStyledText(nodePath);
if (text != null) {
break;
}
}
if (text == null) {
Object node = nodePath.get(nodePath.size() - 1);
if (node instanceof ObjRef) {
text = new StyledString("[No label provider for " + node + " ("
+ XadlUtils.getType(xarch, (ObjRef) node).getSimpleName() + ")]");
}
text = new StyledString(
"[No label provider for " + node + " (" + node.getClass().getName() + ")]");
}
for (IArchipelago2LabelDecorator provider : labelDecorators) {
StyledString decoratedText = provider.decorateStyledText(text, nodePath);
if (decoratedText != null) {
text = decoratedText;
}
}
return text;
}
};
treeViewer.setLabelProvider(new DecoratingStyledCellLabelProvider(labelProvider, null, null));
// Add a sorter that places strings first, then sorts by XSD schema element order, then by class
// name, then by label.
treeViewer.setSorter(new ViewerSorter() {
Map<Object, Object> nodeCategories = new HashMap<>();
Map<Object, Integer> categoryValues = new HashMap<>();
@Override
public boolean isSorterProperty(Object element, String property) {
return true;
}
private Object getCategory(Object element) {
@SuppressWarnings("unchecked")
List<Object> nodePath = (List<Object>) element;
Object node = nodePath.get(nodePath.size() - 1);
// If node is an ObjRef, get the feature ID number for the node.
// The thinking is that feature IDs are assigned in the order that they appear in the schema
// file, and thus, will indicate the order of siblings.
if (node instanceof ObjRef) {
ObjRef objRef = (ObjRef) node;
ObjRef parentRef = xarch.getParent(objRef);
String name = xarch.getContainingFeatureName(objRef);
if (parentRef != null && name != null) {
IXArchADTTypeMetadata typeMetadata = xarch.getTypeMetadata(parentRef);
String nsURI = typeMetadata.getNsURI();
EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(nsURI);
if (ePackage != null) {
String typeName = typeMetadata.getTypeName();
EClassifier eClassifier = ePackage.getEClassifier(typeName);
if (eClassifier instanceof EClass) {
EClass eClass = (EClass) eClassifier;
EStructuralFeature feature = eClass.getEStructuralFeature(name);
if (feature != null) {
return feature.getFeatureID();
}
}
}
}
}
return node.getClass();
}
@Override
public int category(Object element) {
Integer category = categoryValues.get(nodeCategories.get(element));
return category != null ? category.intValue() : 0;
}
@SuppressWarnings("unchecked")
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
int cat1 = category(e1);
int cat2 = category(e2);
if (cat1 != cat2) {
return cat1 - cat2;
}
String ss1 = labelProvider.getStyledText(e1).toString();
String ss2 = labelProvider.getStyledText(e2).toString();
if (!ss1.equals(ss2)) {
return ss1.compareTo(ss2);
}
List<Object> l1 = (List<Object>) e1;
List<Object> l2 = (List<Object>) e2;
if (l1.size() == 0) {
if (l2.size() == 0) {
return 0;
}
return -1;
}
if (l2.size() == 0) {
return 1;
}
Object last1 = l1.get(l1.size() - 1);
Object last2 = l2.get(l2.size() - 1);
return last1.toString().compareTo(last2.toString());
}
@Override
public void sort(Viewer viewer, Object[] elements) {
nodeCategories.clear();
categoryValues.clear();
for (Object element : elements) {
Preconditions.checkNotNull(element);
Object category = Preconditions.checkNotNull(getCategory(element));
nodeCategories.put(element, category);
}
List<Object> categories = Lists.newArrayList(Sets.newHashSet(nodeCategories.values()));
Collections.sort(categories, new Comparator<Object>() {
@SuppressWarnings("unchecked")
@Override
public int compare(Object o1, Object o2) {
// Place strings first.
if (o1.equals(String.class)) {
return -1;
}
if (o2.equals(String.class)) {
return 1;
}
// Place numbers second, sort by the number.
if (o1 instanceof Number) {
if (o1 instanceof Comparable && o1.getClass() == o2.getClass()) {
return ((Comparable<Object>) o1).compareTo(o2);
}
return -1;
}
// Place all other objects last, sort by toString().
return o1.toString().compareTo(o2.toString());
}
});
for (int i = 0; i < categories.size(); ++i) {
categoryValues.put(categories.get(i), i);
}
super.sort(viewer, elements);
}
});
// Trigger a hard selection event upon double click.
treeViewer.addDoubleClickListener(new IDoubleClickListener() {
@Override
public void doubleClick(DoubleClickEvent event) {
fireSelectionEvent(new SelectionChangedEvent(event.getViewer(), event.getSelection()),
hardSelectionListeners);
}
});
// Trigger events on key press.
tree.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// A hard selection on pressing enter.
if (e.character == '\n') {
fireSelectionEvent(new SelectionChangedEvent(treeViewer, treeViewer.getSelection()),
hardSelectionListeners);
}
// A cell edit on pressing F2
if (e.keyCode == SWT.F2) {
ISelection selection = treeViewer.getSelection();
if (selection instanceof StructuredSelection) {
for (Object element : ((StructuredSelection) selection).toList()) {
editElement(element, 0);
break;
}
}
}
}
});
// Populate context menus with IArchipelago2MenuContributor instances.
MenuManager menuMgr = new MenuManager();
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
@SuppressWarnings("unchecked")
@Override
public void menuAboutToShow(IMenuManager manager) {
AbstractBNAUI.addMenuSeparators(manager);
ISelection selection = treeViewer.getSelection();
if (selection instanceof IStructuredSelection) {
for (Object element : ((IStructuredSelection) selection).toList()) {
for (IArchipelago2MenuContributor menuContributor : menuContributors) {
menuContributor.fillMenu(Archipelago2OutlinePage.this, (List<Object>) element,
manager);
}
}
}
}
});
Menu menu = menuMgr.createContextMenu(treeViewer.getControl());
treeViewer.getControl().setMenu(menu);
// Register drag and drop operations.
treeViewer.addDragSupport(DND.DROP_LINK,
new Transfer[] {(Transfer) OutlineElementTransfer.getInstance()}, new DragSourceListener() {
/*
* We have to hang on to the current selection because on some platforms (Mac) the tree
* selection is cleared when dragging
*/
private List<?> selectedElement = Lists.newArrayList();
@Override
public void dragStart(DragSourceEvent event) {
event.doit = false;
ISelection selection = treeViewer.getSelection();
if (selection instanceof ITreeSelection) {
ITreeSelection treeSelection = (ITreeSelection) selection;
if (treeSelection.size() == 1) {
selectedElement = (List<?>) treeSelection.getFirstElement();
if (!(selectedElement.get(selectedElement.size() - 1) instanceof String)) {
event.doit = true;
return;
}
}
}
}
@Override
public void dragSetData(DragSourceEvent event) {
event.doit = false;
if (OutlineElementTransfer.getInstance().isSupportedType(event.dataType)) {
if (selectedElement.size() > 0) {
if (OutlineElementTransfer.getInstance().isSupportedType(event.dataType)) {
event.doit = true;
event.data = selectedElement;
return;
}
}
}
}
@Override
public void dragFinished(DragSourceEvent event) {}
});
// Set outline root element.
treeViewer.setUseHashlookup(true);
treeViewer.setAutoExpandLevel(3);
treeViewer.setInput(Collections.singletonList(docRef));
}
/**
* Refreshes the tree for the given elements. The elements may be a node object, in which case
* {@link #nodeObjectToElementMap} is consulted to get tree elements to refresh; or, the elements
* may be a tree element, in which case the tree element is refreshed directly.
*
* @param event The event with the elements to refresh.
*/
@SuppressWarnings("unchecked")
@Override
public void labelProviderChanged(LabelProviderChangedEvent event) {
TreeViewer treeViewer = getTreeViewer();
List<List<Object>> elementsToRefresh = Lists.newArrayList();
for (Object element : SystemUtils.emptyIfNull(event.getElements())) {
if (element instanceof List) {
elementsToRefresh.add((List<Object>) element);
} else {
for (List<Object> treeElement : SystemUtils
.emptyIfNull(nodeObjectToElementsMap.get(element))) {
elementsToRefresh.add(treeElement);
}
}
}
Object editingElement = treeViewer.isCellEditorActive() ? editor.getEditingElement() : null;
for (Object element : elementsToRefresh) {
treeViewer.update(element, new String[] {""}); // Properties must be non-empty.
treeViewer.refresh(element, true);
}
if (editingElement != null) {
editor.editElement(editingElement, 0);
}
}
/**
* Fires a selection event to a list of listeners.
*
* @param event The selection event to fire.
* @param listeners The listeners to receive the event.
*/
protected void fireSelectionEvent(SelectionChangedEvent event,
List<ISelectionChangedListener> listeners) {
for (ISelectionChangedListener listener : listeners) {
try {
listener.selectionChanged(event);
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
}
/**
* Adds a listener that will be notified if the user performs a "hard" selection, i.e., double
* clicks on an outline node or presses enter while it is selected.
*
* @param listener The listener to receive the event.
*/
public void addHardSelectionListener(ISelectionChangedListener listener) {
hardSelectionListeners.add(listener);
}
/**
* Removes a listener that will be notified if the user performs a "hard" selection, i.e., double
* clicks on an outline node or presses enter while it is selected.
*
* @param listener The listener to receive the event.
*/
public void removeHardSelectionListener(ISelectionChangedListener listener) {
hardSelectionListeners.add(listener);
}
@Override
public void selectElement(final Object element) {
@SuppressWarnings("unchecked")
final List<Object> nodes = (List<Object>) element;
final TreeViewer treeViewer = getTreeViewer();
treeViewer.getTree().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= nodes.size(); ++i) {
List<Object> sublistElement = nodes.subList(0, i);
treeViewer.refresh(sublistElement, true);
treeViewer.expandToLevel(sublistElement, 1);
}
IStructuredSelection newSelection = new StructuredSelection(element);
treeViewer.setSelection(newSelection);
}
});
}
@Override
public void editElement(final Object element, final int column) {
getTreeViewer().getTree().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
editor.editElement(element, column);
}
});
}
}